PD-47 — Sauvegarde PostgreSQL chiffrée avec PITR et traçabilité probatoire¶
1. Objectif¶
La User Story PD-47 contractualise un mécanisme de sauvegarde PostgreSQL automatisé, chiffré et traçable, garantissant :
- la restauration des métadonnées transactionnelles ProbatioVault ;
- un RPO de 24h via sauvegarde logique quotidienne ;
- un RPO < 5 min entre deux sauvegardes via archivage WAL continu ;
- une rétention glissante de 30 jours ;
- une restauration vérifiable en environnement staging ;
- un journal append-only de chaque opération de sauvegarde.
Ce document définit les obligations fonctionnelles, temporelles, de sécurité, de format de données et de testabilité. Toute règle non vérifiable est marquée hors périmètre.
2. Périmètre / Hors périmètre¶
Inclus¶
- Sauvegarde logique PostgreSQL quotidienne compressée.
- Archivage WAL continu pour PITR entre deux sauvegardes logiques.
- Sauvegarde physique hebdomadaire (base PITR).
- Chiffrement double couche : applicatif + SSE-KMS stockage.
- Stockage S3 dédié avec versioning, IAM isolé, lifecycle 30 jours.
- Orchestration par flow dédié et watchdog indépendant.
- Journal append-only interne pour chaque événement de backup.
- Procédure de restauration testable en staging.
- Vérification d’intégrité par hash post-restauration.
Exclu¶
- Sauvegarde des objets documentaires métier déjà redondés multi-cloud.
- Snapshots d’infrastructure (Terraform/Ansible).
- Sauvegarde Redis.
- Ancrage Merkle/blockchain des événements de backup.
3. Définitions¶
RPO: perte maximale de données admissible.RTO: temps maximal de remise en service.PITR: restauration à un instant donné via WAL.WAL: journaux de transactions PostgreSQL.Backup logique: export logique compressé de la base.Basebackup: copie physique complète de la base.SSE-KMS: chiffrement côté stockage avec clé KMS fournisseur.Chiffrement applicatif: chiffrement avant upload.Journal append-only: journal immuable en ajout seul.Watchdog: contrôle indépendant de présence du backup attendu.
4. Invariants (non négociables)¶
| ID | Règle | Justification |
|---|---|---|
| INV-47-01 | Aucun artefact de backup en clair ne persiste sur disque au-delà de 300 s après fin d’export. | Confidentialité et réduction de surface d’exposition. |
| INV-47-02 | Toute sauvegarde uploadée est chiffrée en double couche : AES-256-GCM (applicatif) + SSE-KMS (stockage). | Défense en profondeur. |
| INV-47-03 | La clé de backup est dérivée via mécanisme dédié et séparée de la clé maître. | Séparation cryptographique des usages. |
| INV-47-04 | Le bucket de sauvegarde n’accepte aucun accès public ; accès uniquement via identité IAM dédiée. | Contrôle d’accès strict. |
| INV-47-05 | Chaque tentative de backup (succès/échec) génère au moins un événement append-only horodaté ; les doublons éventuels sont dédupliqués par backup_id. [CORR E-04] | Auditabilité NF Z42-013. |
| INV-47-06 | Le watchdog de contrôle d’exécution est indépendant de l’orchestrateur principal. | Résilience en cas de panne partielle. |
| INV-47-07 | Toute transition d’état d’un backup respecte la machine d’états définie en §5.4 ; transition non listée = interdite. | Prévisibilité et testabilité. |
| INV-47-08 | Toute restauration de validation staging vérifie l’intégrité hash (SHA3-256) avant validation finale. | Non-altération des sauvegardes. |
| INV-47-09 | Envelope encryption : tout artefact cryptographique temporaire (clé dérivée, DEK, fragment de clé) est chiffré au repos, jamais stocké en clair en base. | Invariant crypto obligatoire. |
5. Flux nominaux¶
5.1 Modèle de données contractuel (source unique des formats)¶
| Donnée | Format / encodage | Longueur / taille | Jeu de caractères | Case | Regex | Comportement si invalide |
|---|---|---|---|---|---|---|
backup_id | ASCII structuré | 18 à 56 caractères [CORR E-07] | [a-z0-9-] | sensible à la casse (doit être lowercase) | ^bkp-[0-9]{8}-(pg_dump|wal|basebackup)-[a-z0-9]{1,32}$ [CORR E-06] | rejet de l’événement (code de rejet FAILED_FORMAT), alerte [CORR E-02] |
backup_type | enum | 1 valeur | pg_dump, wal, basebackup | n/a | n/a | rejet (code de rejet FAILED_FORMAT) [CORR E-02] |
status | enum | 1 valeur | SCHEDULED, RUNNING, SUCCESS, FAILED, EXPIRED, DELETED [CORR E-03] | sensible | n/a | rejet (code de rejet FAILED_FORMAT) [CORR E-02] |
timestamp | ISO 8601 UTC | 20 caractères (YYYY-MM-DDTHH:mm:ssZ) | ASCII | sensible | ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ | rejet (code de rejet FAILED_FORMAT) [CORR E-02] |
hash_sha3_256 | hex | 64 caractères (32 bytes) | [a-f0-9] | sensible (lowercase) | ^[a-f0-9]{64}$ | rejet + alerte critique |
size_bytes | entier non signé | min 1, max 53 bits JS-safe | chiffres décimaux | n/a | ^[0-9]{1,16}$ | rejet (code de rejet FAILED_FORMAT) [CORR E-02] |
duration_ms | entier non signé | min 1 ms, max 86_400_000 ms | chiffres décimaux | n/a | ^[0-9]{1,8}$ | rejet (code de rejet FAILED_FORMAT) [CORR E-02] |
s3_object_key_backup | clé objet S3 | 24 à 128 caractères | [a-z0-9/_\.-] | sensible | ^[0-9]{4}/[0-9]{2}/backup_[0-9]{8}\.enc$ | rejet upload |
s3_object_key_wal | clé objet S3 | 20 à 200 caractères | [a-zA-Z0-9/_\.-] [CORR E-01] | sensible | ^wal/[A-Z0-9]{24}(\.[A-Z0-9]{8})?\.enc$ | rejet upload |
encryption | chaîne constante | 19 à 64 caractères | ASCII printable | sensible | ^AES-256-GCM\+SSE-KMS$ | rejet (code de rejet FAILED_FORMAT) [CORR E-02] |
FAILED_FORMAT est un code de rejet (format invalide) et n’est pas un état de status ni de la machine d’états. [CORR E-02]
Aucune autre section ne redéfinit ces formats ; elles y font référence.
5.2 Flux nominal F1 — Sauvegarde logique quotidienne¶
- Déclenchement planifié quotidien à 03:00 Europe/Paris.
- Production d’un dump logique compressé.
- Chiffrement applicatif immédiat (
AES-256-GCM). - Suppression de l’artefact en clair avant 300 s après fin d’export.
- Upload vers bucket dédié avec SSE-KMS actif.
- Écriture d’un événement append-only
backup_type=pg_dump. - Vérification de présence de l’objet attendu par watchdog avant 04:00 Europe/Paris.
5.3 Flux nominal F2 — Archivage WAL continu¶
- Capture continue des segments WAL.
- Calcul du hash
SHA3-256par segment WAL avant upload, et journalisation associée. [CORR E-12] - Chiffrement applicatif de chaque segment avant persistance distante.
- Upload vers préfixe
wal/. - Journalisation append-only par segment ou lot contractuel.
- Exigence de dérive maximale (
lag) de livraison : P95 < 5 min.
5.4 Flux nominal F3 — Sauvegarde physique hebdomadaire¶
- Déclenchement hebdomadaire le dimanche à 02:00 Europe/Paris.
- Chiffrement applicatif puis upload SSE-KMS.
- Journalisation append-only
backup_type=basebackup. - Conservation selon même politique lifecycle (30 jours).
5.5 Flux nominal F4 — Restauration de validation (staging)¶
- Sélection d’un point de restauration cible.
- Restauration base + rejouage WAL jusqu’au point cible.
- Vérification d’intégrité hash
SHA3-256des artefacts restaurés. - Validation de cohérence schéma + données métier attendues.
- Enregistrement de preuve de test de restauration.
5.6 Atomicité multi-composant (DB + async)¶
| Scope | Synchrone/Async | Garantie contractuelle |
|---|---|---|
| Création entrée d’exécution de backup | Synchrone | Atomicité locale de l’écriture d’état |
| Upload objet backup | Async | Idempotent, retry-safe |
| Écriture journal append-only | Async post-traitement | Au moins une écriture par tentative ; déduplication par backup_id [CORR E-04] |
| Crash pré-commit état local | n/a | Aucun état durable “SUCCESS” ne doit exister |
| Crash post-commit état local | n/a | Réconciliation obligatoire jusqu’à cohérence état↔objet↔journal ; l’exécution de la réconciliation et son résultat sont observables (trace/log/événement d’audit). [CORR E-09] |
5.7 Machine d’états et transitions inverses¶
États : SCHEDULED, RUNNING, SUCCESS, FAILED, EXPIRED, DELETED.
SCHEDULED -> RUNNING: autorisée.RUNNING -> SUCCESS: autorisée.RUNNING -> FAILED: autorisée.SUCCESS -> EXPIRED: autorisée à expiration de rétention.EXPIRED -> DELETED: autorisée (suppression lifecycle).FAILED -> SCHEDULED: autorisée (nouvelle tentative planifiée).SUCCESS -> RUNNING: interdite (nouvelle exécution doit créer un nouveaubackup_id).DELETED -> *: interdite (état terminal, résolution manuelle uniquement).EXPIRED -> SUCCESS: interdite.FAILED -> SUCCESS: interdite sans nouvelle exécution.
Clarification retry vs replanification : les retries (upload/chiffrement/export) sont effectués dans la même tentative (sans transition FAILED -> SCHEDULED) ; la transition FAILED -> SCHEDULED ne s’applique qu’après épuisement des retries et replanification d’une nouvelle tentative. [CORR E-11]
Comportement de retour/downgrade :
- Retour opérationnel
FAILED -> SCHEDULEDconserve les traces, ne supprime aucun journal. - Aucune “réouverture” d’un backup
SUCCESS; un nouveau cycle est requis. - Les états terminaux n’autorisent aucune transition sortante.
5.8 SLA temporels et bornes numériques¶
| Paramètre | Défaut | Min | Max | Unité | Configurable | Référence | Percentile | Hors bornes |
|---|---|---|---|---|---|---|---|---|
| Fréquence backup logique | 24 | 1 | 24 [CORR E-05] | h | oui | UTC scheduler | n/a | rejet config |
| Heure backup logique | 03:00 | 00:00 | 23:59 | hh:mm TZ | oui | Europe/Paris | n/a | rejet config |
| RPO backup logique | 24 | 24 [CORR E-08] | 24 [CORR E-08] | h | non | exigences métier | n/a | non-conformité |
| Lag WAL | 5 | 1 | 15 | min | oui | plateforme prod | P95 | alerte critique |
| Durée backup logique | 15 | 1 | 60 | min | oui | DB ≤ 50 GB | P95 | alerte + escalade |
| RTO restauration | 120 | 30 | 240 | min | oui | staging de référence | P95 | non-conformité |
| Rétention S3 | 30 | 7 | 90 | jours | oui | policy bucket | n/a | rejet policy |
| Seuil suppression clair local | 300 | 300 [CORR E-08] | 300 [CORR E-08] | s | non | hôte backup | n/a | arrêt + alerte |
| Retry pg_dump/upload | 3 | 0 | 5 | tentatives | oui | orchestrateur | n/a | rejet config |
| Backoff retry | 2 | 1 | 5 | facteur | oui | orchestrateur | n/a | clamp |
| Délai initial backoff | 1000 [CORR E-14] | 0 [CORR E-14] | 60000 [CORR E-14] | ms | oui | orchestrateur | n/a | clamp |
| Timeout upload | 300000 | 10000 | 1800000 | ms | oui | réseau prod | P95 | échec tentative |
| Périodicité test restauration staging | 90 [CORR E-16] | 90 [CORR E-16] | 90 [CORR E-16] | jours | non [CORR E-16] | conformité | n/a | non-conformité |
5bis. Diagrammes Mermaid¶
5bis.1 Machine d’états — Cycle de vie d’un backup (§5.7)¶
stateDiagram-v2
[*] --> SCHEDULED
SCHEDULED --> RUNNING : Déclenchement planifié
RUNNING --> SUCCESS : Export + chiffrement + upload OK\n[INV-47-01, INV-47-02, INV-47-05]
RUNNING --> FAILED : Erreur export/chiffrement/upload\n[INV-47-05]
FAILED --> SCHEDULED : Replanification après épuisement retries\n[INV-47-07]
SUCCESS --> EXPIRED : Expiration rétention 30j\n(§5.8)
EXPIRED --> DELETED : Suppression lifecycle S3\n[INV-47-04]
DELETED --> [*]
note right of RUNNING
Retries internes (max 3) restent
dans RUNNING sans transition
vers FAILED (§5.7 clarification)
end note
note right of DELETED
État terminal — aucune
transition sortante autorisée
[INV-47-07]
end note
note left of SUCCESS
Aucune réouverture possible —
nouveau cycle = nouveau backup_id
[INV-47-07]
end note 5bis.2 Séquence — Flux nominal F1 : sauvegarde logique quotidienne (§5.2)¶
sequenceDiagram
participant Scheduler as Orchestrateur<br/>(Planificateur)
participant DB as PostgreSQL
participant Crypto as Module Chiffrement
participant S3 as Bucket S3<br/>(SSE-KMS)
participant Journal as Journal<br/>Append-Only
participant Watchdog as Watchdog<br/>(indépendant)
Note over Scheduler: 03:00 Europe/Paris — déclenchement
Scheduler->>Journal: Événement SCHEDULED [INV-47-05]
Scheduler->>DB: pg_dump compressé
DB-->>Scheduler: Dump logique (clair)
Scheduler->>Scheduler: Transition SCHEDULED → RUNNING [INV-47-07]
Scheduler->>Crypto: Générer DEK éphémère (§10.4)
Crypto-->>Scheduler: DEK en mémoire [INV-47-09]
Scheduler->>Crypto: Chiffrer dump (AES-256-GCM)
Crypto-->>Scheduler: Artefact .enc + hash SHA3-256
Scheduler->>Scheduler: Purger artefact clair < 300s [INV-47-01]
Scheduler->>Crypto: Détruire DEK mémoire (§10.4)
Scheduler->>S3: Upload .enc (SSE-KMS actif) [INV-47-02, INV-47-04]
S3-->>Scheduler: Confirmation upload
Scheduler->>Journal: Événement SUCCESS [INV-47-05]
Scheduler->>Scheduler: Transition RUNNING → SUCCESS [INV-47-07]
Note over Watchdog: Vérification avant 04:00 [INV-47-06]
Watchdog->>S3: Vérifier présence backup_YYYYMMDD.enc
S3-->>Watchdog: Objet présent / absent
alt Objet absent
Watchdog->>Journal: Alerte + relance contrôlée (ERR-47-05)
end 5bis.3 Séquence — Flux nominal F4 : restauration de validation staging (§5.5)¶
sequenceDiagram
participant Op as Opérateur
participant S3 as Bucket S3
participant Crypto as Module Chiffrement
participant Staging as PostgreSQL<br/>Staging
participant Journal as Journal<br/>Append-Only
Op->>S3: Sélection point de restauration cible
S3-->>Op: Backup .enc + segments WAL .enc
Op->>Crypto: Déchiffrer backup (K_backup version historique) [§10.3]
Crypto-->>Op: Dump logique clair
Op->>Staging: Restauration base
Staging-->>Op: Base restaurée
Op->>S3: Récupérer WAL intermédiaires
Op->>Crypto: Déchiffrer WAL
Crypto-->>Op: Segments WAL clairs
Op->>Staging: Rejouage WAL jusqu’au point cible (PITR)
Staging-->>Op: Base à l’instant T
Op->>Op: Vérification hash SHA3-256 [INV-47-08]
Op->>Op: Validation cohérence schéma + données métier
Op->>Journal: Enregistrement preuve test restauration [INV-47-05]
Op->>Op: Purger artefacts clairs < 300s [INV-47-01] 6. Cas d’erreur¶
| ID | Cas | Réponse attendue |
|---|---|---|
| ERR-47-01 | Échec export logique | marquer FAILED, retry max 3, alerte critique si épuisé |
| ERR-47-02 | Échec chiffrement | abort immédiat, aucun upload, purge artefact clair, alerte critique |
| ERR-47-03 | Échec upload | retry max 3, puis FAILED + alerte |
| ERR-47-04 | Hash invalide post-upload | objet marqué invalide, non exploitable restauration, alerte critique |
| ERR-47-05 | Watchdog ne trouve pas backup du jour | alerte + relance contrôlée unique ; si la relance échoue, escalade critique obligatoire [CORR E-17] |
| ERR-47-06 | Interruption WAL archiving | alerte immédiate, reprise automatique, contrôle de rattrapage |
| ERR-47-07 | Violation format données (§5.1) | rejet de l’événement (code de rejet FAILED_FORMAT), journal d’erreur obligatoire [CORR E-02] |
| ERR-47-08 | Échec test restauration trimestriel | ouverture incident conformité, interdiction de clôture sans plan d’action |
7. Critères d’acceptation (testables)¶
| ID | Critère | Observable |
|---|---|---|
| CA-47-01 | Un backup logique chiffré est présent chaque jour | objet backup_YYYYMMDD.enc présent avant 04:00 Europe/Paris |
| CA-47-02 | Archivage WAL continu conforme | lag livraison WAL P95 < 5 min sur 24h |
| CA-47-03 | Double chiffrement effectif | preuve chiffrement applicatif + métadonnée SSE-KMS active |
| CA-47-04 | Aucune persistance claire > 300 s | trace d’audit absence fichier clair au-delà du seuil |
| CA-47-05 | Lifecycle 30 jours effectif | objet J+31 absent (hors versions protégées explicitement) |
| CA-47-06 | Journal append-only exhaustif | 1 événement minimum par tentative, succès/échec inclus |
| CA-47-07 | Restauration staging conforme | restauration réussie, hash et cohérence données validés |
| CA-47-08 | Watchdog indépendant opérationnel | panne orchestrateur principal n’empêche pas détection d’absence backup |
| CA-47-09 | Machine d’états conforme | aucune transition hors §5.7 observée en logs d’exécution |
8. Scénarios de test (Given / When / Then)¶
-
ST-47-01 (backup quotidien réussi)
Given une base source disponible
When l’horloge atteint 03:00 Europe/Paris
Then un objetbackup_YYYYMMDD.encest stocké, journaliséSUCCESS, sans fichier clair > 300 s. -
ST-47-02 (échec chiffrement)
Given un export logique généré
When l’étape de chiffrement échoue
Then aucun upload n’est effectué et l’exécution estFAILEDavec alerte critique. -
ST-47-03 (WAL lag conforme)
Given une activité transactionnelle continue
When les segments WAL sont archivés
Then le lag P95 observé sur 24h est strictement inférieur à 5 minutes. -
ST-47-04 (watchdog déclenchement)
Given absence d’objet backup du jour à 04:00
When le watchdog s’exécute
Then une alerte est émise et une relance contrôlée unique est initiée ; si la relance échoue, une escalade critique est émise. [CORR E-17] -
ST-47-05 (rétention)
Given un backup daté de plus de 30 jours
When la politique lifecycle s’applique
Then l’objet devientEXPIREDpuisDELETEDselon transitions autorisées. -
ST-47-06 (restauration PITR)
Given un backup logique J0 et WAL intermédiaires
When une restauration est demandée à T intermédiaire
Then la base restaurée correspond à l’état attendu à T avec hash valide. -
ST-47-07 (format invalide hash)
Given un événement avechash_sha3_256hors regex
When l’événement est soumis
Then l’événement est rejeté avec code de rejetFAILED_FORMATet alerte. [CORR E-02] -
ST-47-08 (transition interdite)
Given un backup en étatDELETED
When une transition sortante est demandée
Then la transition est refusée explicitement et journalisée.
9. Hypothèses explicites¶
| ID | Hypothèse | Impact si faux |
|---|---|---|
| H-47-01 | La base de référence reste dans la classe de taille <= 50 GB. | Dépassement possible du SLA durée backup < 15 min. |
| H-47-02 | Le stockage S3 cible supporte SSE-KMS opérationnellement. | Non-conformité sur INV-47-02. |
| H-47-03 | Le système de journal append-only est disponible au moins en mode dégradé. | Perte de traçabilité ; non-conformité audit. |
| H-47-04 | Les secrets de chiffrement sont fournis par mécanisme sécurisé séparé. | Blocage des backups ou risque de secret exposé. |
| H-47-05 | La connectivité réseau vers stockage est suffisante pour les fenêtres SLA. | RPO/RTO potentiellement non tenus. |
| H-47-06 | Les hôtes participant aux backups (orchestrateur, watchdog, DB si applicable) sont synchronisés par NTP. [CORR E-15] | Risque de non-respect des seuils (03:00/04:00, TTL) et incohérences de traçabilité temporelle. |
10. Points à clarifier et contraintes techniques¶
10.1 Points à clarifier¶
| ID | Point | Donnée manquante / décision attendue |
|---|---|---|
| Q-47-01 | Fuseau horaire contractuel été/hiver | Confirmer gestion CET/CEST (Europe/Paris avec DST). |
| Q-47-02 | Stratégie exacte de lotissement journal append-only WAL | 1 événement par segment vs agrégation temporelle. |
| Q-47-03 | Critère de cohérence fonctionnelle post-restauration | Jeu minimal de tables/compteurs à comparer contractuellement. |
| Q-47-04 | Politique versions S3 lors suppression lifecycle | Clarifier suppression des versions non courantes vs conservation légale. |
| Q-47-05 | Canal et délai d’escalade d’alerte | Définir SLO de prise en charge opérationnelle. |
10.2 Contraintes techniques (stack réelle)¶
- Projet cible principal :
ProbatioVault-backend— NestJS + TypeORM + PostgreSQL. - Composant orchestration/exploitation :
ProbatioVault-infra— Terraform + Ansible + Shell. - Stockage objet compatible S3 avec SSE-KMS requis.
- Aucune exigence mobile/front (
React Native/Next.js) dans ce périmètre. - Aucune transition temporelle non identifiée : faux, des transitions temporelles existent (rétention, RPO/RTO, watchdog) et sont contractualisées en §5.8.
- Aucune borne numérique applicable : faux, bornes numériques définies en §5.8.
- Aucune contrainte inter-module applicable : vrai au sens applicatif métier (pas de guard cross-module API).
- Stratégie de migration DDL : non applicable (aucune modification de colonne existante demandée dans cette story).
10.3 Rotation de clé K_backup¶
La rotation de K_backup est supportée : la rotation ne doit pas rendre les backups historiques indéchiffrables (compatibilité de déchiffrement avec versions précédentes), et les nouveaux backups doivent utiliser la version courante de la clé. [CORR E-13]
10.4 Cycle de vie DEK¶
- Une DEK est générée pour chaque artefact chiffré (backup logique, basebackup, segment WAL), utilisée uniquement en mémoire pour l’opération, puis détruite. [CORR E-18]
- La DEK n’est jamais stockée en clair ; si elle est persistée (ex. enveloppe), elle l’est uniquement sous forme chiffrée conformément à INV-47-09. [CORR E-18]
Références¶
- Epic : Référence épique à compléter.
- JIRA :
PD-47. - Repos concernés :
ProbatioVault-backend,ProbatioVault-infra. - Documents associés : expression de besoin PD-47, exigences NF Z42-013, politiques sécurité clés/chiffrement internes.