> ## 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.

# Vault transit migration

# Runbook — Vault Transit migration for ClientSecret rows

**Owner**: Plateforme · **Audience**: MSSP\_ADMIN avec accès Vault root + Postgres
**Statut**: V1 (avril 2026) · **Niveau de risque**: moyen (déchiffrement de tous les `ClientSecret` puis ré-écriture)

## Contexte

Avant le 2026-04, chaque `ClientSecret` était chiffré en AES-256-GCM avec une clé `ENCRYPTION_KEY` injectée dans `process.env` du conteneur applicatif. La clé n'était pas dans `.env`, mais elle était lisible via `/proc/<node-pid>/environ` par tout root du conteneur ou tout opérateur capable d'un `docker exec`.

Vault Transit (cryptography-as-a-service) déplace la clé maître dans Vault. L'app appelle `transit/encrypt/mssp-clients` et `transit/decrypt/mssp-clients` en HTTP, ne détient jamais la clé, et obtient en plus la rotation, le rewrap et l'audit centralisés.

Le code applicatif (`src/lib/crypto.ts`) supporte les deux formats simultanément :

* **legacy AES** — `ClientSecret.iv` + `authTag` non NULL → déchiffrement local.
* **Transit** — `encryptedValue` préfixé `vault:v1:…`, `iv` et `authTag` à NULL → appel HTTP Vault.

Toute nouvelle écriture utilise Transit. Ce runbook décrit comment rewrap les lignes héritées.

## Pré-requis

1. Vault déployé, descellé, secrets engine `transit/` activé avec les clés `mssp-platform` et `mssp-clients`. Vérification :
   ```bash theme={null}
   docker exec mssp-vault vault list transit/keys
   # Doit retourner: mssp-clients, mssp-platform
   ```
2. La policy `mssp-app` accorde `update` sur `transit/encrypt/mssp-clients` et `transit/decrypt/mssp-clients` (déjà appliqué par `platform/docker/vault/init.sh`).
3. Postgres up, schéma à jour (`ClientSecret.iv` et `ClientSecret.authTag` nullable). Vérification :
   ```bash theme={null}
   docker exec mssp-postgres psql -U mssp -d mssp_platform -c "\d \"ClientSecret\""
   # iv et auth_tag doivent apparaître nullable
   ```
4. Variables d'environnement disponibles à l'opérateur : `DATABASE_URL`, `VAULT_ADDR`, `VAULT_TOKEN` (token avec capability `update` sur `transit/encrypt/mssp-clients`), `ENCRYPTION_KEY` (l'ancienne clé AES, lue depuis Vault `mssp/data/platform`).

## Procédure

### 1 — Inventaire pré-migration

```bash theme={null}
docker exec mssp-postgres psql -U mssp -d mssp_platform -c \
  "SELECT COUNT(*) FROM \"ClientSecret\" WHERE iv IS NOT NULL AND encrypted_value NOT LIKE 'vault:%';"
```

Notez le compte. Ce sera la cible.

### 2 — Dry-run (aucune écriture, valide la chaîne complète decrypt→re-encrypt)

```bash theme={null}
docker exec mssp-app sh -c '
  cd /app && \
  DATABASE_URL=$(cat /proc/1/environ | tr "\0" "\n" | grep "^DATABASE_URL=" | cut -d= -f2-) \
  VAULT_ADDR=$VAULT_ADDR \
  VAULT_TOKEN=$VAULT_TOKEN \
  ENCRYPTION_KEY=$ENCRYPTION_KEY \
  node scripts/rewrap-legacy-secrets.mjs --dry-run
'
```

Sortie attendue : une ligne JSON `"would rewrap"` par row, puis `"done"` avec `ok = N`, `failed = 0`, `dryRun = true`.

Si `failed > 0` : interrompre. Soit la clé AES n'est pas la bonne (mauvais `ENCRYPTION_KEY`), soit une row a été corrompue. Ne pas continuer avant d'avoir résolu.

### 3 — Application

```bash theme={null}
docker exec mssp-app sh -c '
  cd /app && \
  DATABASE_URL=$(cat /proc/1/environ | tr "\0" "\n" | grep "^DATABASE_URL=" | cut -d= -f2-) \
  VAULT_ADDR=$VAULT_ADDR \
  VAULT_TOKEN=$VAULT_TOKEN \
  ENCRYPTION_KEY=$ENCRYPTION_KEY \
  node scripts/rewrap-legacy-secrets.mjs
'
```

Le script traite chaque row dans sa propre transaction. Si une écriture échoue, les rows déjà rewrappées restent en Transit — le rerun est idempotent (filtre `iv IS NOT NULL`).

### 4 — Vérification post-migration

```bash theme={null}
# Compte de lignes héritées restantes (doit être 0)
docker exec mssp-postgres psql -U mssp -d mssp_platform -c \
  "SELECT COUNT(*) FROM \"ClientSecret\" WHERE iv IS NOT NULL AND encrypted_value NOT LIKE 'vault:%';"

# Compte de lignes Transit (doit égaler le total initial)
docker exec mssp-postgres psql -U mssp -d mssp_platform -c \
  "SELECT COUNT(*) FROM \"ClientSecret\" WHERE iv IS NULL AND encrypted_value LIKE 'vault:%';"
```

### 5 — Smoke-test applicatif

Côté UI, ouvrir un client ayant des secrets, déclencher une action qui nécessite `decrypt` (ex : test de connexion onboarding). Si la lecture passe sans erreur, la migration est validée.

### 6 — Audit log

```bash theme={null}
docker exec mssp-postgres psql -U mssp -d mssp_platform -c \
  "INSERT INTO \"PlatformAuditLog\" (action, resource, details, \"userId\")
   VALUES ('vault.transit_migration', 'ClientSecret',
           jsonb_build_object('migrated', N, 'date', now()),
           '<MSSP_ADMIN_USER_ID>');"
```

Remplacer `N` par le compte effectif et `<MSSP_ADMIN_USER_ID>` par l'opérateur. Le log audit est scellé Ed25519 par les anchors.

## Garde-fous

* Le script refuse de tourner contre un `DATABASE_URL` non-localhost sans `--confirm-prod`. Ce flag est volontairement explicite : ne pas l'ajouter par réflexe.
* Le script utilise des transactions par row — pas de blast radius transactionnel. En cas d'échec partiel, les rows OK sont déjà migrées et le rerun reprend là où l'arrêt a eu lieu.
* `ENCRYPTION_KEY` reste nécessaire tant que toutes les rows n'ont pas été rewrappées. Elle peut être retirée de `mssp/data/platform` UNIQUEMENT après confirmation que `COUNT(*) WHERE iv IS NOT NULL = 0`.

## Rollback

Aucun rollback automatique. Si la migration produit des rows inutilisables :

1. Vault Transit ne fait JAMAIS de purge — toutes les opérations sont sur des clés versionnées. Donc même si la clé `mssp-clients` est rotée, les ciphertexts `vault:v1:…` restent déchiffrables.
2. Si malgré tout un rollback est nécessaire : restaurer la table `ClientSecret` depuis le dernier backup Postgres (cf. [docs/dr/](../dr/)).

## Post-migration

Une fois la totalité des clients migrés et validés :

1. Retirer `ENCRYPTION_KEY` de `mssp/data/platform` dans Vault.
2. Retirer la lecture de `ENCRYPTION_KEY` dans `instrumentation.ts` (mapping `encryption_key` → `ENCRYPTION_KEY`) et dans le mapping requis prod du même fichier.
3. Supprimer le code `decryptLegacyAes()` de `src/lib/crypto.ts` (laisser une note dans le commit avec un lien vers ce runbook pour traçabilité).

Ces 3 étapes ne sont pas urgentes — le code legacy est dormant tant qu'il n'y a pas de row `iv NOT NULL`. Calendrier suggéré : Q3 2026 (après 2 mois de stabilité Transit).
