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¶
- Upload Paris - Fichier avec Object Lock COMPLIANCE (5 min retention)
- Vérification Paris - ETag, Lock Mode, Retention
- Attente CRR - Polling jusqu'à réplication (max 20 min)
- Vérification Frankfurt - ETag, Storage Class, Object Lock
- Test WORM - Tentative de suppression (doit échouer)
Nettoyage¶
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
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¶
- S3 Object Lock COMPLIANCE - AWS hardware-backed, non-bypassable
- Bucket Policy DenyAllDeletes - Deny explicite sur DeleteObject
- IAM Explicit Deny - Même root AWS ne peut supprimer
- 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¶
- Vérifier le statut sur l'objet source:
aws s3api head-object \
--bucket probatiovault-documents-cold-dev \
--key <object-key> \
--query 'ReplicationStatus'
- Statuts possibles:
PENDING- En coursCOMPLETED- RéussiFAILED- Erreur (vérifier IAM permissions)REPLICA- C'est déjà une réplique
Object Lock non répliqué¶
Vérifier que le bucket destination a Object Lock enabled:
Test échoue avec AccessDenied¶
- Vérifier les credentials AWS
- Vérifier les permissions IAM:
s3:PutObjects3:PutObjectRetentions3: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