feat(secrets): cut vault over to SOPS+age (P2) #29

Merged
jfraeys merged 6 commits from jfraeys/chore/secrets-p2-cutover into main 2026-10-09 19:50:48 -04:00
Owner

P2: cut the infra vault over to SOPS+age.

  • secrets/vault.sops.yml (36 keys, operator + recovery recipients) via
    scripts/sops-cutover.sh; round-trip verified key-for-key. Fresh-clone decrypt
    with only the Bitwarden recovery key passed (2026-10-09).
  • First real rotation under SOPS (vaultwarden: admin token + shared Authelia
    OIDC client secret). Only those 3 keys changed; both recipients kept. NOT yet
    deployed: replay vaultwarden AND authelia together via
    scripts/with-vault.sh ansible-playbook -i inventory/hosts.yml playbooks/services.yml --tags vaultwarden,authelia --limit services_hosts
  • Post-cutover fixes: scripts/with-vault.sh (playbooks silently skip a missing
    secrets/vault.yml, so bare ansible-playbook ran with empty secrets);
    deploy-services.sh, rotate-secret.sh hints and playbooks/rotate-secret.yml
    use it.
  • setup: in CI (CI/GITHUB_ACTIONS/GITEA_ACTIONS set) with no age key, warn and
    continue without secrets instead of exit 2, since vault.sops.yml is now
    tracked. CI env var names are assumed; confirm on the first CI run.

Risk before merge: run staging-validate on this branch to confirm the CI fallback.
Still open for P2: staging deploy under SOPS, then retire Ansible Vault.

🤖 Generated with Claude Code

P2: cut the infra vault over to SOPS+age. - secrets/vault.sops.yml (36 keys, operator + recovery recipients) via scripts/sops-cutover.sh; round-trip verified key-for-key. Fresh-clone decrypt with only the Bitwarden recovery key passed (2026-10-09). - First real rotation under SOPS (vaultwarden: admin token + shared Authelia OIDC client secret). Only those 3 keys changed; both recipients kept. NOT yet deployed: replay vaultwarden AND authelia together via scripts/with-vault.sh ansible-playbook -i inventory/hosts.yml playbooks/services.yml --tags vaultwarden,authelia --limit services_hosts - Post-cutover fixes: scripts/with-vault.sh (playbooks silently skip a missing secrets/vault.yml, so bare ansible-playbook ran with empty secrets); deploy-services.sh, rotate-secret.sh hints and playbooks/rotate-secret.yml use it. - setup: in CI (CI/GITHUB_ACTIONS/GITEA_ACTIONS set) with no age key, warn and continue without secrets instead of exit 2, since vault.sops.yml is now tracked. CI env var names are assumed; confirm on the first CI run. Risk before merge: run staging-validate on this branch to confirm the CI fallback. Still open for P2: staging deploy under SOPS, then retire Ansible Vault. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add secrets/vault.sops.yml (36 keys, encrypted to the operator and recovery
recipients) and scripts/sops-cutover.sh that produced it. The Ansible Vault
original is kept locally as secrets/vault.yml.ansible-vault until staging and
a real rotation pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
First real rotation after the SOPS cutover: VAULTWARDEN_ADMIN_TOKEN and the
shared OIDC client secret pair (Authelia + Vaultwarden). Both recipients
verified in the re-encrypted file; only those three keys changed. Not yet
deployed: replay the authelia and vaultwarden roles together.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Playbooks include_vars secrets/vault.yml only when present, so a bare
ansible-playbook after the cutover ran with every secret empty. Add
scripts/with-vault.sh (decrypt for the command's duration, always clean up),
use it in deploy-services.sh and the rotate-secret.sh next-steps, and add
authelia to the vaultwarden redeploy tags (shared OIDC client secret).
./setup now continues without secrets in CI, which deliberately has no age
key, instead of exiting 2 now that vault.sops.yml is tracked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
fix(secrets): make rotate-secret.yml work under SOPS
All checks were successful
Lint / lint (pull_request) Successful in 2m3s
12e88709d6
Accept vault.sops.yml, drop the .vault_pass requirement when SOPS is live,
refresh the plaintext vault.yml after rotation so the replay play deploys the
new values (with-vault.sh's copy predates them), and refuse to run outside
with-vault.sh so the plaintext copy is always cleaned up.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jfraeys/infra!29
No description provided.