Aller au contenu

PD-6: Cross-Region Replication Paris → Frankfurt

Date: 15 décembre 2025 Statut: DONE User Story: PD-6 Auteur: Claude Code (Anthropic)


Vue d'ensemble

PD-6 implémente la réplication cross-région (CRR) des archives WORM de Paris vers Frankfurt pour assurer la résilience géographique et la conformité juridique.

Architecture déployée

                          AWS Cloud
┌──────────────────────────────────────────────────────────────────────┐
│                                                                      │
│   Paris (eu-west-3)                    Frankfurt (eu-central-1)     │
│   ┌─────────────────────┐              ┌─────────────────────┐      │
│   │ documents-cold-dev  │              │archives-frankfurt   │      │
│   │                     │───── CRR ───▶│         -dev        │      │
│   │ S3 Object Lock      │   15 min     │                     │      │
│   │ COMPLIANCE          │    SLA       │ S3 Object Lock      │      │
│   │                     │              │ COMPLIANCE          │      │
│   │ Standard → Glacier  │              │                     │      │
│   │ Deep Archive (J+1)  │              │ DEEP_ARCHIVE        │      │
│   └─────────────────────┘              └─────────────────────┘      │
│                                                                      │
│   ┌─────────────────────┐                                           │
│   │ audit-logs-dev      │                                           │
│   │                     │                                           │
│   │ S3 Object Lock      │                                           │
│   │ COMPLIANCE          │                                           │
│   │ Append-only logs    │                                           │
│   └─────────────────────┘                                           │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

Composants déployés

1. Buckets S3 Paris (eu-west-3)

Bucket Object Lock Lifecycle Usage
probatiovault-documents-cold-dev COMPLIANCE 50 ans J+1 → DEEP_ARCHIVE Documents archivés
probatiovault-audit-logs-dev COMPLIANCE 50 ans J+1 → DEEP_ARCHIVE Logs d'audit

Caractéristiques:

  • Object Lock COMPLIANCE mode (WORM hardware-backed)
  • Versioning activé
  • Bucket policy DenyAllDeletes
  • Access logging vers s3-access-logs

2. Bucket S3 Frankfurt (eu-central-1)

Bucket Object Lock Storage Class Usage
probatiovault-archives-frankfurt-dev COMPLIANCE 50 ans DEEP_ARCHIVE Replica CRR

Caractéristiques:

  • Object Lock COMPLIANCE répliqué depuis Paris
  • Storage class DEEP_ARCHIVE directement
  • Bucket policy DenyAllDeletes

3. Cross-Region Replication (CRR)

Paramètre Valeur
Source documents-cold-dev (Paris)
Destination archives-frankfurt-dev (Frankfurt)
Storage Class destination DEEP_ARCHIVE
Replication Time Control Enabled (15 min SLA)
Delete marker replication Disabled
Object Lock replication Enabled

Configuration Terraform

Module worm_glacier

Structure du module:

terraform/modules/worm_glacier/
├── main.tf              # Orchestrateur des sous-modules
├── variables.tf         # Variables d'entrée
├── outputs.tf           # Sorties du module
├── README.md            # Documentation
├── paris/               # Buckets S3 Paris + IAM
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   ├── s3.tf            # Buckets avec Object Lock
│   ├── iam.tf           # IAM Role probatoire
│   └── lifecycle.tf     # Transition Glacier
├── frankfurt/           # Bucket replica + CRR
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   ├── s3.tf            # Bucket Frankfurt
│   └── crr.tf           # Configuration CRR
└── monitoring/          # CloudWatch + SNS
    ├── main.tf
    ├── variables.tf
    └── outputs.tf

Appel du module

# worm-glacier-module.tf
module "worm_glacier_dev" {
  source = "./modules/worm_glacier"

  providers = {
    aws.paris     = aws.paris
    aws.frankfurt = aws.frankfurt
  }

  project_name              = var.project_name
  environment               = var.environment
  aws_region                = var.aws_region
  retention_years           = 50
  lifecycle_transition_days = 1
  backend_user_arn          = aws_iam_user.backend_user.arn
  s3_access_logs_bucket_id  = aws_s3_bucket.s3_access_logs.id

  # Cross-Region Replication
  enable_crr = true

  # Monitoring (optionnel)
  enable_monitoring = false

  tags = {
    Project     = "ProbatioVault"
    ManagedBy   = "Terraform"
    Environment = var.environment
    Compliance  = "NF-Z42-013 ISO-14641 RGPD"
    UserStory   = "PD-5 PD-6"
  }
}

Configuration CRR

# modules/worm_glacier/frankfurt/crr.tf
resource "aws_s3_bucket_replication_configuration" "paris_to_frankfurt" {
  provider = aws.paris

  bucket = var.source_bucket_id
  role   = aws_iam_role.crr_replication.arn

  rule {
    id     = "crr-paris-to-frankfurt"
    status = "Enabled"

    filter {}

    # Delete marker replication DISABLED pour WORM
    delete_marker_replication {
      status = "Disabled"
    }

    destination {
      bucket        = aws_s3_bucket.archives_frankfurt.arn
      storage_class = "DEEP_ARCHIVE"

      # Replication Time Control - SLA 15 minutes
      replication_time {
        status = "Enabled"
        time {
          minutes = 15
        }
      }

      metrics {
        status = "Enabled"
        event_threshold {
          minutes = 15
        }
      }
    }

    source_selection_criteria {
      replica_modifications {
        status = "Enabled"
      }
    }
  }
}

Test automatisé

Script test-crr.sh

Le script scripts/test-crr.sh effectue un test complet de la réplication:

# Exécution
AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=yyy ./scripts/test-crr.sh

# Résultat attendu:
# ==============================================
#   CRR Test: Paris → Frankfurt
# ==============================================
#
# [INFO] Step 1: Uploading test file to Paris...
# [OK] File uploaded to Paris
#
# [INFO] Step 2: Verifying file in Paris...
# [OK] Paris file verified
#   ETag: "abc123..."
#   Lock Mode: COMPLIANCE
#   Retention Until: 2025-12-15T12:05:00Z
#
# [INFO] Step 3: Waiting for CRR replication to Frankfurt...
# [OK] File replicated to Frankfurt!
#
# [INFO] Step 4: Verifying file in Frankfurt...
# [OK] Frankfurt file verified
#   ETag: "abc123..."
#   Storage Class: DEEP_ARCHIVE
# [OK] ETags match - data integrity verified
# [OK] Storage class is DEEP_ARCHIVE as expected
#
# [INFO] Step 5: Testing WORM protection...
# [OK] WORM protection active - object still exists
#
# ==============================================
#   CRR Test Results
# ==============================================
# [OK] All tests passed!

Tests effectués

  1. Upload Paris - Fichier avec Object Lock COMPLIANCE (5 min retention)
  2. Vérification Paris - ETag, Lock Mode, Retention
  3. Attente CRR - Polling jusqu'à réplication (max 20 min)
  4. Vérification Frankfurt - ETag, Storage Class, Object Lock
  5. Test WORM - Tentative de suppression (doit échouer)

Nettoyage

# Nettoyer les fichiers de test après expiration de la retention
./scripts/test-crr.sh --cleanup

IAM Permissions

Role CRR Replication

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SourceBucketPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:GetReplicationConfiguration",
        "s3:ListBucket"
      ],
      "Resource": "arn:aws:s3:::probatiovault-documents-cold-dev"
    },
    {
      "Sid": "SourceObjectPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:GetObjectVersionForReplication",
        "s3:GetObjectVersionAcl",
        "s3:GetObjectVersionTagging",
        "s3:GetObjectRetention",
        "s3:GetObjectLegalHold"
      ],
      "Resource": "arn:aws:s3:::probatiovault-documents-cold-dev/*"
    },
    {
      "Sid": "DestinationBucketPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:ReplicateObject",
        "s3:ReplicateDelete",
        "s3:ReplicateTags",
        "s3:ObjectOwnerOverrideToBucketOwner"
      ],
      "Resource": "arn:aws:s3:::probatiovault-archives-frankfurt-dev/*"
    },
    {
      "Sid": "ObjectLockReplication",
      "Effect": "Allow",
      "Action": [
        "s3:PutObjectRetention",
        "s3:PutObjectLegalHold"
      ],
      "Resource": "arn:aws:s3:::probatiovault-archives-frankfurt-dev/*"
    }
  ]
}

Role Probatoire (IAM)

Le role probatoire inclut un Explicit Deny sur toutes les opérations de suppression:

{
  "Sid": "ExplicitDenyDeleteOperations",
  "Effect": "Deny",
  "Action": [
    "s3:DeleteObject",
    "s3:DeleteObjectVersion",
    "s3:DeleteBucket"
  ],
  "Resource": "*"
}

Secrets HashiCorp Vault

Credentials AWS Storage

Path: kv/app/aws-storage

# Lecture
vault kv get kv/app/aws-storage

# Clés stockées:
# - access_key_id
# - secret_access_key
# - region
# - bucket_documents_cold
# - bucket_audit_logs
# - bucket_archives_frankfurt

Credentials OVH (Terraform provider)

Path: kv/ovh/api-credentials

vault kv get kv/ovh/api-credentials

Conformité

Normes respectées

Norme Exigence Implémentation
NF Z42-013 WORM hardware-backed S3 Object Lock COMPLIANCE
ISO 14641 Conservation 50 ans Retention configurée
RGPD Résilience données CRR multi-région
Code du travail L3243-4 Bulletins 50 ans Retention 50 ans

Protection WORM

  1. S3 Object Lock COMPLIANCE - AWS hardware-backed, non-bypassable
  2. Bucket Policy DenyAllDeletes - Deny explicite sur DeleteObject
  3. IAM Explicit Deny - Même root AWS ne peut supprimer
  4. CRR réplication Object Lock - Retention répliquée sur Frankfurt

Coûts estimés

Par bucket (10 TB)

Composant Coût mensuel
S3 Standard (J+0 à J+1) ~$2.30/TB
Glacier Deep Archive (après J+1) ~$0.99/TB
CRR Transfer (Paris→Frankfurt) ~$0.02/GB
Total (après transition) ~$20/mois (2x10TB)

Notes

  • CRR Transfer: Coût unique lors de l'upload initial
  • DEEP_ARCHIVE: Retrieval coûteux (~$0.02/GB + 12-48h délai)

Troubleshooting

Replication échoue

  1. Vérifier le statut sur l'objet source:
aws s3api head-object \
  --bucket probatiovault-documents-cold-dev \
  --key <object-key> \
  --query 'ReplicationStatus'
  1. Statuts possibles:
  2. PENDING - En cours
  3. COMPLETED - Réussi
  4. FAILED - Erreur (vérifier IAM permissions)
  5. REPLICA - C'est déjà une réplique

Object Lock non répliqué

Vérifier que le bucket destination a Object Lock enabled:

aws s3api get-object-lock-configuration \
  --bucket probatiovault-archives-frankfurt-dev

Test échoue avec AccessDenied

  1. Vérifier les credentials AWS
  2. Vérifier les permissions IAM:
  3. s3:PutObject
  4. s3:PutObjectRetention
  5. s3:GetObject

Changelog

v2.0.0 (2025-12-15) - PD-6

  • DONE CRR Paris → Frankfurt
  • DONE Bucket Frankfurt DEEP_ARCHIVE
  • DONE RTC 15 min SLA
  • DONE Object Lock replication
  • DONE Test automatisé test-crr.sh
  • REMOVED Glacier Vault (redondant)
  • CHANGED Module restructuré (paris/, frankfurt/, monitoring/)

v1.0.0 (2025-11-20) - PD-5

  • DONE S3 Object Lock COMPLIANCE
  • DONE Glacier Vault Lock (supprimé en v2.0.0)
  • DONE IAM Role probatoire
  • DONE Lifecycle J+1 → DEEP_ARCHIVE

Rapport généré le: 15/12/2025 User Story: PD-6 - Cross-Region Replication Sprint: Sprint 4 - Résilience et DR