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]>
46 lines
2.5 KiB
YAML
46 lines
2.5 KiB
YAML
# =============================================================================
|
|
# L0 STATIC — the Cloudflare Tunnel exposes only what the operator decided, and cannot stall Flux
|
|
# Catalog: docs/14 L0 row ("Pre-apply, in CI") — runs WITHOUT a cluster.
|
|
#
|
|
# platform/cloudflared/config.yaml is the entire internet-facing surface of the tunnel. This test
|
|
# asserts the operator decisions of 2026-11-08 against it on every gate run: no rule may target
|
|
# anything but a 404 or the shared Gateway (so never the Kubernetes API); the LAN/VPN-only
|
|
# hostnames (plex, jackett, pihole, semaphore, qbittorrent, radarr, sonarr, headlamp,
|
|
# unifi, crafty-mc) 404 before the wildcard; the
|
|
# egress policy admits no API/node entity; and the Flux tier is isolated (wait:false, nothing
|
|
# dependsOn it, both overlays decrypt it). It proves itself non-vacuous by mutation. Rationale and
|
|
# the honest NOT-proven list: check.sh.
|
|
#
|
|
# WHY SCOPE, NOT REQUIREMENTS: the property spans platform/cloudflared, platform/gateway (the
|
|
# derived Service name), clusters/base and both overlays. A requirement-scoped selection would skip
|
|
# it on, e.g., a Gateway rename — precisely a change that breaks it.
|
|
# =============================================================================
|
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
|
kind: Test
|
|
metadata:
|
|
name: cloudflared-ingress-rules
|
|
labels:
|
|
conformance.home.k3s/R-NET-01: "true"
|
|
conformance.home.k3s/R-NET-02: "true"
|
|
annotations:
|
|
conformance.home.k3s/requirements: "R-NET-01, R-NET-02"
|
|
conformance.home.k3s/layer: "L0"
|
|
conformance.home.k3s/scope: "whole-repo"
|
|
conformance.home.k3s/safety-critical: "true"
|
|
conformance.home.k3s/executable: "true"
|
|
conformance.home.k3s/intent: >-
|
|
The Cloudflare Tunnel routes only to the shared Gateway or a 404, never to the Kubernetes
|
|
API; the LAN/VPN-only hostnames 404 ahead of the wildcard; the connector's egress admits no
|
|
API or node entity; and its Flux tier cannot stall any other tier.
|
|
spec:
|
|
steps:
|
|
- name: assert-tunnel-ingress-rules-and-isolation
|
|
description: >-
|
|
Parse platform/cloudflared/config.yaml with BASE_DOMAIN substituted, assert every rule's
|
|
target against the Gateway Service derived from platform/gateway/gateway.yaml, assert the
|
|
excluded hostnames 404 first, check the egress CiliumNetworkPolicy and the Flux wiring, and
|
|
run the evaluator on mutated configs to prove it is not vacuous.
|
|
try:
|
|
- script:
|
|
content: bash ./check.sh
|