Documentation Index
Fetch the complete documentation index at: https://snakysec.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Runbook — Chiffrement data-in-motion + data-at-rest
Owner: Plateforme · Audience: MSSP_ADMIN, ops VPS
Statut: V1 (avril 2026)
TL;DR
Persistance — depuis avril 2026, le Vault pré-prod tourne en mode prod (file storage + Shamir 1/1 auto-unsealed). State, certs, KV, AppRole IDs survivent à tous les docker restart / docker compose recreate. Plus de re-bootstrap après chaque recreate. Cf. compose/vault.preprod.yml.
| Plan | Composant | Statut V1 | Cipher / mode |
|---|
| In motion | Browser → App | ✅ | TLS 1.3 (Let’s Encrypt prod / mkcert dev) via Traefik |
| App → Microsoft Graph / GitLab / Sentry | ✅ | TLS 1.3 (forcé par les fournisseurs) |
| App → Postgres | ✅ | TLS 1.2+ (self-signed, sslmode=require) |
| App → Redis | ✅ | TLS 1.2+ (self-signed, rediss://) |
| App → Vault | ✅ | TLS 1.2+ (self-signed, https://) |
| Vault → Postgres | ✅ | TLS 1.2+ (sslmode=require) |
| Vault → Redis | ✅ | TLS 1.2+ (tls=true insecure_tls=true) |
| At rest | ClientSecret values | ✅ | Vault Transit AES-256-GCM-96 (rotation 90j) |
| Backups Postgres → S3 OVH/Scaleway | ✅ | pgbackrest aes-256-cbc (cipher pass dans Vault) |
| Vault data | ✅ | Shamir seal 5/3 (file storage chiffré) |
| Audit log | ✅ | intégrité Ed25519 hash chain (PAS confidentialité — par design) |
| Postgres data files | ⚠️ | LUKS au niveau VPS (cf. §2.4) |
| Redis RDB/AOF | ⚠️ | LUKS au niveau VPS (cf. §2.4) |
| Reports / artifacts disk | ⚠️ | volume Docker privé, LUKS au niveau VPS |
Niveaux de menace couverts :
- ✅ Snooping passif sur data-net (entre containers)
- ✅ Vol de DB dump non chiffré
- ✅ Compromission S3 backup (chiffré client-side avant upload)
- ⚠️ Vol disque physique du VPS (mitigé par LUKS, à valider sur l’infra cible)
- ❌ MITM actif sur data-net (mitigation Q3 via Vault PKI + verify-ca)
1 — In motion (TLS sur data-net interne)
1.1 — Postgres TLS
Cert : self-signed CN=postgres, SAN=postgres,localhost,127.0.0.1, validité 5 ans, généré au premier initdb par pg-tls-init.sh puis persisté dans postgres-data volume.
Config : postgresql.auto.conf reçoit ssl = on, ssl_min_protocol_version = TLSv1.2.
Clients :
- App :
?sslmode=require dans toutes les DATABASE_URL (compose env, instrumentation.ts injection, entrypoint.sh reconstruction)
- Vault
database/config/postgres : connection_url='…?sslmode=require'
Vérification post-déploiement :
docker exec mssp-postgres psql -U mssp -d mssp_platform -c "SHOW ssl;"
# attendu: ssl | on
docker exec mssp-postgres psql "host=postgres port=5432 user=mssp password=$(...) dbname=mssp_platform sslmode=require" \
-c "SELECT ssl_is_used();"
# attendu: t
# Côté app: les logs Prisma doivent mentionner "SSL"
docker logs mssp-app | grep -i ssl | head
Rotation : volume-persisté → cert change uniquement si server.crt/server.key sont supprimés du PGDATA puis le container redémarré. Q3 : passer en Vault PKI avec rotation 90j auto.
1.2 — Redis TLS
Cert : self-signed CN=redis, SAN=redis,localhost, validité 5 ans, généré au premier boot par redis-tls-entrypoint.sh dans /data/tls/.
Config : Redis bind sur --tls-port 6379, port plain --port 0 (TLS-only). --tls-auth-clients no : pas de mTLS client cert (V1, à activer Q3 avec Vault PKI).
Clients :
- App :
REDIS_URL=rediss://... (ioredis détecte automatiquement le scheme TLS)
- Vault
database/config/mssp-redis : tls=true insecure_tls=true
NODE_TLS_REJECT_UNAUTHORIZED=0 est déjà setté dans l’env de mssp-app, donc ioredis accepte le cert self-signed sans broncher. À retirer en Q3 quand on aura le cert CA pinné.
Vérification :
# Connexion plain bloquée
docker exec mssp-redis redis-cli -p 6379 ping
# expected: "Error: Connection reset by peer" or similar (TLS expected)
# Connexion TLS OK
docker exec mssp-redis redis-cli --tls --insecure -a "$REDIS_PASSWORD" ping
# expected: PONG
1.3 — Vault HTTPS interne
Cert :
- Dev : généré automatiquement par
vault server -dev -dev-tls -dev-tls-cert-dir=/vault/tls
- Prod : généré par entrypoint.sh au premier boot dans
/vault/tls/vault.crt + /vault/tls/vault.key. Référencé dans vault-prod.hcl tls_cert_file. Persisté dans le volume Vault.
Config :
- Listener :
tls_cert_file + tls_key_file + tls_min_version=tls12 (vault-prod.hcl)
api_addr = "https://mssp-vault:8200" (vault-prod.hcl) — Vault advertise cette URL aux clients
Clients :
- App + workers :
VAULT_ADDR=https://mssp-vault:8200
NODE_TLS_REJECT_UNAUTHORIZED=0 couvre la vérification self-signed (idem Redis ci-dessus)
- Pour le CLI dans le container :
VAULT_SKIP_VERIFY=true (env compose)
- Scripts entrypoint (sh) :
wget --no-check-certificate / curl -k
Vérification :
# CLI inside Vault container
docker exec mssp-vault vault status
# CLI from host (skip verify because self-signed)
docker exec mssp-vault sh -c 'VAULT_ADDR=https://localhost:8200 VAULT_SKIP_VERIFY=true vault status'
# Plain HTTP must fail (listener is TLS-only)
docker exec mssp-vault sh -c 'VAULT_ADDR=http://localhost:8200 vault status' || echo "HTTP correctly refused"
1.4 — Externes (HTTPS forcé fournisseur)
- Microsoft Graph : HTTPS only, certs Microsoft, TLS 1.2+
- GitLab API (gitlab.com) : HTTPS, cert Let’s Encrypt
- Sentry EU : HTTPS, cert Sentry
- Browser ↔ App : Traefik termine TLS — Let’s Encrypt en prod (
certresolver=letsencrypt), mkcert en dev
2 — At rest
2.1 — ClientSecret values (Vault Transit)
Voir vault-transit-migration.md et secrets-rotation.md §1. AES-256-GCM-96, clé jamais hors de Vault, auto-rotate 90j.
2.2 — Backups Postgres (pgbackrest)
platform/docker/postgres/pgbackrest.conf configure :
repo1-cipher-type=aes-256-cbc # OVH Object Storage
repo2-cipher-type=aes-256-cbc # Scaleway Object Storage
Le cipher pass est lu depuis Vault (PGBACKREST_REPO{1,2}_CIPHER_PASS) au moment du backup par postgres-pgbackrest.sh. Aucun backup chiffré en clair sur disque, OVH/Scaleway voient des bytes random.
Rotation cipher pass : conservée pour les 4 derniers fulls (pgbackrest gère). Pour rotation manuelle : générer nouveau, écrire dans Vault, lancer un full backup. Les diff/incremental basés sur l’ancien full continuent à utiliser l’ancien cipher pass jusqu’à expiration.
2.3 — Vault data (Shamir seal)
Trois modes selon l’environnement :
| Mode | Compose | Storage | Shamir | Unseal | Data persiste |
|---|
| Prod | vault.prod.yml | file sur /opt/mssp/data/vault (LUKS) | 5 shares, 3 threshold | manuel via 3 keyholders | ✅ |
| Pré-prod | vault.preprod.yml | file dans Docker volume platform_vault-data | 1 share, 1 threshold | auto via init-keys.json | ✅ |
| Dev (CI/tests) | vault.dev.yml | in-memory | n/a (-dev mode) | auto | ❌ ephemeral |
Pré-prod = mêmes garanties de persistance que prod, sans la complexité opérationnelle des keyholders. C’est volontaire : le pré-prod doit refléter prod pour valider les déploiements + migrations sans rejouer le bootstrap à chaque restart.
init-keys.json (master key + root token) vit dans le volume Docker platform_vault-data, lui-même protégé par le disk encryption du host (FileVault/BitLocker en dev, LUKS sur VPS en prod).
Promotion pré-prod → prod : copier /vault/data/ vers /opt/mssp/data/vault/, re-seal avec 5 shares (vault operator rekey-init), distribuer fragments selon docs/dr/01-keyholders.md.
Bootstrap one-shot (à faire une seule fois après make preprod) :
make vault-bootstrap-db # provisions le user PG "vault" + wire database engine
Voir secrets-rotation.md §2 — Pré-requis pour les détails.
2.4 — Disk-level (LUKS sur VPS)
Recommandation prod :
# Au provisionnement initial du VPS (one-shot, avant install Docker) :
cryptsetup luksFormat /dev/<data-disk>
cryptsetup luksOpen /dev/<data-disk> mssp-data
mkfs.ext4 /dev/mapper/mssp-data
mount /dev/mapper/mssp-data /opt/mssp/data
# Au boot du VPS, l'unlock peut être :
# - manuel (3 min downtime, plus secure : passphrase tapée en SSH au reboot)
# - automatique via TPM2 (clé dérivée du TPM)
# - automatique via Tang/Clevis (network-bound encryption)
# /etc/crypttab pour TPM2 auto-unlock :
mssp-data UUID=<luks-uuid> none luks,tpm2-device=auto,tpm2-pcrs=0+7
Couvre :
- ✅ Vol disque physique du VPS
- ✅ Snapshot de volume cloud (le snapshot est chiffré)
- ✅ Récupération de disque en cas de panne hardware
Ne couvre pas :
- ❌ Compromission OS du VPS (root sur l’OS = lecture clair)
- ❌ Memory dump (RAM contient les secrets unsealed)
Volumes Docker à mettre derrière LUKS :
/opt/mssp/data/postgres (PGDATA)
/opt/mssp/data/redis
/opt/mssp/data/vault (storage Shamir)
/opt/mssp/data/approle (AppRole credentials)
/opt/mssp/data/artifacts (PDF/Excel/JSON reports)
/opt/mssp/data/archive (audit retention 12 mois)
Volumes mac dev : FileVault au niveau OS suffit (déjà actif par défaut sur macOS récent).
Volumes Windows dev : BitLocker au niveau disque (vérifier dans Settings → Update & Security).
2.5 — Reports / artifacts disk (V1 plain)
Les rapports générés (PDF exécutif, Excel, HTML) atterrissent en clair dans reports-data Docker volume. Mitigation V1 : LUKS sur le disque VPS.
Q3 : chiffrer chaque rapport via Vault Transit avant écriture sur disque, déchiffrer à la lecture. Coût : 2 round-trips Vault par téléchargement de rapport. Vaut le coup pour les rapports CERTIFICATION (signed Ed25519), pas pour les TRACKING (vivants, régénérables).
3 — Procédures rotation
Cert TLS Postgres (5y validity)
Trop long pour V1 (les 5y évitent un cycle de rotation à orchestrer). Q3 : Vault PKI émet des certs 90j auto-renouvelés.
Forcer une rotation manuelle :
docker exec mssp-postgres bash -c '
PGDATA_DIR="/var/lib/postgresql/data"
rm -f "$PGDATA_DIR/server.crt" "$PGDATA_DIR/server.key"
'
docker compose restart postgres
# Le hook initdb ne re-run pas (PGDATA déjà initialisée). Re-générer manuellement:
docker exec mssp-postgres /docker-entrypoint-initdb.d/02-pg-tls-init.sh
docker compose restart postgres
Cert TLS Redis
Idem Postgres, supprimer /data/tls/server.{crt,key} puis restart redis.
Cert TLS Vault
Idem, supprimer /vault/tls/vault.{crt,key} puis restart vault. Attention : restart Vault prod = re-seal + besoin d’unseal manuel via les 3 fragments Shamir.
4 — Roadmap Q3
| Chantier | Sortie de | Effort |
|---|
| Vault PKI engine pour certs internes (verify-ca + rotation 90j auto) | self-signed + skip-verify | Moyen |
Drop NODE_TLS_REJECT_UNAUTHORIZED=0 global, pin certs explicitement | env globale | Faible |
| Reports disk encryption via Transit | disk plain | Moyen |
mTLS Redis (--tls-auth-clients yes) | TLS unilatéral | Faible |
Postgres sslmode=verify-full + cert pinning | sslmode=require | Faible (post-PKI) |