Playbook: Autonomiczny reset wszystkich plików SOPS
Autor: secret-manager agent
Wersja: 1.0 — 2026-07-07
Zastosowanie: Pełna rekonstrukcja wszystkich plików SOPS poza role-*.env.sops
Wymaga: role-secret-manager.env.sops z kluczami admin (CF, Wasabi, IONOS, MongoDB, SSH) — wersja 2026-07-07+
Kiedy używać
- Wszystkie pliki SOPS zostały utracone lub uszkodzone (disaster recovery)
- Migracja na nowe klucze age (po
sops updatekeys) - Nowy deployment środowiska od zera (
new-sops-env.ps1bez-OnlyMissing) - Zaplanowana rotacja masowa wszystkich kluczy (nie mylić z
credential-rotation.yml— tamto rotuje tylko klucze zauto_rotate=true)
Pliki SOPS objęte tym playbook
| Plik | Środowisko | Deployment |
|---|---|---|
monitoring.env.sops | vps-i1 monitoring stack | secrets-sync.yml |
n8n-bms4.env.sops | bms-4 n8n + workers | secrets-sync.yml |
n8n-bms4-gh.env.sops | bms-4 GitHub PATs | secrets-sync.yml |
vps-h1.env.sops | vps-h1 services | secrets-sync.yml |
bms-servers.env.sops | BMS root passwords | manual (on-demand) |
mongodb-bms.env.sops | MongoDB rs0 credentials | secrets-sync.yml |
art-agency.env.sops | Art-Agency app | manual sync |
brandpilot.env.sops | BrandPilot Vercel | secrets-sync.yml |
et-operational-platform.env.sops | et-op Vercel | secrets-sync.yml |
whatsup.env.sops | p24-wa-a app | manual sync |
et-lager.env.sops | et-lager Vercel | secrets-sync.yml |
pinbox24-w3.env.sops | Pinbox24 v32 bms-1/bms-3 | secrets-sync.yml |
pinbox24-w4.env.sops | Pinbox24 v42 bms-1/bms-4 | secrets-sync.yml |
gcp.env.sops | GCP service account | manual |
WYKLUCZONE z tego playbook:
administration.env.sops— developer-only, osobna procedurarole-*.env.sops— role credentials, osobna procedura.canary.env.sops— test keys, auto-generowane
Warunki wstępne
# 1. Weryfikacja kontekstu
git remote -v # musi zawierać radieu/p24-infra
# 2. age key
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
# 3. Canary — role-secret-manager musi być sprawny (SOPS klucze admin)
sops --decrypt --input-type dotenv --output-type dotenv secrets\role-secret-manager.env.sops | Out-Null
if ($LASTEXITCODE -ne 0) { throw "role-secret-manager.env.sops nie działa — sprawdź age key" }
# 4. Acquire env lock (opcjonalne — scripts/env-lock.ps1 not yet implemented)
# TODO: implement scripts/env-lock.ps1 for concurrent-session protection
# Acquire-EnvLock -Resource "sops:all" -Operation "sops-autonomous-reset"Faza 0 — Wczytanie kluczy admin z role-secret-manager.env.sops
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
function Get-Kv([string[]]$lines, [string]$key) {
$m = $lines | Where-Object { $_ -match "^${key}=" } | Select-Object -First 1
if ($m) { $v = $m.Split("=",2)[1]; if ($v) { return $v } }
return $null
}
$smPlain = sops --decrypt --input-type dotenv --output-type dotenv secrets\role-secret-manager.env.sops 2>$null
# Admin keys — załadowane raz, używane przez wszystkie fazy
$env:SM_CF_GLOBAL = Get-Kv $smPlain "CF_GLOBAL_API_KEY"
$env:SM_WASABI_AK = Get-Kv $smPlain "WASABI_ADMIN_ACCESS_KEY"
$env:SM_WASABI_SK = Get-Kv $smPlain "WASABI_ADMIN_SECRET_KEY"
$env:SM_IONOS_PFX = Get-Kv $smPlain "IONOS_API_TOKEN_PREFIX"
$env:SM_IONOS_ENC = Get-Kv $smPlain "IONOS_API_TOKEN_ENCRYPTION"
$env:SM_ROTATOR = Get-Kv $smPlain "ROTATOR_API_KEY"
$env:SM_MONGO_PASS = Get-Kv $smPlain "MONGODB_RS0_ADMIN_PASSWORD"
$env:SM_MONGO_USER = Get-Kv $smPlain "MONGODB_RS0_ADMIN_USER"
$env:SM_VPS_SSH = Get-Kv $smPlain "VPS_SSH_PRIVATE_KEY" # used by scripts/sops-reset-mongodb.ps1 + scripts/sops-reset-redis.ps1 (SSH to bms-4, bms-2) — split from sops-reset-pinbox24.ps1 Phase 3+4 in issue #3256; sops-reset-pinbox24.ps1 is now a thin orchestrator over the per-service scripts in scripts/, each runnable standalone with its own -DryRun
$env:SM_SUPABASE_TOK = Get-Kv $smPlain "ROLE_SECRET_MANAGER_SUPABASE_ACCESS_TOKEN"
$env:SM_GH_PAT = Get-Kv $smPlain "ROLE_SECRET_MANAGER_GITHUB_PAT"
$env:SM_VERCEL_TOK = Get-Kv $smPlain "ROLE_SECRET_MANAGER_VERCEL_TOKEN"
$env:SM_MAILGUN_ADMIN = Get-Kv $smPlain "MAILGUN_ADMIN_API_KEY"
$env:SM_OPENAI_ADMIN = Get-Kv $smPlain "OPENAI_ADMIN_KEY"
$smPlain = @()
Write-Host "Admin keys loaded from role-secret-manager.env.sops"Faza 1 — Tier 1: Klucze autonomiczne (generowane przez API)
Wszystkie operacje mogą być wykonane przez workera bms-4 bez udziału człowieka.
1a. Cloudflare API tokens
Wymaga: $env:SM_CF_GLOBAL (CF Global API Key)
# Dokumentacja: docs/playbooks/cloudflare-credential-rotation.md
# Tokeny do rotacji (PUT /client/v4/user/tokens/{id}/value):
# - CLOUDFLARE_TOKEN_ZINTEGROWANA (monitoring.env.sops)
# - CF_EDIT_ALL_ZONES_API_TOKEN (monitoring.env.sops)
# - CF_WORKERS_API_TOKEN (monitoring.env.sops)
# - CF_RADEKKONARSKI_DNS_TOKEN (monitoring.env.sops)
# CF_GLOBAL_API_KEY lives in administration.env.sops (NOT monitoring.env.sops)
# Auth email is the actual CF account owner: radieu@gmail.com
# (ecotrans.automation@gmail.com is the separate Gmail/n8n automation account and does NOT
# authenticate against Cloudflare — a prior version of this snippet had this wrong, causing
# a false "Unknown X-Auth-Key or X-Auth-Email" (9103) failure; confirmed 2026-08-01)
# Pobierz listę tokenów
$headers = @{ "X-Auth-Key" = $env:SM_CF_GLOBAL; "X-Auth-Email" = "radieu@gmail.com" }
$tokens = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/user/tokens" -Headers $headers
# Następnie: PUT /user/tokens/{id}/value dla każdego tokenu
# Zapisz nowe wartości do env vars, potem do SOPS (sekcja "Zapis do SOPS" poniżej)1b. Wasabi IAM keys
Wymaga: $env:SM_WASABI_AK + $env:SM_WASABI_SK
# Dokumentacja: docs/playbooks/wasabi-key-rotation.md
import boto3
session = boto3.Session(
aws_access_key_id=os.environ['SM_WASABI_AK'],
aws_secret_access_key=os.environ['SM_WASABI_SK']
)
iam = session.client('iam', endpoint_url='https://iam.wasabisys.com')
# Użytkownicy do rotacji: p24-infra, art-agency, p24-brand, pinbox24
for user in ['p24-infra', 'art-agency', ...]:
new_key = iam.create_access_key(UserName=user)['AccessKey']
# Zapisz do SOPS (sekcja poniżej), potem:
iam.delete_access_key(UserName=user, AccessKeyId=old_key_id)1c. n8n API key
Wymaga: BMS4_N8N_API_KEY z n8n-bms4.env.sops (nie w role-secret-manager — TODO)
# Dokumentacja: docs/playbooks/n8n/n8n-bms4-api-key-rotation.md
# Dopóki BMS4_N8N_API_KEY nie jest w role-secret-manager.env.sops:
# Odczytaj z n8n-bms4.env.sops bezpośrednio (agent na bms-4 ma dostęp)
$n8nPlain = sops --decrypt --input-type dotenv --output-type dotenv secrets\n8n-bms4.env.sops 2>$null
$env:SM_N8N_API_KEY = ($n8nPlain | Where-Object { $_ -match "^BMS4_N8N_API_KEY=" }).Split("=",2)[1]
$n8nPlain = @()
# POST /api/v1/credentials na bms-4 n8n → zapis do SOPS1d. Resend API keys
Wymaga: istniejący ważny klucz Resend z brandpilot.env.sops lub monitoring.env.sops
# Dokumentacja: docs/playbooks/resend-api-key-rotation.md
# POST /api-keys z Authorization: Bearer <existing_resend_key>
# DELETE /api-keys/{old_id} po potwierdzeniu nowego1e. Mezmo ingestion keys
Wymaga: P24_INFRA_MEZMO_SERVICE_KEY z monitoring.env.sops
# Dokumentacja: docs/playbooks/mezmo-key-rotation.md
# POST /v1/config/keys?type=ingestion — jeden klucz per host
# Użyj P24_INFRA_MEZMO_SERVICE_KEY jako Bearer token1f. Internal HMAC / random keys
Generowane lokalnie — brak zewnętrznego API:
# Klucze do wygenerowania (random hex 32 bytes):
# GITHUB_TRIGGER_SECRET, N8N_HU_SP_REPORT_SECRET, BRAND_WEBHOOK_SECRET,
# CRON_SECRET, EXTERNAL_DB_SYM_KEY, LOKI_BASIC_AUTH_PASSWORD,
# PDF_SERVICE_API_KEY, GF_RENDERING_RENDERER_TOKEN,
# CLAUDE_SESSION_MANAGER_KEY, INSPECTION_WEBHOOK_SECRET
function New-RandomKey {
$bytes = [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)
return ($bytes | ForEach-Object { '{0:x2}' -f $_ }) -join ''
}
$env:NEW_GITHUB_TRIGGER_SECRET = New-RandomKey
# ... etc. — nigdy nie printować, tylko użyć w SOPS update1g. MongoDB prometheus user
Wymaga: $env:SM_MONGO_PASS + SSH do PRIMARY rs0
# Dokumentacja: docs/playbooks/mongodb-credential-rotation.md
$newMongoPrometheusPass = New-RandomKey
# SSH do bms-4 (PRIMARY lub bms-2):
$sshCmd = "mongosh 'mongodb://admin:$env:SM_MONGO_PASS@localhost:27017/admin' --eval `"db.changeUserPassword('prometheus', '$newMongoPrometheusPass')`""
# ... zapisz $newMongoPrometheusPass do SOPS (n8n-bms4.env.sops + monitoring.env.sops)Faza 2 — Tier 2: Semi-autonomiczne (prereq wymagany)
2a. Playwright: Google session
# Przed Tier 2 — weryfikacja sesji Google w Playwright
# Otwórz Playwright MCP: playwright_navigate do google.com
# Jeśli session expired: Google login (email + password + 2FA push)
# ~5-15 minut jeśli sesja wygasła2b. OpenAI Admin Key
# Playwright: platform.openai.com/api-keys
# Delete old key → Create new → Capture (nigdy nie logować do chata)
# Aktualizuj monitoring.env.sops + role-secret-manager.env.sops2c. Sentry Auth Token
# scripts/rotate/sentry-auth-token.js (Playwright)
$env:TOKEN_OUT_FILE = "C:\tmp\sentry-token-tmp.txt"
node scripts/rotate/sentry-auth-token.js
$newSentryToken = (Get-Content $env:TOKEN_OUT_FILE -Raw).Trim()
Remove-Item $env:TOKEN_OUT_FILE -Force
# Zapisz do role-secret-manager.env.sops2d. IONOS API tokens
# Dokumentacja: docs/playbooks/ionos-api-token-rotation.md
# Wymaga: $env:SM_IONOS_PFX + $env:SM_IONOS_ENC (z role-secret-manager)
# POST /v1/um/users/{id}/tokens2e. LinkedIn token refresh
# Semi-auto: OAuth refresh_token flow (jeśli refresh_token jeszcze ważny)
# Endpoint: POST https://www.linkedin.com/oauth/v2/accessToken
# Body: grant_type=refresh_token&refresh_token={...}&client_id={...}&client_secret={...}Faza 3 — Tier 3: Human-action (tworzenie GH issues)
Dla każdego Tier 3 klucza: utwórz GH issue i CZEKAJ na callback.
# NIGDY nie tworzyć issue jako 'human-action' dla Tier 1 lub Tier 2 kluczy!
$tier3Keys = @(
@{ key="GH_TOKEN"; files=@("monitoring","n8n-bms4","vps-h1"); url="github.com/settings/tokens" }
@{ key="SUPABASE_SERVICE_ROLE_KEY"; files=@("monitoring","n8n-bms4","brandpilot","art-agency","vps-h1"); url="app.supabase.com/project/mwkqmgadqnkkihjdeqsi/settings/api" }
@{ key="CF_GLOBAL_API_KEY"; files=@("monitoring","role-secret-manager"); url="dash.cloudflare.com > My Profile > API Tokens" }
@{ key="ATRAX_AUTH_STRING"; files=@("monitoring","n8n-bms4","vps-h1"); url="Atrax admin portal" }
@{ key="MAILGUN_ADMIN_API_KEY"; files=@("monitoring","role-secret-manager"); url="app.mailgun.com" }
@{ key="DISCORD_BOT_TOKEN"; files=@("monitoring"); url="discord.com/developers/applications" }
@{ key="SMTP_PASSWORD"; files=@("monitoring"); url="app.mailgun.com (SMTP credentials)" }
# administration + role-secret-manager, NOT monitoring — moved out 2026-07-05 (#2075/#2620, #4585)
@{ key="SUPABASE_ACCESS_TOKEN"; files=@("administration","role-secret-manager"); url="app.supabase.com/account/tokens" }
)
foreach ($t in $tier3Keys) {
gh issue create --repo radieu/p24-infra `
--title "sec: rotate $($t.key) — scheduled 2026-07-07" `
--label "human-action" `
--body "## Credential Rotation Required`n`nCredential: ``$($t.key)```nSOPS files: $($t.files -join ', ')`nProvider: $($t.url)`n`nHuman must:`n- [ ] Log into provider dashboard`n- [ ] Generate new credential`n- [ ] Set: ``\$env:NEW_VALUE = 'paste-here'`` in terminal (NEVER in chat)`n- [ ] Notify secret-manager session to update SOPS"
}Zapis do SOPS — wzorzec dla każdego klucza
# Uniwersalny wzorzec: decrypt → patch → re-encrypt → canary → atomic swap
function Update-SopsKey {
param([string]$SopsFile, [string]$KeyName, [string]$NewValue)
$TEMP_PLAIN = $SopsFile -replace "\.env\.sops$", "-edit-tmp.env.sops"
$TEMP_ENC = $SopsFile -replace "\.env\.sops$", "-enc-tmp.env.sops"
$plain = sops --decrypt --input-type dotenv --output-type dotenv $SopsFile 2>$null
if ($LASTEXITCODE -ne 0) { throw "Decrypt failed: $SopsFile" }
$content = ($plain -join "`n") + "`n"
$plain = @()
if ($content -match "(?m)^${KeyName}=") {
$content = $content -replace "(?m)^${KeyName}=.*$", "${KeyName}=${NewValue}"
} else {
$content += "${KeyName}=${NewValue}`n"
}
$NewValue = "" # wipe from memory
[System.IO.File]::WriteAllText($TEMP_PLAIN, $content, [System.Text.UTF8Encoding]::new($false))
$content = ""
sops --encrypt --input-type dotenv --output-type dotenv --output $TEMP_ENC $TEMP_PLAIN 2>$null
if ($LASTEXITCODE -ne 0) {
@($TEMP_PLAIN,$TEMP_ENC) | Where-Object { Test-Path $_ } | Remove-Item -Force
throw "Encrypt failed"
}
Remove-Item $TEMP_PLAIN -Force
sops --decrypt --input-type dotenv --output-type dotenv $TEMP_ENC 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) {
Remove-Item $TEMP_ENC -Force -ErrorAction SilentlyContinue
throw "Canary failed on temp"
}
Move-Item $TEMP_ENC $SopsFile -Force
Write-Host "Updated $KeyName in $SopsFile"
}
# Przykład użycia:
# $env:NEW_GITHUB_TRIGGER_SECRET = New-RandomKey
# Update-SopsKey "secrets\n8n-bms4.env.sops" "GITHUB_TRIGGER_SECRET" $env:NEW_GITHUB_TRIGGER_SECRET
# Update-SopsKey "secrets\vps-h1.env.sops" "GITHUB_TRIGGER_SECRET" $env:NEW_GITHUB_TRIGGER_SECRET
# $env:NEW_GITHUB_TRIGGER_SECRET = ""Dystrybucja po resecie
# 1. Git commit wszystkich zmienionych SOPS plików
git add secrets\*.env.sops docs\sops-templates\
git commit -m "chore(secrets): autonomous SOPS reset — all Tier 1/2 keys rotated $(Get-Date -Format 'yyyy-MM-dd')"
# 2. PR do main
gh pr create --title "chore: autonomous SOPS reset $(Get-Date -Format 'yyyy-MM-dd')" \
--body "Automated SOPS reset — all Tier 1/2 keys rotated by secret-manager worker. Tier 3 keys pending human-action issues." \
--repo radieu/p24-infra
# 3. secrets-sync.yml fires automatycznie po merge do main
# Deployment: vps-i1 /opt/p24-infra/monitoring/.env + bms-4 /opt/p24-infra/bms-4/.env
# 4. Weryfikacja po deployment
# Prometheus targets UP: https://grafana.vps-i1.infra.zintegrowana.online
# n8n healthy: bms-4 n8n dashboard
# secrets-rotation-log.md: append entries for all rotated keysCleanup
# Wyczyść wszystkie SM_* env vars po zakończeniu sesji
Get-ChildItem env: | Where-Object { $_.Name -match "^SM_" } | ForEach-Object {
[System.Environment]::SetEnvironmentVariable($_.Name, "", "Process")
}
# Release lock (opcjonalne — patrz TODO przy Acquire-EnvLock powyżej)
# Release-EnvLock -Resource "sops:all"Znane ograniczenia
| Ograniczenie | Impact | Workaround |
|---|---|---|
BMS4_N8N_API_KEY nie w role-secret-manager | n8n vault update wymaga odczytu z n8n-bms4.env.sops | Odczytaj bezpośrednio z n8n-bms4.env.sops na początku sesji |
GITHUB_APP_PRIVATE_KEY_B64 puste w administration.env.sops | GitHub App auth nie działa autonomicznie | Użyj ROLE_SECRET_MANAGER_GITHUB_PAT zamiast App |
N8N_ENCRYPTION_KEY — NIGDY nie rotować | Zmiana unieważnia wszystkie vault credentials | Osobna migration procedure (poza scope tego playbook) |
AGE_KEY_GHA — root of trust | Wymaga re-encrypt wszystkich SOPS po rotacji | master-keys-bootstrap.md — osobna DR procedure |
| Tier 3 klucze zablokowane | ~30 kluczy wymaga human | Human-action GH issues — workflow kontynuuje bez nich |
Powiązane playbooki
secret-manager.md— master playbook (operational manual)sops-edit-operations.md— bezpieczne operacje SOPS na Windowssops-windows-crlf.md— recovery po korupcji CRLF/BOMcredential-rotation-access-matrix.md— tier każdego kluczacredential-rotation-180d.md— per-key procedury rotacjistatic-api-key-incident-rotation.md— exposure incident (1h SLA)master-keys-bootstrap.md— rotacja age master key (root of trust)