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.ps1 bez -OnlyMissing)
  • Zaplanowana rotacja masowa wszystkich kluczy (nie mylić z credential-rotation.yml — tamto rotuje tylko klucze z auto_rotate=true)

Pliki SOPS objęte tym playbook

PlikŚrodowiskoDeployment
monitoring.env.sopsvps-i1 monitoring stacksecrets-sync.yml
n8n-bms4.env.sopsbms-4 n8n + workerssecrets-sync.yml
n8n-bms4-gh.env.sopsbms-4 GitHub PATssecrets-sync.yml
vps-h1.env.sopsvps-h1 servicessecrets-sync.yml
bms-servers.env.sopsBMS root passwordsmanual (on-demand)
mongodb-bms.env.sopsMongoDB rs0 credentialssecrets-sync.yml
art-agency.env.sopsArt-Agency appmanual sync
brandpilot.env.sopsBrandPilot Vercelsecrets-sync.yml
et-operational-platform.env.sopset-op Vercelsecrets-sync.yml
whatsup.env.sopsp24-wa-a appmanual sync
et-lager.env.sopset-lager Vercelsecrets-sync.yml
pinbox24-w3.env.sopsPinbox24 v32 bms-1/bms-3secrets-sync.yml
pinbox24-w4.env.sopsPinbox24 v42 bms-1/bms-4secrets-sync.yml
gcp.env.sopsGCP service accountmanual

WYKLUCZONE z tego playbook:

  • administration.env.sops — developer-only, osobna procedura
  • role-*.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 SOPS

1d. 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 nowego

1e. 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 token

1f. 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 update

1g. 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ła

2b. 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.sops

2c. 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.sops

2d. 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}/tokens

2e. 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 keys

Cleanup

# 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

OgraniczenieImpactWorkaround
BMS4_N8N_API_KEY nie w role-secret-managern8n vault update wymaga odczytu z n8n-bms4.env.sopsOdczytaj bezpośrednio z n8n-bms4.env.sops na początku sesji
GITHUB_APP_PRIVATE_KEY_B64 puste w administration.env.sopsGitHub App auth nie działa autonomicznieUżyj ROLE_SECRET_MANAGER_GITHUB_PAT zamiast App
N8N_ENCRYPTION_KEY — NIGDY nie rotowaćZmiana unieważnia wszystkie vault credentialsOsobna migration procedure (poza scope tego playbook)
AGE_KEY_GHA — root of trustWymaga re-encrypt wszystkich SOPS po rotacjimaster-keys-bootstrap.md — osobna DR procedure
Tier 3 klucze zablokowane~30 kluczy wymaga humanHuman-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 Windows
  • sops-windows-crlf.md — recovery po korupcji CRLF/BOM
  • credential-rotation-access-matrix.md — tier każdego klucza
  • credential-rotation-180d.md — per-key procedury rotacji
  • static-api-key-incident-rotation.md — exposure incident (1h SLA)
  • master-keys-bootstrap.md — rotacja age master key (root of trust)