PD-47 — Expression de Besoin¶
Contexte¶
ProbatioVault repose sur PostgreSQL (OVH Managed) pour les métadonnées transactionnelles : utilisateurs, documents, enveloppes probatoires, journaux append-only, ancrages Merkle. Si les documents sont redondés multi-cloud (OVH S3 WORM + AWS Glacier), les métadonnées PostgreSQL constituent le cœur transactionnel du système.
Une perte ou corruption de la base compromettrait : - La capacité à relier un document à son propriétaire - La reconstruction des enveloppes probatoires - La vérification probatoire (Merkle, tx_id, journaux append-only) - L'auditabilité complète exigée par NF Z42-013
Problème¶
Il n'existe actuellement aucun mécanisme de backup automatisé pour PostgreSQL. La redondance documentaire (S3 WORM + Glacier) ne protège pas les métadonnées. La résilience transactionnelle est incomplète.
Les exigences sont : 1. Résilience : RPO 24h pour pg_dump, PITR entre deux dumps via WAL archiving 2. Confidentialité : Aucune donnée en clair persistante — double chiffrement (applicatif + SSE) 3. Rotation : Rétention glissante 30 jours avec lifecycle S3 4. Traçabilité : Journal append-only de chaque événement de backup 5. Restauration : Procédure testable et vérifiable en environnement staging
Solution proposée¶
Stratégie hybride pg_dump + WAL archiving¶
| Composant | Mode | Fréquence | RPO |
|---|---|---|---|
| pg_dump -Fc -Z 9 | Full logique compressé | Quotidien 03h00 CET | 24h |
| WAL archiving | Continu (archive_command) | Temps réel | < 5 min |
| Basebackup | Full physique | Hebdomadaire (dimanche 02h00) | N/A (base PITR) |
Le pg_dump quotidien fournit un point de restauration garanti. Le WAL archiving permet le PITR (Point-In-Time Recovery) entre deux dumps.
Chiffrement double couche¶
| Couche | Mécanisme | Clé | Responsabilité |
|---|---|---|---|
| Applicatif | AES-256-GCM | K_backup dérivée via HKDF, sous enveloppe HSM | ProbatioVault |
| Transport/Stockage | SSE-KMS côté S3 | KMS OVH/AWS | Fournisseur cloud |
Invariant : Aucun dump en clair ne persiste sur disque > 5 minutes. Le fichier temporaire est supprimé immédiatement après chiffrement et upload.
Scheduling : Prefect + cron watchdog¶
| Composant | Rôle | Détail |
|---|---|---|
| Prefect flow (agent dédié) | Orchestrateur principal | Déclenche backup, gère retries, alerting, monitoring dashboard |
| Cron watchdog | Filet de sécurité | Vérifie que le backup du jour existe dans S3. Si absent → alerte + relance |
Le backup est indépendant du backend NestJS : même si le backend est down, le backup s'exécute.
Stockage S3¶
- Bucket :
probatiovault-db-backups - Arborescence :
{year}/{month}/backup_{YYYYMMDD}.enc+wal/{segment}.enc - Versioning : Activé
- Chiffrement : SSE-KMS activé
- Accès : IAM dédié, aucun accès public
- Lifecycle : Suppression automatique > 30 jours, transition Glacier optionnelle après 7 jours
Logging probatoire¶
Chaque backup génère un événement dans le journal append-only interne :
{
"event_type": "db_backup",
"timestamp": "2026-03-02T03:00:00Z",
"backup_id": "bkp-20260302-full",
"backup_type": "pg_dump|wal|basebackup",
"size_bytes": 123456789,
"hash_sha3_256": "abc123...",
"encryption": "AES-256-GCM+SSE-KMS",
"status": "SUCCESS|FAILED",
"duration_ms": 45000
}
Décision : Pas d'ancrage Merkle/blockchain pour les événements de backup. Journal interne uniquement.
Périmètre¶
Inclus¶
- pg_dump quotidien compressé + chiffré
- WAL archiving continu + chiffré
- Basebackup hebdomadaire
- Upload S3 bucket dédié avec IAM isolé
- Politique lifecycle S3 : 30 jours
- Prefect flow + cron watchdog
- Script de restauration documenté
- Logging append-only
- Tests de restauration (staging)
Exclus¶
- Sauvegarde des fichiers S3 (redondance multi-cloud existante)
- Snapshot infra Terraform
- Sauvegarde Redis (non critique, reconstituable)
- Ancrage Merkle des événements de backup (décision PO)
Exigences non fonctionnelles¶
| Critère | Exigence |
|---|---|
| Confidentialité | Double chiffrement (applicatif + SSE) |
| Traçabilité | Journal append-only |
| Durée max | < 15 min pour DB < 50 GB |
| RPO pg_dump | 24h |
| RPO WAL | < 5 min |
| RTO cible | < 2h |
| Restauration | Procédure testée trimestriellement |
Invariants de sécurité¶
- INV-47-01 : Aucune donnée en clair ne persiste sur disque > 5 minutes
- INV-47-02 : Clé K_backup séparée de K_master (dérivation HKDF dédiée)
- INV-47-03 : Accès IAM isolé — aucun accès public au bucket
- INV-47-04 : Logging immuable (append-only) de chaque opération
- INV-47-05 : Cron watchdog indépendant du flow Prefect (filet de sécurité)
Critères d'acceptation¶
- Backup automatique pg_dump visible chaque jour dans S3
- WAL archivés en continu dans S3
- Fichiers chiffrés (double couche) vérifiables
- Rotation 30j effective (lifecycle S3)
- Restauration testée en environnement staging (schéma + données intègres)
- Hash SHA3-256 cohérent après restauration
- Journal probatoire généré pour chaque opération
- Prefect flow opérationnel + dashboard monitoring
- Cron watchdog détecte un backup manquant et alerte
Cas d'erreur¶
| Cas | Action |
|---|---|
| Échec pg_dump | Retry x3 (backoff exponentiel) + alerte Prefect |
| Échec upload S3 | Retry x3 + alerte |
| Fichier > taille attendue | Alerte (anomalie potentielle) |
| Hash mismatch post-upload | Annulation + alerte critique |
| Échec chiffrement | Abort — aucun upload de donnée en clair |
| WAL archiving interrompu | Alerte + reprise automatique |
Learnings injectés¶
| Story | Learning | Impact PD-47 |
|---|---|---|
| PD-5 | Vault Lock vs Object Lock ; Deep Archive restoration 12-48h | Lifecycle S3 : préférer Object Lock (réversible) |
| PD-264 | Atomicité : clarifier DB sync vs post-commit async (BullMQ) | Backup = opération infra async, hors transaction applicative |
| PD-8 | Vault PostgreSQL backend, TLS end-to-end, AppRole | K_backup sous enveloppe HSM via Vault AppRole |
Décisions validées (PO)¶
| # | Question | Décision |
|---|---|---|
| D1 | WAL ou pg_dump ? | Hybride : pg_dump quotidien + WAL archiving |
| D2 | Chiffrement applicatif ou SSE ? | Double : applicatif AES-256-GCM + SSE-KMS |
| D3 | Ancrage Merkle ? | Non — journal interne uniquement |
| D4 | Scheduling ? | Prefect flow (agent dédié) + cron watchdog |