Auto-provision vault secrets in bootstrap (fold provision-*-secrets)
closedGoal
Fold provision-*-secrets into just bootstrap so an operator never has to
remember a provisioning recipe or paste anything via ansible-vault edit:
derive the role from the domain, detect missing vault keys, ask ONE Y/n, then
generate/pull/merge/encrypt them. Auto-persist the Stalwart credential after a
green bootstrap. just update is fail-only (never provisions).
Implemented
deployment/scripts/_vault_lib.py: merge-aware primitives (ruamel round-trip),load_vault/save_vault, encrypt-then-shred, shared+host key registry, store access (secrets decrypt), INI/key-value parsers, role classification, required-key matrix, value validators, and a fix toencrypt_in_place(subprocess, not execvp).provision-shared-secrets: owns all shared keys; generates OIDC/OAuth, pulls relay (smtp2go_api_key+smtp2go_smtp_credentials.toml),resend_api_key, and the fleet-consistentsovrn_metrics_password, computes the bcrypt hash, merges (preserves foreign keys). Store is authoritative for the metrics password.provision-zds-secrets→provision-cell-secrets(renamed, old name removed): owns ZDS×5 + account-level R2×2 fromrclone.conf[r2].persist-bootstrap-secrets: merge-safe (preserves ZDS/R2), refuses a conflicting pinned credential.ensure-bootstrap-secrets(new): role→required-key matrix,--check(fail-only),--dry-run,--yes; pre-checks store sources; single prompt; never hangs on non-TTY.Justfile:bootstrapaccepts cell/relay/infra.sovrn.at (metrics = ensure secrets then converge in update mode), consumes--yes, auto-runspersist-bootstrap-secretsafter a green cell/relay play;updaterunsensure-bootstrap-secrets --checkfirst;provision-metrics-secretis now a thin wrapper.- New-in-bootstrap prerequisite guard for cells, role comments/fail_msgs,
docs (deployment.md, provision-cell.md, metrics-box.md), and the
resend_pds_api_key→resend_api_keyname fix. deployment/scripts/test-deploy(23 tests: vault merge/idempotency, store sourcing, ensure matrix, dry-run redaction, Justfile gates), wired intojust test.
Verification
just test-deploy: 23⁄23 pass.- Real repo
provision-shared-secrets --dry-run: succeeds, fully REDACTED. ansible-playbook playbooks/site.yml --syntax-check: ok.- py_compile + YAML sweep: clean.
SECURITY: leaked secrets during verification (READ FIRST)
A real provision-shared-secrets --dry-run leaked the EXISTING fleet
sovrn_oidc_key_pem and sovrn_oauth_attestation_key_b64 to the terminal:
the old redaction scrubbed by string-replacing only NEW values, and
multi-line/indented PEM body lines defeated replacement. The store-backed
values (relay/resend/metrics) were masked by the shell filter and did NOT leak.
Fixed by redacting by KEY (all secret keys rendered as REDACTED before dump;
no real value ever reaches stdout) in all three provisioners, with regression
tests. Action: rotate both keys with just provision-shared-secrets --force
and commit (OIDC rotation invalidates active sessions — pick a window). Treat
the previously printed key material as compromised.
Note (unrelated)
deployment/inventory/host_vars/mx99.eu.sovrn.at/vault.yml is currently a
working-copy deletion, unrelated to this work.
4 Comments
Correction: the metrics-box domain is infra.sovrn.at, not infra.rtw.run — an earlier mix-up. All Justfile/script/doc literals plus the issue body have been corrected; the ‘pending inventory rename’ follow-up is void because inventory/hosts.yml already lists infra.sovrn.at. [email protected] is an unrelated pre-existing alert recipient and is unchanged.
Superseded (2026-10-06): the vault scripts this built go away with Ansible (3beadb2, c3b0d04). Secrets now come from the
secretsstore as Colmena keys.The security note above is still open: nothing records that
sovrn_oidc_key_pemandsovrn_oauth_attestation_key_b64were rotated after leaking. Their store copies (sovrn/shared/oidc-key.pem,sovrn/shared/oauth-attestation.key) are what the NixOS cell uses. Rotation is task 1 of ae42c89 (no live hosts, so it costs nothing now). Close this once that’s done.Keys rotated (2026-10-06, ae42c89 task 1): - New
sovrn/shared/oidc-key.pemandsovrn/shared/oauth-attestation.key, generated withgo run ./cmd/gensecretsinto a private temp dir (removed), encrypted into the store. Hashes changed for both. - The leaked copies are kept, renamed out of the store’s view:~/projects/data/sovrn/shared/{oidc-key.pem,oauth-attestation.key}.age.leaked-2026-10-06. Delete them when you like; nothing reads them. - No host uses either key yet (no sovrn hosts are live), and both public halves are derived by sovrnd at runtime (client metadata inline JWKS), so nothing else needed updating. - The Ansible vault (deployment/inventory/group_vars/all/vault-shared.yml) still holds the old leaked values; it is deleted with Ansible (c3b0d04).Ready to close when you’re satisfied.
Closing (user, 2026-10-08): superseded. Ansible and its vaults are gone; secrets come from the age-encrypted secrets store via Colmena keys, generated per host by the fleet’s gen-host-secrets.