bootstrap/sops (age-keygen refusing in-repo, .sops.yaml, install-age-key.sh glue installing the sops-age Secret) + bootstrap/flux (bootstrap.sh: flux install -> apply -k clusters/dev; decryption/url patches). PM wiring: place .sops.yaml at repo root (SOPS walks to root); inline the 3 flux patches into clusters/dev (avoids kustomize ../ load-restriction). clusters/dev builds with decryption enabled; full CI gate PASS. No key material; nothing executed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
38 lines
2.8 KiB
Markdown
38 lines
2.8 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).
|
|
|
|
> **AUTHOR-ONLY. Nothing here has been executed. No key or secret material exists in this output.**
|
|
> No `age-keygen`, `sops`, or `kubectl` was run. The age keypair is generated later by the operator;
|
|
> `.sops.yaml` carries only a **placeholder** for the public recipient.
|
|
|
|
## 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. |
|
|
| `.sops.yaml` | SOPS creation rules (encrypt only `data`/`stringData` → readable diffs). `age:` recipient is a clearly-marked `REPLACE-ME` placeholder filled at bootstrap. **Effective home is the repo root** (see below). |
|
|
| `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. |
|
|
|
|
## 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` belongs at the REPOSITORY ROOT.** SOPS discovers it by walking up from the file
|
|
being encrypted, so it only governs `platform/**` and `apps/**` from root. It is authored here (in
|
|
this worker's subtree); the **PM/clusters owner installs it at the repo root** (copy or symlink).
|
|
Flagged as an escalation in the T-1.4 report.
|
|
- **Fill two placeholders at bootstrap:** the `age:` recipient in `.sops.yaml` (from
|
|
`age-keygen.sh`'s output) — 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.
|