Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.snakysec.com/llms.txt

Use this file to discover all available pages before exploring further.

Runbook — Auto-rotation des secrets

Owner: Plateforme · Audience: MSSP_ADMIN avec accès Vault root Statut: V1 (avril 2026)

Résumé exécutif

Type de secretEngine VaultTTLRotationEffort opérateur
Clés Transit (Encryption)transit/Auto-rotate 90jAucun
Postgres app credsdatabase/ (postgresql-database-plugin)7j (max 30j)À chaque boot d’un conteneurRestart hebdo
Redis app credsdatabase/ (redis-database-plugin)7j (max 30j)À chaque boot d’un conteneurRestart hebdo
GitLab PATKV v2 + worker BullMQ90jWorker worker-gitlab-rotator mensuelAucun
AUTH_SECRET, certs EntraKV v2ManuelCf. key-rotation.md

1 — Clés Transit (Encryption)

Vault Transit garde la clé maître en interne ; l’app ne fait que des appels HTTP transit/encrypt|decrypt. Auto-rotate 90j configuré au boot de Vault via docker/vault/init.sh :
vault write transit/keys/mssp-platform/config auto_rotate_period=2160h
vault write transit/keys/mssp-clients/config  auto_rotate_period=2160h
Vault crée automatiquement une nouvelle version (vault:v2:, vault:v3: …). Tous les ciphertexts existants restent déchiffrables sans migration applicative ; seule la version active utilisée pour les nouvelles encryptions change. Vérifier :
docker exec mssp-vault vault read transit/keys/mssp-clients
# auto_rotate_period: 2160h
# latest_version: 1   ← s'incrémente après 90j
Pour forcer une rotation manuelle (urgence) :
docker exec mssp-vault vault write -f transit/keys/mssp-clients/rotate
Pour rewrap tous les ciphertexts vers la dernière version (optionnel, n’affecte pas le déchiffrement) :
node platform/scripts/rewrap-legacy-secrets.mjs --dry-run
node platform/scripts/rewrap-legacy-secrets.mjs

2 — Postgres dynamic credentials

Architecture

Boot                       Runtime (every lease/4 sec)         Max TTL (30j)
────                       ──────────────────────────         ──────────────
GET database/creds/    →   PUT sys/leases/renew          →   Vault refuses renewal
   mssp-app                 (lease prolongé +7j)              → Sentry alert
{ username: v-token-...                                       → restart container
  password: ...                                               → fresh creds
  lease_duration: 168h }

Pré-requis (one-time setup)

  1. Postgres : créer un user vault avec CREATEROLE + grant du rôle mssp_app_role.
  2. Vault : vault_pg_password dans mssp/data/platform, engine database/ configuré.
Bootstrap (run après make preprod initial OU après reset de la KV Vault) :
# 1. Génère un password dédié vault → Postgres
VAULT_PG_PASS=$(openssl rand -hex 32)

# 2. Stocke dans Vault
docker exec mssp-vault vault kv patch mssp/platform vault_pg_password="$VAULT_PG_PASS"

# 3. Crée user Postgres (idempotent)
docker exec mssp-postgres psql -U mssp -d mssp_platform <<SQL
DO \$\$
BEGIN
  IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'vault') THEN
    CREATE ROLE vault WITH LOGIN CREATEROLE PASSWORD '$VAULT_PG_PASS';
  ELSE
    ALTER ROLE vault WITH PASSWORD '$VAULT_PG_PASS';
  END IF;
  IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'mssp_app_role') THEN
    CREATE ROLE mssp_app_role INHERIT;
  END IF;
END
\$\$;

GRANT CONNECT ON DATABASE mssp_platform TO mssp_app_role;
GRANT USAGE ON SCHEMA public TO mssp_app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO mssp_app_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO mssp_app_role;
ALTER DEFAULT PRIVILEGES FOR ROLE mssp IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO mssp_app_role;
ALTER DEFAULT PRIVILEGES FOR ROLE mssp IN SCHEMA public
  GRANT USAGE, SELECT ON SEQUENCES TO mssp_app_role;
GRANT mssp_app_role TO vault WITH ADMIN TRUE, INHERIT TRUE, SET TRUE;
SQL

# 4. Re-run init.sh pour configurer le database engine
docker exec mssp-vault sh /vault/init.sh

Activer le mode dynamique côté app

# .env (dev) ou docker-compose env (prod)
VAULT_DYNAMIC_DB=true
Recreate les conteneurs (make preprod / make app-rebuild). Au boot :
[INFO] Vault dynamic Postgres creds issued { leaseId, role, leaseDurationSec: 604800 }
[INFO] Vault secrets injected { injected: 2, dynamicDb: true, dynamicRedis: false }

Vérifier la rotation

# Lister les leases actifs
docker exec mssp-vault vault list sys/leases/lookup/database/creds/mssp-app

# Inspecter un lease
docker exec mssp-vault vault read sys/leases/lookup lease_id=<id>
Toutes les 168/4h ≈ 42h, l’app appelle sys/leases/renew automatiquement (platform/src/lib/vault-lease-renewer.ts). Logs :
[INFO] Vault lease renewed { leaseId, label: "database/mssp-app", newTtlSec: 604800 }

Restart hebdo

Vault refuse de renouveler après max_ttl=720h (30j). À ce moment, l’app doit avoir été redémarrée pour fetch des creds frais. Cadence recommandée : deploy/restart hebdomadaire, soit via :
  • pipeline GitLab CI nightly/weekly redeploy
  • cron host : 0 4 * * 0 docker compose restart (dimanche 04:00)
Si le restart est manqué, Sentry alerte via la signature vault-lease-renewer quand TTL < 24h.

3 — Redis dynamic credentials

Identique à Postgres mais via le plugin redis-database-plugin du même engine database/. Les rôles sont nommés avec suffixe -redis pour ne pas collisionner :
  • database/roles/mssp-app-redis
  • database/roles/mssp-worker-redis

Activer

# .env
VAULT_DYNAMIC_REDIS=true
Le password Redis admin (mssp/platform.redis_password) reste utilisé par Vault pour créer/révoquer les users dynamiques. Il n’est plus utilisé par l’app au runtime quand VAULT_DYNAMIC_REDIS=true.

ACL Redis

V1 = [+@all, ~*] (toutes commandes, toutes clés). À tightener Q3 vers [+@read, +@write, +@stream, ~*] (BullMQ utilise les streams).

4 — GitLab PAT — worker BullMQ mensuel

GitLab n’a pas d’engine Vault dynamique. Le worker gitlab-token-rotator-worker.ts rotates le PAT le 15 de chaque mois à 03:00 UTC :
// Pseudo-code
const self = await GET /personal_access_tokens/self
const rotated = await POST /personal_access_tokens/{self.id}/rotate
                       { expires_at: now + 90j }
await putSecret("mssp/platform", { ...secrets, gitlab_token: rotated.token })
POST /rotate est atomique : nouveau token créé + ancien révoqué dans la même requête.

Audit log

Chaque rotation émet un événement secret.rotate dans platform_audit_log (chaîne intègre Ed25519). Filtrer par :
SELECT created_at, after FROM platform_audit_log
WHERE action = 'secret.rotate'
  AND resource_label = 'mssp/platform.gitlab_token'
ORDER BY created_at DESC
LIMIT 10;

Alertes Sentry

SignalNiveau
Token expire dans <14j (cycle de rotation manqué)warning
POST /rotate échoueerror + captureException
Token introuvable via /self (déjà révoqué ?)error

Forcer une rotation manuelle

Cas : fuite de token, lockdown urgence.
docker exec mssp-app node /app/dist/workers/gitlab-token-rotator-worker.js
# Ou via BullMQ board : POST job "rotate" sur queue "gitlab-token-rotator"
Note : le worker logue dans platform_audit_log même en run manuel.

5 — Garde-fous communs

Test post-rotation

Après chaque rotation (auto ou manuelle), vérifier :
# 1. Health endpoint
curl -fs http://localhost:3000/api/health
# attendu: {"db":"ok","redis":"ok"}

# 2. Lease actif et frais
docker exec mssp-vault vault list sys/leases/lookup/database/creds/mssp-app

# 3. Pas d'erreurs Sentry sur composant vault-*

Rollback Vault Transit

Vault Transit conserve toutes les versions (min_decryption_version=1). Pas de risque de perte de données — un rewrap raté laisse les anciens ciphertexts utilisables.

Rollback dynamic DB / Redis

Désactiver le flag :
# .env
VAULT_DYNAMIC_DB=false
VAULT_DYNAMIC_REDIS=false
Recreate les conteneurs → fallback automatique sur les passwords statiques de mssp/data/platform.

Rollback GitLab PAT

# Lire la version précédente dans Vault KV v2 (10 versions retenues par défaut)
docker exec mssp-vault vault kv get -version=N mssp/platform | grep gitlab_token

# Réécrire en version courante
docker exec mssp-vault vault kv patch mssp/platform gitlab_token="<old_token>"

6 — Calendrier de bascule

DateActionOwner
2026-04-30Phase 0/1/2/3 mergée sur main, code activable via flagsDev
2026-05-15Phase 1 (Postgres) bascule sur preprod, monitoring 2 semainesOps
2026-06-01Phase 1 + 2 + 3 bascule en prod, alertes Sentry activesOps
2026-09-01Premier auto-rotate Transit observé (90j depuis init)
2026-Q3Tightening ACL Redis (@read+@write+@stream)Dev