Files
home-network-k3s-impl/bootstrap/sops
jakman85andClaude Opus 4.8 37f4fa6767 feat(updates): install Flux image controllers + cluster-wide write-back engine (R-UPD-02)
Install image-reflector-controller + image-automation-controller as part of the
flux-system install (bootstrap.sh --components-extra) so the image.toolkit CRDs
pre-exist before reconcile — no F-MON1 split needed for the ImageUpdateAutomation CR.
Refine the cluster-wide ImageUpdateAutomation for machine-authored git write-back
(setter strategy, path ./apps) reusing the flux-system GitRepository secretRef, and
document the operator git-write-credential hand-off + SOPS secret template
(gitea-image-automation-credentials). image-automation stays deferred in
platform/kustomization.yaml until that credential is provisioned.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:14:39 -05:00
..

bootstrap/sops — SOPS + age key material & decryption wiring (Phase 1, T-1.4)

Generates the age keypair, declares SOPS creation rules, and documents how the age private key is installed into the cluster so Flux can decrypt SOPS at reconcile. Spec: ADR-0007 (SOPS+age), docs/07 · Requirement: R-SEC-01 (secrets encrypted at rest in git, decrypted only in-cluster).

The SOPS creation rules live in the single canonical repo-root .sops.yaml. The operator generates the age keypair via age-keygen.sh, pastes the public recipient into that root file, and installs the private key in-cluster as the sops-age Secret. No private key material ever enters git. (CREDENTIALS.md is the end-to-end provisioning checklist.)

Files

File Purpose
age-keygen.sh Operator-run script: generates the age keypair to a path outside the repo, prints only the public recipient. Refuses to write inside the repo or clobber an existing key.
age-key-secret.md Runbook + kubectl create secret template installing the age private key as the sops-age Secret in flux-system (the sanctioned out-of-band step). Rotation + DR custody included.
CREDENTIALS.md End-to-end operator checklist: SOPS setup + the full secret inventory (per component/phase).
secret-templates/ *.yaml.example scaffolds (correct name/namespace/keys, placeholder values) to copy → fill → sops -e.
repo-root .sops.yaml The single canonical SOPS creation rules (encrypt only data/stringData → readable diffs). At the repo root, NOT here — a copy in this dir would shadow it for files under bootstrap/**.

How this satisfies R-SEC-01 / ADR-0007

  • Secrets are committed encrypted (sops --encrypt) using the age public recipient in .sops.yaml; plaintext never enters git (.gitignore guards age.key, *.key, *.dec.yaml).
  • Flux decrypts in-cluster using the age private key held in the sops-age Secret — the one documented out-of-band bootstrap action (docs/07, ADR-0007 Consequences).
  • decryption.provider: sops + secretRef: sops-age on the platform/apps Flux Kustomizations is wired by ../flux/patches/ (realising the commented TODOs in clusters/base).
  • No key material appears in scripts, docs, PRs, or logs (AGENTS.md §5).

Interface / handoff

  • .sops.yaml is a SINGLE file at the REPOSITORY ROOT. SOPS discovers it by walking up from the file being encrypted, so a root file governs platform/**, apps/**, catalog/**, and secrets/**. Do not keep a second copy in this directory — it would be discovered first for files under bootstrap/** and shadow the root config (that duplicate was removed after it caused exactly that).
  • Fill the recipient at bootstrap: paste age-keygen.sh's public recipient into the root .sops.yaml — the same value in both creation rules.
  • Order (see ../flux/README.md): age-keygen.sh → install sops-age Secret (age-key-secret.md) → ../flux/bootstrap.sh. The Flux entrypoint fails fast if the Secret is missing.