Files
home-network-k3s-impl/platform/cloudflared
jakman85andClaude Opus 5.5 aabc8c60b6 feat(cloudflared): Cloudflare Tunnel connector, Phase 1 (isolated, no traffic change)
Adds platform/cloudflared: a locally-managed tunnel whose ingress rules live in
git. LAN/VPN-only hosts (plex, plex-vip, jackett, pihole, pihole-dns, semaphore,
qbittorrent, radarr, sonarr, headlamp, unifi, crafty-mc) map to 404. The apex
and *.home go to cilium-gateway-shared.gateway over TLS with matchSNItoHost.
Last comes a catch-all 404. Nothing routes to the Kubernetes API.

- 2-replica Deployment, hard node spread, non-root, pinned digest, PDB
- default-deny plus an egress CNP: Cloudflare edge 7844/443, the Gateway, and
  Gateway backends by gateway-access=shared minus the LAN-only namespaces
  (needed because of the CVE-2025-30162 L7LB egress re-check)
- PodMonitor plus two alerts that fire on absent series, with promtool tests
- its own Flux Kustomization (dependsOn platform-config, wait:false, nothing
  depends on it), plus SOPS decryption patches in both overlays
- placeholder credentials (SOPS-encrypted) so it can merge before enrolment
- L0 cloudflared-ingress-rules, and a cloudflared_ scrape owner
- runbooks/cloudflare-tunnel.md and decisions/2026-11-08-cloudflare-tunnel.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 12:38:22 -06:00
..

platform/cloudflared

Cloudflare Tunnel connector: inbound HTTPS from the Cloudflare edge to the shared Gateway, outbound-only from the cluster (no router port forward, no hairpin, no split-DNS dependency).

  • Flux tier: clusters/base/cloudflared.yaml — its own Kustomization, dependsOn: [platform-config], wait: false, and nothing depends on it. A missing or invalid credential can affect only the tunnel.
  • Runbook (enrolment, canary, alerts, rollback): runbooks/cloudflare-tunnel.md
  • Design record: decisions/2026-11-08-cloudflare-tunnel.md
  • Guard: conformance/tests/l0-static/cloudflared-ingress-rules
File What it is
config.yaml The locally-managed tunnel: literal tunnel UUID + ingress rules. LAN/VPN-only hosts → 404, apex + wildcard → https://cilium-gateway-shared.gateway.svc.cluster.local:443 with matchSNItoHost, catch-all 404. Never the Kubernetes API.
cloudflared-credentials.sops.yaml SOPS-encrypted tunnel credentials JSON. Ships with REPLACE-ME; the operator fills it.
deployment.yaml 2 replicas, hard spread across nodes, non-root, read-only root fs, pinned digest; PDB minAvailable: 1.
networkpolicy.yaml default-deny (single owner — not an app namespace) + Prometheus scrape allow + egress CNP: world 7844 tcp/udp and 443, the Gateway (ingress entity), and Gateway backends by gateway-access=shared minus the LAN-only namespaces.
podmonitor.yaml, alerts.yaml Scrape of :2000 and the two alerts, both written to fire (not go quiet) when their series vanish.

Phase 1 only. This component changes no live traffic: Cloudflare sends a hostname through the tunnel only when its DNS record is a CNAME to <UUID>.cfargotunnel.com, and nothing here creates one. Moving DNS (external-dns, Gateway annotations, Pi-hole/CoreDNS split horizon) is a later change.