Record the operator's 2026-11-08 decision to move public HTTP(S) ingress from edge port-forwarding of 80/443 to an outbound-only Cloudflare Tunnel (in-cluster cloudflared, proxied CNAMEs via ExternalDNS). Motivation: the GFiber router cannot hairpin NAT and advertises Google IPv6 DNS via RA, so LAN clients bypass Pi-hole split horizon and fail. Docs 00/01/03/07/08/09/13/14, GLOSSARY and definition-of-done brought into line. R-DNS-01..03 and R-TLS-01..03 wording unchanged. New requirements: R-DNS-04 (no 80/443 forward, resolver-independent LAN reachability), R-DNS-05 (only `public` services at the tunnel; 404 catch-all; no public record for `internal`), R-SEC-04 (Kubernetes API never publicly reachable). Co-Authored-By: Claude Opus 5.5 <[email protected]>
6.1 KiB
6.1 KiB
07 — Security & Secrets
Serves: R-SEC-01/02/03, supports all R-NET-* and R-TLS-*. Related:
ADR-0007 (SOPS+age).
Security posture
Defense in depth with declarative, auditable controls:
- Network: default-deny east-west, explicit port-level allow, egress-class routing, VPN kill- switch (03). This is the primary isolation control.
- Identity/least privilege: each workload runs with a minimal ServiceAccount and only the RBAC it needs; no workload gets cluster-admin. VMs and containers alike get least network reach (R-SEC-02).
- Secrets: encrypted at rest in git; decrypted only in-cluster (R-SEC-01).
- TLS: trusted, auto-managed certificates end to end (08, R-TLS-*).
- Supply chain: pin images by digest where automation allows; prefer known-good upstreams; updates flow through policy, not ad-hoc pulls (11).
- Identity / SSO: a central IdP (Keycloak) provides single sign-on, one user directory, group-based authorization, and MFA; services authenticate via native OIDC or an oauth2-proxy forward-auth layer so no management UI is unauthenticated (15, ADR-0014, R-IDP-*). This is the primary authentication control, complementing network default-deny (the primary isolation control).
- Policy enforcement (admission): Kyverno runs in-cluster as an admission controller and
rejects non-conforming resources at reconcile time — e.g. a namespace missing its default-deny
baseline, or a workload without a required egress-class/auth-mode label. The same policies
gate CI (14, ADR-0011),
so invariants are enforced both before merge and at admission (defense in depth). Enforcement
complements — never replaces — the runtime behavior tests.
- Plaintext-secret enforcement is split by layer (do not conflate): the "no plaintext Secret" invariant is a CI-time check (the committed Secret must carry SOPS ciphertext). It cannot be an admission rule, because Flux decrypts SOPS before the object reaches the API server — an admitted Secret never carries ciphertext, so an admission "reject plaintext" rule would reject every real Secret. Admission instead enforces SOPS provenance (the Secret was reconciled through the Flux/SOPS path), not ciphertext presence.
- Auditability: isolation, egress path, cert validity, and backup integrity are all demonstrable (R-SEC-03) via Hubble, cert status, and DR drills.
Secrets management (R-SEC-01)
SOPS + age, native to Flux:
- Secrets are committed to git encrypted with
agerecipients; Flux decrypts them at reconciliation using an in-cluster age key. Plaintext never enters git. - The age private key is bootstrapped into the cluster out-of-band (documented bootstrap step, one of the few sanctioned non-GitOps actions) and stored as a Kubernetes Secret Flux can read.
- Never print decrypted secret material into logs, PR text, or agent output (see AGENTS.md §5).
Secret categories to manage
- VPN credentials (PIA username/password +
.ovpn; OpenVPN-UDP) — held host-side on the egress-gateway node(s) via Ansible vault (the tunnel is host infra, not a cluster pod; ADR-0015), NOT a cluster SOPS Secret. - Keycloak admin credential and per-service OIDC client secrets (15).
- DNS provider API token (Cloudflare) for ACME DNS-01 + ExternalDNS.
- Cloudflare Tunnel credential/token for in-cluster cloudflared (SOPS-encrypted; ADR-0016).
- SMTP credentials for Alertmanager email.
- Service credentials (Nextcloud, Gitea admin, UniFi, Semaphore, Kasm, DB passwords).
- Off-cluster backup target credentials (S3/NFS) for Velero/Longhorn.
- Grafana admin, and any API tokens the C2 UI needs.
Rotation
- Rotating a secret = re-encrypting with SOPS and committing; Flux rolls it out. Where a secret change requires a workload restart, that is triggered by the reconcile (e.g. checksum annotation).
- VPN keys, DNS tokens, and SMTP creds SHOULD have a documented rotation procedure.
Alternatives considered (see ADR-0007)
- Sealed Secrets — viable; SOPS chosen for Flux-native decryption and multi-recipient/age simplicity and readable diffs.
- External Secrets Operator + external vault — heavier; deferred until a real external secret store exists. May be revisited via ADR.
RBAC & tenancy
- Single operator + platform automation; no multi-tenant RBAC now (out of scope, 00).
- Platform controllers (Flux, cert-manager, ExternalDNS, Velero, KubeVirt) get scoped permissions; application workloads get near-zero cluster permissions.
- The C2 UI's operational actions run through a constrained, audited path — not a shared admin kubeconfig handed to a browser.
Node & host security
- Nodes baselined via cloud-init/Ansible (02); SSH key-only, minimal packages, required sysctls only.
- Host-level changes are IaC, not hand edits (drift + auditability).
Auditability requirements (R-SEC-03) — what an operator must be able to prove
- Isolation: "service X cannot reach service Y" — via Hubble flow logs + a denied-probe test.
- Egress path: "service X's public IP is the VPN's" — via an egress IP check.
- Kill-switch: "with VPN down, X leaks nothing" — via a fault-injection test.
- TLS: "every endpoint serves a valid, non-self-signed, unexpired cert" — via cert status + external check.
- Backups: "irreplaceable data is restorable" — via a DR drill.
These proofs are part of the platform acceptance suite, not one-time checks.
Acceptance gates
- No plaintext secret exists anywhere in git history (scanned).
- Flux decrypts SOPS secrets and workloads consume them without manual steps.
- Least-privilege RBAC verified (no workload has broad cluster rights).
- The five auditability proofs above can be produced on demand.