The pod ran as 65532:65532 with no fsGroup, so the kubelet laid the Secret's credentials.json down root:root 0440 — unreadable to the container. cloudflared logged "permission denied" and then the red-herring "Error locating origin cert". - deployment.yaml: securityContext.fsGroup 65532 (mode stays 0440) - L0 cloudflared-ingress-rules (7): Secret volumes must be readable by the container's run-as uid/groups; mutation self-tested - runbook: permission-denied troubleshooting entry, credential rotation section - decisions/2026-11-08-cloudflared-creds-permission.md Co-Authored-By: Claude Opus 5.5 <[email protected]>
48 lines
2.6 KiB
YAML
48 lines
2.6 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; and assert the
|
|
credentials Secret volume is readable by the container's run-as uid/groups (fsGroup +
|
|
mode), self-tested by mutation.
|
|
try:
|
|
- script:
|
|
content: bash ./check.sh
|