ADR 1032: Security by layer (the 4 C's) and the remaining controls¶
- Status: Accepted
- Date: 2026-11-02
- Applies to: both sites, the AWS accounts and the supporting SaaS accounts
- Phase: 1 (most controls). Later: Phase 2 adds prompt-injection guardrails; Phase 3 adds the AWS edge and VPC controls.
Context¶
Cloud-native security is commonly described in four layers, the 4 C's: Cloud (the provider and infrastructure), Cluster (Kubernetes), Container (images and runtime) and Code (the application and its dependencies); each layer depends on the one outside it. A review against that model, and against cloud provider, infrastructure, isolation, artifact, image, workload and application-code security, found the design strong in depth but with gaps:
- the project's other providers (GitHub, Cloudflare, Google Cloud, Slack, the domain registrars) weren't hardened like AWS
- the home network put the nodes on the same LAN as household devices; Secure Boot and BIOS passwords weren't decided
- user namespaces weren't used
- Helm charts weren't signed or pinned by digest; critical third-party images weren't mirrored;
packages: writewasn't limited to release jobs; Python and PHP dependencies weren't hash-pinned - the summarizer's base image wasn't specified
- Secrets as environment variables weren't prevented
- the sites had no HTTP security headers, no DAST, no WordPress-specific hardening (user enumeration, keys and salts), and no OWASP ASVS mapping
- the controls weren't summarized by layer
Decision¶
- Summarize every control by layer in the Phase 1 design ("Security by layer"), and organize the threat model and control matrix (Phase 1 step 8.5) by the 4 C's, with an OWASP ASVS mapping for the application.
- SaaS account hardening: passkeys or hardware security keys for GitHub, Cloudflare, Google, AWS (Identity Center), Slack and the registrars; registrar lock on both domains; GitHub organization settings (default workflow token read-only, only verified or allow-listed Actions, required reviews, environment protections), managed with Terraform's GitHub provider where possible; Cloudflare least-privilege API tokens and audit-log review; a minimal Google Cloud project; a quarterly access review of all of them.
- Home network and boot: a lab VLAN (or separate router subnet) with rules allowing the cluster out to the internet and in from the workstation only, and blocking it from household devices; Talos Secure Boot images with the TPM-sealed disk encryption (ADR 1031); BIOS passwords.
- User namespaces (
hostUsers: false) for application pods where the Kubernetes and Talos versions support them (root in a container then maps to an unprivileged user on the node). - Artifact integrity: sign Helm charts with cosign (verified in CI before the version-bump pull request) and pin charts by digest in
russetleaf-gitops; grantpackages: writeonly to release jobs; mirror critical third-party images into GHCR (copied by digest after verifying upstream signatures, then re-signed) for availability and rate limits; hash-pinned dependencies (uv's lock file with hashes;composer.lock). - Image: a distroless or slim multi-stage base for the summarizer.
- Workload: Secrets mounted as files, not environment variables (Kyverno rule), and least-privilege RBAC on Secrets.
- Application:
- HTTP security headers: HSTS at Cloudflare and CloudFront; Content-Security-Policy in report-only mode first, then enforced; X-Content-Type-Options, X-Frame-Options or
frame-ancestors, Referrer-Policy, Permissions-Policy. - DAST: OWASP ZAP baseline scan of staging in the promotion gate (and once against russetleaf.com during a demo).
- WordPress hardening: block user enumeration (author archives and the REST
usersendpoint for anonymous visitors); auth keys and salts from Secrets Manager, rotated (which signs everyone out); XML-RPC already off.
- HTTP security headers: HSTS at Cloudflare and CloudFront; Content-Security-Policy in report-only mode first, then enforced; X-Content-Type-Options, X-Frame-Options or
Alternatives considered¶
- Leaving non-AWS providers at defaults: they hold the code, DNS, sign-in and alerts; a compromise there bypasses the AWS controls.
- Flat home network: simpler, but a compromised node could reach household devices (and vice versa).
- Signing images only: leaves charts (which define what runs) unverified.
- A WAF-only approach to web security: WAFs don't set browser security policies (CSP, HSTS) or find application flaws the way DAST does.
- A commercial DAST or ASVS tool: ZAP's baseline scan is free and enough for this site.
Consequences¶
- Every control is placed in a layer, making gaps visible and interview answers structured.
- More settings to maintain (headers, CSP reports, mirror jobs, access reviews), mostly automated.
- CSP may break plugins at first; report-only mode finds problems before enforcement.
- Rotating keys and salts signs admins out (acceptable, and done on a schedule).
When to revisit¶
- User namespaces aren't supported yet: re-check after Talos and Kubernetes upgrades.
- The router can't do VLANs: use a separate router or firewall for the lab.
Open items¶
- Phase 1 steps 0.1, 0.3, 1.1, 2.2, 2.3, 5.1, 5.2, 5.6, 5.7, 6.1 and 8.5 and Phase 2 step 2.4; Phase 3 steps 1.2, 2.4 and 5.1.