--- # Shared Gateway — HTTPS listeners on *.home.keithjackson.dev, TLS terminated with the wildcard cert # (docs/09). Implemented by Cilium (GatewayClass `cilium`). The ingress VIP is announced via Cilium # L2 announcement on the flat LAN (docs/09, OQ-1 resolved). Phase 0 STUB. apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: shared namespace: gateway annotations: # PUBLIC DNS TARGET (2026-10-03, operator decision). Without this, external-dns publishes every # route on this Gateway with the Gateway's OWN address (${GATEWAY_VIP}, RFC1918), which is # unreachable from the internet. With it, every published `.${BASE_DOMAIN}` (and the apex) # becomes a CNAME to `wan.${BASE_DOMAIN}`, an A record that platform/externaldns/ddns.yaml keeps # pointed at the home WAN's current public IP. The WAN forwards 80/443 to ${GATEWAY_VIP}. # # Scope is EVERY route on this Gateway that publishes at all — the operator chose to make the # admin UIs (auth, grafana, headlamp, crafty, apex) publicly resolvable alongside the five # `exposure: public` apps. `internal` services stay unpublished via their # `external-dns.alpha.kubernetes.io/controller: none` route annotation, which external-dns checks # BEFORE it reads this target. external-dns v0.21.0 reads `target` from the Gateway only, never # from an HTTPRoute — see platform/externaldns/helmrelease.yaml. # # LAN and in-cluster clients are unaffected: PiHole and CoreDNS answer ${BASE_DOMAIN} with # ${GATEWAY_VIP} directly (split-horizon), so they never take this public path. external-dns.alpha.kubernetes.io/target: "wan.${BASE_DOMAIN}" spec: gatewayClassName: cilium listeners: - name: https protocol: HTTPS port: 443 hostname: "*.${BASE_DOMAIN}" tls: mode: Terminate certificateRefs: - kind: Secret name: wildcard-home-tls # produced by wildcard-certificate.yaml allowedRoutes: namespaces: from: Selector selector: matchLabels: # App namespaces opt in to attach HTTPRoutes here (WS-APPS renders the label + # ReferenceGrant from ServiceDefinition `exposure`). docs/09. gateway-access: "shared" - name: http-redirect protocol: HTTP port: 80 hostname: "*.${BASE_DOMAIN}" allowedRoutes: namespaces: from: Selector selector: matchLabels: gateway-access: "shared" # APEX listeners. The wildcard listeners above match ONLY subdomains: `*.home.keithjackson.dev` # does NOT intersect the bare apex `home.keithjackson.dev`, so an HTTPRoute for the apex host # (the Homepage landing dashboard, clusters/base/homepage.yaml) would bind to NO listener and # never serve. These two listeners give the apex its own hostname. TLS is the SAME wildcard cert: # `home.keithjackson.dev` is already a SAN on wildcard-certificate.yaml, so `wildcard-home-tls` # terminates the apex too — no extra Certificate needed. Same gateway-access=shared opt-in. - name: https-apex protocol: HTTPS port: 443 hostname: "${BASE_DOMAIN}" tls: mode: Terminate certificateRefs: - kind: Secret name: wildcard-home-tls # apex is a SAN on the wildcard cert (wildcard-certificate.yaml) allowedRoutes: namespaces: from: Selector selector: matchLabels: gateway-access: "shared" - name: http-apex-redirect protocol: HTTP port: 80 hostname: "${BASE_DOMAIN}" allowedRoutes: namespaces: from: Selector selector: matchLabels: gateway-access: "shared" # TODO(WS-NET, Phase 2): pin the L2-announced VIP / IP pool; add HTTP->HTTPS redirect HTTPRoute; # confirm cross-namespace ReferenceGrant pattern with WS-APPS. Ingress opens NORTH-SOUTH only — # it does not relax east-west default-deny (docs/09).