feat(dns): publish public names as proxied CNAMEs to the Cloudflare Tunnel
Gateway target -> fc81938b-...cfargotunnel.com, --cloudflare-proxied globally, L4 service publishers (unifi, crafty-mc) pinned DNS-only, Plex web UI and Headlamp disowned (LAN/VPN-only). L0 internal-services-publish-no-public-dns gains assertion (9) tying the Gateway target to the tunnel UUID, requiring cloudflare-proxied=false on every service-source publisher, and keeping the tunnel's 404 list and the unpublished set consistent. L2 dns-exposure fixture opts out of proxying. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2e777e13ca
commit
92944486d1
16 files changed
+417
-88
No files matched your search
@@ -30,5 +30,9 @@ east-west default-deny (docs/09).
|
||||
|
||||
- **Phase 2 (WS-NET):** pin the L2-announced ingress VIP / IP pool (Cilium L2 announcement, docs/09);
|
||||
add the HTTP→HTTPS redirect route; confirm the cross-namespace ReferenceGrant pattern with WS-APPS.
|
||||
- **Public exposure** is a **manual edge port-forward** of 80/443 to the Gateway VIP (single SOHO
|
||||
router, docs/09) — a documented manual step, the analogue of the old pfSense exception.
|
||||
- **Public exposure** is via the **Cloudflare Tunnel** (2026-11-08, `platform/cloudflared`): the
|
||||
Gateway's `external-dns.alpha.kubernetes.io/target` is `<tunnel-uuid>.cfargotunnel.com`, so every
|
||||
published name is a proxied CNAME to the tunnel and cloudflared forwards to this Gateway's
|
||||
ClusterIP. No inbound WAN 80/443 forward is needed (the old manual edge port-forward is retired —
|
||||
runbooks/cloudflare-tunnel.md). LAN/VPN-only names (internal apps, plex, headlamp) carry
|
||||
`controller: none` on their route and are 404'd by the tunnel.
|
||||
@@ -8,22 +8,34 @@ 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}.
|
||||
# PUBLIC DNS TARGET = THE CLOUDFLARE TUNNEL (2026-11-08, operator decision; replaces the
|
||||
# 2026-10-03 `wan.${BASE_DOMAIN}` target). external-dns v0.23.0's gateway-httproute source reads
|
||||
# `target` from the GATEWAY only (source/gateway.go: `overrides: annotations.
|
||||
# TargetsFromTargetAnnotation(gw.Annotations)`) — never from an HTTPRoute — so this one value moves
|
||||
# EVERY published route on this Gateway at once. Each published `<svc>.${BASE_DOMAIN}` (and the
|
||||
# apex) becomes a CNAME to `<tunnel-uuid>.cfargotunnel.com`, and platform/externaldns/helmrelease.yaml
|
||||
# sets `--cloudflare-proxied` so the CNAME is orange-clouded: public clients resolve Cloudflare
|
||||
# anycast, Cloudflare terminates edge TLS (Total TLS) and hands the request to the in-cluster
|
||||
# cloudflared connectors (platform/cloudflared), which forward to this Gateway's ClusterIP. No
|
||||
# origin address of ours appears in public DNS, and no inbound WAN port is needed for HTTP(S).
|
||||
#
|
||||
# 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.
|
||||
# THE UUID IS A LITERAL, AND IT MUST EQUAL `tunnel:` IN platform/cloudflared/config.yaml. A CNAME
|
||||
# to any other <uuid>.cfargotunnel.com is a dead name (Cloudflare answers 1016). That equality is
|
||||
# asserted on every gate run by conformance/tests/l0-static/internal-services-publish-no-public-dns
|
||||
# (assertion 9a). A literal rather than a cluster-var keeps this change off clusters/**.
|
||||
#
|
||||
# Scope: every route on this Gateway that publishes at all. `internal` services (and the
|
||||
# operator-designated LAN/VPN-only names: plex, headlamp) stay unpublished via their
|
||||
# `external-dns.alpha.kubernetes.io/controller: none` route annotation, which external-dns
|
||||
# applies BEFORE it reads this target. Those same names map to `http_status:404` in the tunnel's
|
||||
# ingress rules, so even a hand-made record cannot reach them through the tunnel.
|
||||
#
|
||||
# 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}"
|
||||
#
|
||||
# `wan.${BASE_DOMAIN}` (platform/externaldns/ddns.yaml) is NO LONGER a target of anything here;
|
||||
# it stays only as the WireGuard udp/51820 endpoint.
|
||||
external-dns.alpha.kubernetes.io/target: "fc81938b-5855-47f1-8a27-6286f56dda93.cfargotunnel.com"
|
||||
spec:
|
||||
gatewayClassName: cilium
|
||||
listeners:
|
||||
|
||||
Reference in new issue
Block a user