diff --git a/deploy-k3s/manifests/admin/deployment.yaml b/deploy-k3s/manifests/admin/deployment.yaml index 53f18ec..7f70c5f 100644 --- a/deploy-k3s/manifests/admin/deployment.yaml +++ b/deploy-k3s/manifests/admin/deployment.yaml @@ -82,7 +82,7 @@ spec: timeoutSeconds: 5 livenessProbe: httpGet: - path: /admin/ + path: / port: 3000 initialDelaySeconds: 30 periodSeconds: 30 diff --git a/deploy-k3s/manifests/ingress/ingress-simple.yaml b/deploy-k3s/manifests/ingress/ingress-simple.yaml index f9c6c6f..a44ee62 100644 --- a/deploy-k3s/manifests/ingress/ingress-simple.yaml +++ b/deploy-k3s/manifests/ingress/ingress-simple.yaml @@ -1,11 +1,10 @@ -# Simple hostname-based Ingress — no TLS (Cloudflare Flexible handles edge -# TLS, CF→origin is plain HTTP on 80). Upgrade to Full (strict) by -# adding back a `tls:` block with a Cloudflare Origin CA cert stored in -# secret/cloudflare-origin-cert. +# Hostname-based Ingress with TLS terminated at Traefik using the +# Cloudflare Origin CA cert (secret/cloudflare-origin-cert). CF→origin +# encryption enables CF SSL mode "Full (strict)". # # Middleware chain (security headers, rate limit, CF-only allowlist, admin -# basic auth) is defined in `middleware.yaml` but NOT attached here — -# annotate this ingress to turn any of them on. +# basic auth) is defined in `middleware.yaml`. security-headers + rate-limit +# are attached below via annotation. apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -13,8 +12,15 @@ metadata: namespace: honeydue labels: app.kubernetes.io/part-of: honeydue + annotations: + traefik.ingress.kubernetes.io/router.middlewares: honeydue-security-headers@kubernetescrd,honeydue-rate-limit@kubernetescrd spec: ingressClassName: traefik + tls: + - hosts: + - api.myhoneydue.com + - myhoneydue.com + secretName: cloudflare-origin-cert rules: - host: api.myhoneydue.com http: @@ -46,8 +52,14 @@ metadata: namespace: honeydue labels: app.kubernetes.io/part-of: honeydue + annotations: + traefik.ingress.kubernetes.io/router.middlewares: honeydue-security-headers@kubernetescrd,honeydue-rate-limit@kubernetescrd spec: ingressClassName: traefik + tls: + - hosts: + - admin.myhoneydue.com + secretName: cloudflare-origin-cert rules: - host: admin.myhoneydue.com http: @@ -67,8 +79,14 @@ metadata: namespace: honeydue labels: app.kubernetes.io/part-of: honeydue + annotations: + traefik.ingress.kubernetes.io/router.middlewares: honeydue-security-headers@kubernetescrd,honeydue-rate-limit@kubernetescrd spec: ingressClassName: traefik + tls: + - hosts: + - app.myhoneydue.com + secretName: cloudflare-origin-cert rules: - host: app.myhoneydue.com http: diff --git a/deploy-k3s/manifests/ingress/middleware.yaml b/deploy-k3s/manifests/ingress/middleware.yaml index 5ea56b3..67b87d6 100644 --- a/deploy-k3s/manifests/ingress/middleware.yaml +++ b/deploy-k3s/manifests/ingress/middleware.yaml @@ -27,7 +27,10 @@ spec: X-Content-Type-Options: "nosniff" X-Frame-Options: "DENY" Strict-Transport-Security: "max-age=31536000; includeSubDomains" - Content-Security-Policy: "default-src 'self'; frame-ancestors 'none'" + # Content-Security-Policy is intentionally NOT set here — the Go API + # sets a CSP in internal/router/router.go that permits Google Fonts + # for the landing page. Two CSP headers would intersect and break it. + # admin and web apps set their own CSP via Next.js middleware. Permissions-Policy: "camera=(), microphone=(), geolocation=()" X-Permitted-Cross-Domain-Policies: "none" diff --git a/deploy-k3s/manifests/network-policies.yaml b/deploy-k3s/manifests/network-policies.yaml index cf5e08c..afb9e25 100644 --- a/deploy-k3s/manifests/network-policies.yaml +++ b/deploy-k3s/manifests/network-policies.yaml @@ -47,10 +47,19 @@ spec: policyTypes: - Ingress ingress: + # Traefik runs as DaemonSet with hostNetwork=true, so traffic from it + # arrives with the NODE IP as source (not a pod IP). The node pod CIDR + # 10.42.0.0/16 covers any intra-cluster caller; the three node IPs + # cover Traefik on hostNetwork. - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: kube-system + - ipBlock: + cidr: 178.105.32.198/32 # ubuntu-8gb-nbg1-1 + - ipBlock: + cidr: 178.104.247.152/32 # ubuntu-8gb-nbg1-2 + - ipBlock: + cidr: 178.104.249.189/32 # ubuntu-8gb-nbg1-3 + - ipBlock: + cidr: 10.42.0.0/16 # cluster pod CIDR ports: - protocol: TCP port: 8000 @@ -69,10 +78,17 @@ spec: policyTypes: - Ingress ingress: + # Traefik runs as DaemonSet with hostNetwork=true — see allow-ingress-to-api + # for the rationale. Same ipBlock list. - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: kube-system + - ipBlock: + cidr: 178.105.32.198/32 + - ipBlock: + cidr: 178.104.247.152/32 + - ipBlock: + cidr: 178.104.249.189/32 + - ipBlock: + cidr: 10.42.0.0/16 ports: - protocol: TCP port: 3000 @@ -200,3 +216,62 @@ spec: ports: - protocol: TCP port: 8000 + +--- +# --- Web: allow ingress from Traefik (kube-system namespace) --- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-to-web + namespace: honeydue +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: web + policyTypes: + - Ingress + ingress: + # Traefik runs as DaemonSet with hostNetwork=true — see allow-ingress-to-api + # for the rationale. Same ipBlock list. + - from: + - ipBlock: + cidr: 178.105.32.198/32 + - ipBlock: + cidr: 178.104.247.152/32 + - ipBlock: + cidr: 178.104.249.189/32 + - ipBlock: + cidr: 10.42.0.0/16 + ports: + - protocol: TCP + port: 3000 + +--- +# --- Web: allow egress for the Next.js server-side proxy routes --- +# Browser → app.myhoneydue.com → web pod (Node.js) → api.myhoneydue.com +# The web pod resolves api.myhoneydue.com via public DNS and hits +# Cloudflare (143.). We don't know which CF IP yet at policy time, so +# allow HTTPS to public ipBlock (except private CIDRs). +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress-from-web + namespace: honeydue +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: web + policyTypes: + - Egress + egress: + # HTTPS to public (api.myhoneydue.com via CF, PostHog, any other remote) + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + ports: + - protocol: TCP + port: 443