Files

41 lines
3.2 KiB
Markdown

# 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)](../../../home-network-k3s/decisions/adr-0007-secrets-sops-age.md),
[docs/07](../../../home-network-k3s/docs/07-security-and-secrets.md) · 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`](../../.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`](../../.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.