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
@@ -0,0 +1,77 @@
|
||||
---
|
||||
date: 2026-11-08
|
||||
branch: feat/dns-via-cloudflare-tunnel
|
||||
title: Public DNS via the Cloudflare Tunnel (Phase 2) — proxied CNAMEs, Plex/Headlamp LAN-only, L0 guard
|
||||
---
|
||||
|
||||
## What changed
|
||||
|
||||
One atomic change, so the conformance guard lands with the DNS flip.
|
||||
|
||||
- `platform/gateway/gateway.yaml`: the shared Gateway's `external-dns.alpha.kubernetes.io/target`
|
||||
is now the literal `fc81938b-5855-47f1-8a27-6286f56dda93.cfargotunnel.com` (was
|
||||
`wan.${BASE_DOMAIN}`). Comment block rewritten.
|
||||
- `platform/externaldns/helmrelease.yaml`: `extraArgs: [--cloudflare-proxied]` (global, because
|
||||
v0.23.0 reads per-object `cloudflare-proxied` from the route/Service, never the Gateway). The
|
||||
public-target comment block rewritten, including the prefix-regression risk.
|
||||
- `apps/vms/unifi/50-service-l4.yaml`, `platform/crafty/service-l4.yaml`:
|
||||
`external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"` (L4 A records stay DNS-only). They
|
||||
are the only `service`-source publishers in the tree and live (measured).
|
||||
- Plex web UI LAN/VPN-only: `catalog/plex.yaml` `exposure: public` → `internal`; `apps/wave-0/plex.yaml`
|
||||
re-rendered with the canonical command into a scratch dir and ONLY plex.yaml copied back. The
|
||||
render diff is exactly +2 lines (`annotations: external-dns.alpha.kubernetes.io/controller: none`
|
||||
on HTTPRoute plex-32400). No other golden moved, and nothing was onboarded (vm-demo/keycloak files
|
||||
were not touched). ADVERTISE_IP, the L4 VIP .24 and `plex-remote.yaml` are untouched.
|
||||
- Headlamp LAN/VPN-only: `controller: none` on the hand-authored `platform/headlamp/httproute.yaml`.
|
||||
- `conformance/tests/l0-static/internal-services-publish-no-public-dns/check.sh`: header and
|
||||
register prose updated; new assertion (9):
|
||||
(9a) Gateway target == `<tunnel: from platform/cloudflared/config.yaml>.cfargotunnel.com`, and
|
||||
`--cloudflare-proxied` is set; (9b) every `service`-source publisher has `cloudflare-proxied:
|
||||
"false"` and no HTTPRoute opts out of proxying; (9c) no undisowned route names a hostname the
|
||||
tunnel 404s, the only `service` publishers of a 404'd name are the declared L4 set
|
||||
{unifi, crafty-mc}, and every disowned route's hostname IS 404'd by the tunnel (lists kept
|
||||
consistent both ways); (9d) plex and headlamp pinned disowned. Its `chainsaw-test.yaml` gap prose
|
||||
updated.
|
||||
- `conformance/tests/l2-behavioral/dns-exposure/chainsaw-test.yaml`: fixture Service gets
|
||||
`cloudflare-proxied: "false"`, else the global flag proxies it and the exact-IP poll fails.
|
||||
- Docs: `platform/externaldns/ddns.yaml` header (wan. is WireGuard-only), `platform/externaldns/README.md`,
|
||||
`platform/gateway/README.md`, `runbooks/plex-remote-access.md`, `runbooks/cloudflare-tunnel.md`
|
||||
(Phase 2 done; operator step to close WAN tcp/80+443 and keep udp/51820 + tcp/32400; rotation
|
||||
now also swaps the Gateway UUID; rollback path).
|
||||
|
||||
## What I verified
|
||||
|
||||
- external-dns v0.23.0 source, read at the tag: `source/gateway.go` takes target overrides from
|
||||
`gw.Annotations` and provider-specific (incl. `cloudflare-proxied`) from the ROUTE's annotations;
|
||||
`annotations.SetAnnotationPrefix` re-derives `CloudflarePrefix`, so the alpha-prefixed key is the
|
||||
one read here; `provider/cloudflare` `shouldBeProxied` starts from `--cloudflare-proxied` and a
|
||||
parsed `"false"` annotation overrides it.
|
||||
- (8b) only forbids `annotation-prefix` / `enable-legacy-annotation-prefix` in extraArgs; a mutation
|
||||
confirmed `--cloudflare-proxied` passes and `--annotation-prefix=x/` fails.
|
||||
- The L0 check passes (116 assertions) and each new assertion FAILS on its mutation: Gateway target
|
||||
back to wan (9a), flag removed (9a), unifi proxied "true" (9b), headlamp undisowned (9c,9d), plex
|
||||
golden undisowned (9c,9d,(1)), jackett removed from the tunnel's 404 list (9c).
|
||||
`cloudflared-ingress-rules` still passes; `render.py --selftest` PASS.
|
||||
- Live, read-only (kubectl k3s-prod, ~23:55Z): 17 HTTPRoutes; the six internal apps carry
|
||||
`controller: none`; plex and headlamp do not (yet); the only Services with an external-dns
|
||||
hostname are crafty/crafty-l4-tcp and unifi/unifi-l4-tcp; Gateway target is currently
|
||||
`wan.home.keithjackson.dev`.
|
||||
- Toolbox gate result: see the PR description (rc and skipped stages quoted there).
|
||||
|
||||
## What I did NOT verify
|
||||
|
||||
- Anything after merge: that external-dns actually UPDATEs the eight public names to proxied CNAMEs
|
||||
to the tunnel, DELETEs plex + headlamp and their TXT, and leaves unifi/crafty-mc/wan untouched.
|
||||
Expected effect is listed in the PR; the PM must read external-dns logs and `dig @1.1.1.1`.
|
||||
- A rendered HelmRelease: I read the chart 1.23.0 `templates/deployment.yaml` (a list `extraArgs`
|
||||
is emitted item-by-item via `tpl . $ | quote`), but did not `helm template` it.
|
||||
- kubectl OIDC from nodes: nodes resolve publicly, so `keycloak.` discovery/JWKS now goes through
|
||||
Cloudflare + the tunnel. No `oidc-ca-file` is set (system roots), so the edge cert should validate,
|
||||
but Cloudflare bot/WAF settings on that path are untested.
|
||||
- RISK: if the annotation prefix stops being honoured at runtime again (11-07 incident class),
|
||||
`target` is ignored and every name becomes a PROXIED A record at 192.168.2.20, while internal
|
||||
names publish. Assertion (8) only keeps the tree and `values.annotationPrefix` consistent; it
|
||||
cannot catch a controller that ignores the flag. No runtime alert exists for it.
|
||||
- The router WAN 80/443 close is an operator step, not done. The Pi-hole wildcard trim is a later PR.
|
||||
- Follow-up: spec requirement IDs for this (proposed R-DNS-04/05, R-SEC-04) once the spec ADR merges;
|
||||
`platform/pm-agent/README.md` and a comment in `catalog/pihole.yaml` still describe the wan. CNAME.
|
||||
Reference in new issue
Block a user