Aller au contenu

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

  1. Déclenchement planifié quotidien à 03:00 Europe/Paris.
  2. Production d’un dump logique compressé.
  3. Chiffrement applicatif immédiat (AES-256-GCM).
  4. Suppression de l’artefact en clair avant 300 s après fin d’export.
  5. Upload vers bucket dédié avec SSE-KMS actif.
  6. Écriture d’un événement append-only backup_type=pg_dump.
  7. Vérification de présence de l’objet attendu par watchdog avant 04:00 Europe/Paris.

5.3 Flux nominal F2 — Archivage WAL continu

  1. Capture continue des segments WAL.
  2. Calcul du hash SHA3-256 par segment WAL avant upload, et journalisation associée. [CORR E-12]
  3. Chiffrement applicatif de chaque segment avant persistance distante.
  4. Upload vers préfixe wal/.
  5. Journalisation append-only par segment ou lot contractuel.
  6. Exigence de dérive maximale (lag) de livraison : P95 < 5 min.

5.4 Flux nominal F3 — Sauvegarde physique hebdomadaire

  1. Déclenchement hebdomadaire le dimanche à 02:00 Europe/Paris.
  2. Chiffrement applicatif puis upload SSE-KMS.
  3. Journalisation append-only backup_type=basebackup.
  4. Conservation selon même politique lifecycle (30 jours).

5.5 Flux nominal F4 — Restauration de validation (staging)

  1. Sélection d’un point de restauration cible.
  2. Restauration base + rejouage WAL jusqu’au point cible.
  3. Vérification d’intégrité hash SHA3-256 des artefacts restaurés.
  4. Validation de cohérence schéma + données métier attendues.
  5. 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 nouveau backup_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 -> SCHEDULED conserve 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 objet backup_YYYYMMDD.enc est 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 est FAILED avec 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 devient EXPIRED puis DELETED selon 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 avec hash_sha3_256 hors regex
    When l’événement est soumis
    Then l’événement est rejeté avec code de rejet FAILED_FORMAT et alerte. [CORR E-02]

  • ST-47-08 (transition interdite)
    Given un backup en état DELETED
    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-backendNestJS + TypeORM + PostgreSQL.
  • Composant orchestration/exploitation : ProbatioVault-infraTerraform + 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.