Files
home-network-k3s-impl/platform/gateway/gateway.yaml
T
jakman85andClaude Opus 5.5 a1e808e697
gate / gate (pull_request) Successful in 1m46s
gate / conformance (pull_request) Has been skipped
feat(dns): publish Gateway names to the WAN via in-cluster DDNS
Gateway-published records now CNAME to wan.${BASE_DOMAIN}, which a
favonia/cloudflare-ddns Deployment keeps at the home WAN's current public
IP. Operator decision: every Gateway-published name goes public; internal
services (controller: none) remain unpublished.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-03 16:59:53 -05:00

91 lines
4.0 KiB
YAML

---
# 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 `<svc>.${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).