Playbook: Master Keys Bootstrap & Recovery
Owner: p24-infra · Updated: 2026-06-27
Master keys are the credentials that enable ALL other rotation and deployment operations. If a master key is lost or compromised, multiple dependent rotations fail silently. This playbook documents what each master key controls, where it lives, and how to recover.
Master Key Inventory
| Master Key | Where stored | Controls | Rotation frequency | Recovery if lost |
|---|---|---|---|---|
| Developer age key | ~/.age/p24-infra-keys.txt + KeePass backup | ALL SOPS files (incl. administration.env.sops) | Never rotate — add new recipient instead | Restore from KeePass; if lost: re-key all 7 SOPS files with sops-set.ps1 -RekeyOnly (not sops updatekeys — #4601) |
AGE_KEY_GHA | GH Secret radieu/p24-infra | MON+N8N+VH1+BMS+ART+BP in CI (NOT administration) | Manually on suspected exposure | Generate new age key: age-keygen; re-key all 6 non-admin SOPS files with sops-set.ps1 -RekeyOnly; update GH Secret |
GH_PAT_ADMIN | secrets/administration.env.sops + GH Secret GH_PAT | gh secret set in credential-rotation.yml (write+secrets scope) | Human-action (browser, PAT page) | GitHub Settings → Developer Settings → Personal access tokens → create new with repo + secrets:write scope |
SUPABASE_ACCESS_TOKEN | secrets/administration.env.sops + GH Secret (+ role-scoped ROLE_SECRET_MANAGER_SUPABASE_ACCESS_TOKEN in secrets/role-secret-manager.env.sops for bms-4 workers) — not monitoring.env.sops (#2620, #4585) | Supabase Management API (schema, keys). ⚠️ Mint/write path broken — #4626 | Human-action (dashboard) | Supabase dashboard → Account → Access Tokens → create new |
SUPABASE_SERVICE_KEY | secrets/monitoring.env.sops + GH Secret | All Supabase DB queries by scripts/exporters | Human-action (sb_secret masked in API) | Supabase dashboard → Settings → API → create new secret key |
WASABI_ADMIN_ACCESS/SECRET_KEY | secrets/administration.env.sops + GH Secrets | Wasabi IAM (create/delete keys for rotation) | Human-action (Wasabi Console) | Wasabi Console → Access Keys → create new; update SOPS + GH Secrets |
CF_GLOBAL_API_KEY | secrets/administration.env.sops | Cloudflare scoped-token self-rotation | Human-action (CF dashboard) | CF dashboard → My Profile → API Tokens → API Keys |
OPENAI_ADMIN_KEY | GH Secret only | OpenAI monitoring token rotation | Human-action (OpenAI Console) | OpenAI Platform → Settings → API Keys → create new |
VPS_SSH_PRIVATE_KEY | GH Secret | secrets-sync.yml SSH deploy to vps-i1 | Rotate when key exposed | Generate new ed25519 key; add pubkey to claude-admin authorized_keys; update GH Secret |
dev_r_services entries for master keys
These master keys need rows in dev_r_services with rotation_freq and auto_rotate=false:
INSERT INTO dev_r_services (service_name, element_type, criticality, rotation_freq,
auto_rotate, sops_file, status, compliance_workbook, compliance_notes)
VALUES
('AGE_KEY_GHA','credential','critical','365d',false,'none — GH Secret only','active','yes',
'Master key for CI SOPS access; rotate only if exposed'),
('WASABI_ADMIN_ACCESS_KEY','credential','critical','365d',false,
'secrets/administration.env.sops','active','yes','IAM admin key for Wasabi key rotation'),
('CF_GLOBAL_API_KEY','credential','high','365d',false,
'secrets/administration.env.sops','active','yes','Global CF key for token self-rotation'),
('OPENAI_ADMIN_KEY','credential','medium','365d',false,'none — GH Secret only','active','yes',
'Admin key for OpenAI monitoring token rotation');Rotation: GH_PAT after sync-github-secrets.ps1 SYNC_MAP reduction
After Faza 4 (SYNC_MAP reduced to local-only keys), GH_PAT GH Secret is no longer auto-synced from .env.local. To rotate:
- Create new PAT at GitHub Settings → Developer Settings → Personal access tokens (Classic)
Scopes needed:
repo(full) +write:packages(optional) — NOTadmin:org - Decrypt administration.env.sops, update
GH_PAT_ADMINvalue, re-encrypt (canary) - Push SOPS commit to dev → merge
- Manually set GH Secret:
gh secret set GH_PAT --body "<new_token>" --repo radieu/p24-infra - Run credential-rotation.yml dry-run to verify new token works
AGE_KEY_GHA rotation procedure
- Generate new age key:
age-keygen -o /tmp/new-age-key.txt - Get new public key:
age-keygen -y /tmp/new-age-key.txt - For each of the 6 non-admin SOPS files (NOT administration.env.sops):
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt" # -RekeyOnly re-encrypts under whatever recipients .sops.yaml now lists, changing no values. # NOT `sops updatekeys` — broken on dotenv *.env.sops in SOPS 3.9.1, no flag fixes it (#4601). .\scripts\sops-set.ps1 -SopsFile secrets\monitoring.env.sops -RekeyOnly # -RekeyOnly already canary-decrypts and verifies the recipient count before it commits the # write (and rolls back on failure); this is an independent re-check: sops -d --input-type dotenv --output-type dotenv secrets\monitoring.env.sops | Out-Null - Update GH Secret:
gh secret set AGE_KEY_GHA --body (Get-Content /tmp/new-age-key.txt -Raw) --repo radieu/p24-infra - Delete old private key from /tmp; update KeePass with new key
Emergency: developer age key lost
If ~/.age/p24-infra-keys.txt is lost and not in KeePass:
- ALL SOPS files become unreadable locally (CI still works via AGE_KEY_GHA)
- Generate new developer age key:
age-keygen -o ~/.age/p24-infra-keys.txt - For each SOPS file, decrypt with CI key (AGE_KEY_GHA) → re-encrypt with new dev key:
# On vps-i1 (has AGE_KEY_GHA loaded in runner): sops -d secrets/monitoring.env.sops > /tmp/mon.plain # Then locally: sops --encrypt-in-place secrets/monitoring.env.sops - Update administration.env.sops: only the new dev key can re-encrypt it
- Commit all updated SOPS files to a branch; merge to dev