ProbatioVault - Architecture Stockage Hybride¶
PD-4: OVH S3 (HOT) + AWS Glacier Deep Archive (COLD + COMPLIANCE)
1. Vue d'Ensemble¶
1.1 Objectifs Architecture¶
┌──────────────────────────────────────────────────────────────┐
│ Architecture Hybride Multi-Cloud │
│ │
│ OVH S3 (HOT) ←→ AWS Glacier (COLD) │
│ ├─ Accès rapide < 100ms ├─ Object Lock COMPLIANCE│
│ ├─ Coût modéré ├─ Durabilité 11 9's │
│ └─ Région EU (Gravelines) └─ Coût minimal │
└──────────────────────────────────────────────────────────────┘
Objectifs: 1. ✅ Performance: Accès rapide OVH S3 (< 100ms latence) 2. ✅ Conformité: Object Lock COMPLIANCE AWS (WORM hardware-backed) 3. ✅ Coût: Optimisation Glacier Deep Archive (~$1/TB/mois) 4. ✅ Disponibilité: 99.99% SLA OVH + 99.999999999% AWS 5. ✅ Souveraineté: Données EU (Gravelines + Paris)
1.2 Décision Architecture: Pourquoi Hybride?¶
Pourquoi pas OVH uniquement? - ❌ OVH ne supporte PAS Object Lock S3 natif - ❌ Impossible d'implémenter WORM hardware-backed sur OVH - ❌ NF Z42-013 requiert immutabilité garantie
Pourquoi pas AWS uniquement? - ⚠️ Latence accès Glacier (restore 12-48h) - ⚠️ Coût S3 Standard élevé pour stockage HOT ($23/TB/mois) - ⚠️ Verrouillage fournisseur (vendor lock-in)
Solution hybride:
Upload → OVH S3 (HOT, fast access)
→ Worker async → AWS Glacier (COLD, compliance)
→ Best of both worlds
2. Architecture Détaillée¶
2.1 Flux d'Upload Document¶
┌─────────┐
│ Client │ (React Native app)
│ Mobile │
└────┬────┘
│ 1. Upload fichier
│ + metadata (type, tags)
▼
┌──────────────────┐
│ Backend NestJS │
│ │
│ 2. Chiffrement │───────────┐
│ AES-256-GCM │ │ K_doc (unique par document)
│ K_doc derived │ │ dérivée de K_master_user
│ from K_master │◄──────────┘ (PD-35: Key Wrapping)
└────┬─────────────┘
│ 3. Upload OVH S3 HOT
│ (documents-hot bucket)
▼
┌──────────────────────────────┐
│ OVH Object Storage (GRA) │
│ │
│ Bucket: documents-hot │
│ ├─ Versioning: Enabled │
│ ├─ Encryption: AES-256 (auto)│
│ ├─ Latency: < 100ms │
│ └─ Cost: ~$10/TB/mois │
└────┬─────────────────────────┘
│ 4. Event → Queue (PD-6)
│ (async replication)
▼
┌──────────────────────────────┐
│ Worker Replication (PD-6) │
│ (Background job) │
│ │
│ 5. Download from OVH │
│ 6. Upload to AWS Glacier │
│ + Object Lock COMPLIANCE │
└────┬─────────────────────────┘
│ 7. Upload AWS S3
│ + Object Lock metadata
▼
┌────────────────────────────────────────┐
│ AWS S3 Glacier Deep Archive (eu-west-3)│
│ │
│ Bucket: documents-cold │
│ ├─ Object Lock: COMPLIANCE (50 ans) │
│ ├─ Versioning: Enabled (requis) │
│ ├─ Encryption: AES-256 (S3 Managed) │
│ ├─ Lifecycle: DEEP_ARCHIVE (90 jours) │
│ ├─ Durability: 99.999999999% │
│ └─ Cost: ~$1/TB/mois │
└────────────────────────────────────────┘
2.2 Flux de Lecture Document¶
┌─────────┐
│ Client │
└────┬────┘
│ 1. GET /documents/:id
▼
┌──────────────────┐
│ Backend NestJS │
│ │
│ 2. Check cache │──NO─┐
│ (Redis) │ │
└────┬─────────────┘ │
│ YES │
│ 3a. Return │
│ from cache │
│ │
│◄──────────────────┘
│ 3b. Download OVH S3
│ (HOT storage)
▼
┌──────────────────────────────┐
│ OVH Object Storage │
│ │
│ 4. GetObject │
│ (< 100ms latency) │
└────┬─────────────────────────┘
│ 5. Decrypt AES-256-GCM
│ with K_doc
▼
┌──────────────────┐
│ Client │
│ (fichier clair) │
└──────────────────┘
Note: Si document pas dans OVH (purge manuelle),
fallback sur AWS Glacier restore (12-48h)
2.3 Flux Audit Logs (Append-Only)¶
┌──────────────────┐
│ Backend NestJS │
│ │
│ Event: │
│ - User login │
│ - Document access│
│ - Key rotation │
│ - Device revoked │
└────┬─────────────┘
│ 1. Log event
│ (structured JSON)
▼
┌────────────────────────────────────────┐
│ AWS S3 Audit Logs (eu-west-3) │
│ │
│ Bucket: audit-logs │
│ ├─ Object Lock: COMPLIANCE (50 ans) │
│ ├─ Storage Class: S3 STANDARD │
│ │ (pas de transition Glacier pour │
│ │ accès rapide si audit externe) │
│ ├─ Bucket Policy: DENY DELETE │
│ ├─ IAM Policy: Write-Only (append) │
│ └─ Versioning: Enabled │
└────────────────────────────────────────┘
Security:
- Backend peut uniquement PutObject (append)
- Lecture logs réservée aux admins (IAM tag Role=Admin)
- Suppression IMPOSSIBLE avant 50 ans (Object Lock COMPLIANCE)
3. Buckets et Configuration¶
3.1 Buckets OVH S3 (HOT)¶
| Bucket | Usage | Versioning | Lifecycle | Encryption |
|---|---|---|---|---|
documents-hot | Documents utilisateurs (accès rapide) | ✅ Enabled | ❌ None (kept forever) | ✅ AES-256 auto |
backups | Backups base de données | ❌ Disabled | ✅ Expire 30 jours | ✅ AES-256 auto |
temp-uploads | Uploads temporaires (staging) | ❌ Disabled | ✅ Expire 7 jours | ✅ AES-256 auto |
Région: GRA (Gravelines, France) Endpoint: https://s3.gra.cloud.ovh.net Credentials: S3 Access Key / Secret Key (OVH Cloud Project)
3.2 Buckets AWS S3/Glacier (COLD)¶
| Bucket | Usage | Object Lock | Storage Class | Lifecycle |
|---|---|---|---|---|
documents-cold | Documents WORM (compliance) | ✅ COMPLIANCE 50 ans | S3 Standard → Glacier Deep Archive (90j) | ✅ Auto transition |
audit-logs | Audit logs append-only | ✅ COMPLIANCE 50 ans | S3 Standard (no transition) | ✅ Expire 50 ans |
s3-access-logs | Logs d'accès S3 (metadata) | ❌ Disabled | S3 Standard | ✅ Expire 90 jours |
Région: eu-west-3 (Paris, France) Endpoint: https://s3.eu-west-3.amazonaws.com Credentials: IAM User probatiovault-backend-dev (Access Key / Secret Key)
4. Sécurité et Isolation¶
4.1 Chiffrement Multi-Couches¶
┌──────────────────────────────────────────────────────────┐
│ Fichier Original (document.pdf) │
└──────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Couche 1: Chiffrement Client (Backend) │
│ ├─ Algorithme: AES-256-GCM │
│ ├─ Clé: K_doc (dérivée de K_master_user) │
│ ├─ IV: Unique par document (12 bytes random) │
│ └─ Output: document.pdf.enc (chiffré) │
└──────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Couche 2: TLS 1.2+ (Transport) │
│ ├─ Cipher: TLS_AES_256_GCM_SHA384 │
│ ├─ Certificats: Let's Encrypt (auto-renew) │
│ └─ HSTS: Activé (force HTTPS) │
└──────────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Couche 3: Chiffrement au repos (S3/Glacier) │
│ ├─ OVH: AES-256 (automatic, S3 managed) │
│ ├─ AWS: AES-256 (S3 Managed Keys, SSE-S3) │
│ └─ Bucket Key: Activé (optimisation coûts KMS) │
└──────────────────────────────────────────────────────────┘
Résultat: Triple chiffrement (AES-256 client + TLS + AES-256 serveur)
4.2 Isolation Réseau¶
┌─────────────────────────────────────────────────────────┐
│ Internet Public │
└────────────────┬────────────────────────────────────────┘
│
│ HTTPS uniquement (TLS 1.2+)
│
┌────────┴────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ OVH S3 │ │ AWS S3 │
│ (Public) │ │ (Public) │
│ │ │ │
│ Bucket │ │ Bucket │
│ Policy: │ │ Policy: │
│ - DENY HTTP │ │ - DENY HTTP │
│ - Private │ │ - DENY │
│ (no public │ │ Public │
│ access) │ │ Access │
└──────────────┘ └──────────────┘
▲ ▲
│ │
│ Credentials: │ Credentials:
│ OVH S3 Keys │ AWS IAM User
│ │
└────────┬────────┘
│
┌────────┴────────┐
│ │
│ Backend NestJS │
│ (VPS OVH) │
│ │
│ Security: │
│ - Firewall UFW │
│ - SSH Key only │
│ - Fail2ban │
└─────────────────┘
4.3 IAM Policies (Least Privilege)¶
Backend AWS IAM User:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DocumentsColdReadWrite",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectRetention",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::probatiovault-documents-cold-dev",
"arn:aws:s3:::probatiovault-documents-cold-dev/*"
]
},
{
"Sid": "AuditLogsWriteOnly",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectRetention"
],
"Resource": [
"arn:aws:s3:::probatiovault-audit-logs-dev/*"
]
}
]
}
Permissions NON accordées (sécurité): - ❌ s3:DeleteObject (impossible de supprimer) - ❌ s3:DeleteBucket (impossible de supprimer bucket) - ❌ s3:PutBucketVersioning (impossible de désactiver versioning) - ❌ s3:PutObjectLockConfiguration (impossible de modifier Object Lock) - ❌ s3:BypassGovernanceRetention (impossible de bypass retention)
5. Scalabilité et Performance¶
5.1 Capacité¶
| Métrique | OVH S3 | AWS S3/Glacier |
|---|---|---|
| Stockage max | Illimité | Illimité |
| Fichier max | 5 TB | 5 TB |
| Débit upload | 100 MB/s | 100 MB/s |
| Débit download | 100 MB/s | 12-48h restore (Glacier) |
| IOPS | 3500 IOPS | N/A (archive) |
5.2 Latence¶
| Opération | OVH S3 HOT | AWS S3 COLD |
|---|---|---|
| Upload | < 100 ms | < 200 ms |
| Download | < 100 ms | 12-48h restore |
| List Objects | < 50 ms | < 50 ms |
| Head Object | < 20 ms | < 20 ms |
5.3 Optimisations Performance¶
Multipart Upload (fichiers > 100 MB):
// Backend upload service
async uploadLargeDocument(file: Buffer, key: string) {
const partSize = 100 * 1024 * 1024; // 100 MB parts
// Initiate multipart upload
const uploadId = await this.s3Client.createMultipartUpload({
Bucket: 'documents-hot',
Key: key,
});
// Upload parts en parallèle (max 5 threads)
const parts = await Promise.all(
chunks(file, partSize).map((chunk, index) =>
this.s3Client.uploadPart({
Bucket: 'documents-hot',
Key: key,
PartNumber: index + 1,
UploadId: uploadId,
Body: chunk,
})
)
);
// Complete multipart upload
await this.s3Client.completeMultipartUpload({
Bucket: 'documents-hot',
Key: key,
UploadId: uploadId,
MultipartUpload: { Parts: parts },
});
}
Cache Redis (lecture fréquente):
async getDocument(documentId: string) {
// 1. Check cache Redis
const cached = await this.redis.get(`doc:${documentId}`);
if (cached) {
return Buffer.from(cached, 'base64');
}
// 2. Download from OVH S3
const s3Object = await this.s3Client.getObject({
Bucket: 'documents-hot',
Key: documentId,
});
// 3. Cache 1 hour (documents chiffrés)
await this.redis.setex(
`doc:${documentId}`,
3600,
s3Object.Body.toString('base64')
);
return s3Object.Body;
}
6. Coûts Estimés¶
6.1 Coûts OVH S3 (HOT)¶
| Ressource | Volume | Prix unitaire | Coût mensuel |
|---|---|---|---|
| Stockage | 1 TB | ~€0.01/GB/mois | €10 |
| Requêtes PUT | 100k | €0.005/1k req | €0.50 |
| Requêtes GET | 1M | €0.004/10k req | €0.40 |
| Transfert sortant | 500 GB | €0.01/GB | €5 |
| TOTAL | ~€16/mois |
6.2 Coûts AWS S3/Glacier (COLD)¶
| Ressource | Volume | Prix unitaire | Coût mensuel |
|---|---|---|---|
| S3 Standard (90j) | 1 TB | $0.023/GB/mois | $23 |
| Glacier Deep Archive | 1 TB | $0.00099/GB/mois | $1 |
| Object Lock | 1M objets | Inclus | $0 |
| Requêtes PUT | 100k | $0.005/1k | $0.50 |
| Restore Glacier | 10 GB/mois | $0.02/GB | $0.20 |
| Transfert sortant | 50 GB | $0.09/GB | $4.50 |
| TOTAL (après 90j) | ~$6/mois |
6.3 Économies Architecture Hybride¶
Scénario: 10 TB stockés, 90% archives (accès rare)
| Solution | Coût mensuel | Économie |
|---|---|---|
| AWS S3 Standard uniquement | $230 | - |
| OVH S3 uniquement (❌ pas Object Lock) | $100 | - |
| Hybride OVH HOT (1 TB) + AWS COLD (9 TB) | $16 + $9 = $25 | -89% |
7. Monitoring et Observabilité¶
7.1 Métriques CloudWatch (AWS)¶
Métriques S3:
├─ BucketSizeBytes (taille bucket)
├─ NumberOfObjects (nombre objets)
├─ 4xxErrors (erreurs client)
├─ 5xxErrors (erreurs serveur)
└─ AllRequests (total requêtes)
Alarmes configurées:
├─ Object Lock violations (tentatives suppression)
├─ Glacier transition errors (échecs lifecycle)
└─ Insecure access (requêtes non-HTTPS)
7.2 Logs d'Accès S3¶
Localisation: s3://probatiovault-s3-access-logs-dev/
Format:
documents-cold-logs/2024/01/19/access-log-*.txt
audit-logs-logs/2024/01/19/access-log-*.txt
Rétention: 90 jours (lifecycle auto-expire)
Champs loggés:
- Bucket, Key (ressource accédée)
- Operation (GET, PUT, DELETE)
- RequestDateTime, ResponseStatus
- BytesSent, BytesReceived
- UserAgent, IPAddress
7.3 Dashboard Grafana (Recommandé PD-XX)¶
┌─────────────────────────────────────────────────┐
│ ProbatioVault Storage Dashboard │
├─────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ OVH Hot │ │ AWS Cold │ │
│ │ 1.2 TB │ │ 8.9 TB │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Upload Rate (last 24h) │ │
│ │ ▁▂▃▅▄▃▂▁▂▃▅▇▆▅▄▃▂▁ │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Object Lock Compliance Status │ │
│ │ ✅ 1,234,567 objects locked │ │
│ │ ⚠️ 123 objects pending lock │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Storage Cost (monthly) │ │
│ │ OVH: $16 | AWS: $9 | Total: $25 │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
8. Disaster Recovery¶
8.1 Scénarios de Panne¶
Panne OVH S3:
Impact: Uploads impossibles, lectures impossibles
Durée: SLA 99.9% → max 43 min/mois
Mitigation:
1. Fallback automatique vers AWS S3 (lecture seulement)
2. Glacier restore (12-48h délai)
3. Queue uploads (retry automatique quand OVH up)
Panne AWS S3:
Impact: Compliance logs impossibles, réplication impossible
Durée: SLA 99.99% → max 4 min/mois
Mitigation:
1. Logs temporaires en base PostgreSQL
2. Sync vers AWS quand service up
3. Pas d'impact utilisateur (lectures depuis OVH HOT)
8.2 Backup et Restore¶
Base de données (metadata):
# Backup quotidien PostgreSQL → OVH S3
pg_dump probatiovault | gzip > backup-$(date +%Y%m%d).sql.gz
s3cmd put backup-*.sql.gz s3://probatiovault-backups-dev/
# Lifecycle: Expire après 30 jours
Documents (fichiers):
Réplication:
OVH S3 → AWS Glacier = Backup automatique
99.999999999% durabilité AWS = Risque perte négligeable
Restore:
1. Metadata depuis backup PostgreSQL
2. Fichiers depuis AWS Glacier (restore 12-48h)
9. Migration et Évolution¶
9.1 Migration Future vers Multi-Region¶
Phase 1 (actuel):
Phase 2 (future):
OVH GRA (Gravelines) + OVH UK (Londres)
AWS eu-west-3 (Paris) + AWS eu-west-2 (Londres)
→ Geo-replication automatique
→ Haute disponibilité multi-région
9.2 Évolution vers S3 Intelligent-Tiering¶
Actuel: Transition fixe (90 jours → Glacier Deep Archive)
Future: S3 Intelligent-Tiering
Automatic cost optimization:
- Frequent Access Tier (< 30 jours)
- Infrequent Access Tier (30-90 jours)
- Archive Instant Access Tier (90-180 jours)
- Deep Archive Tier (> 180 jours)
Compatible avec Object Lock COMPLIANCE
Document rédigé le: 2024-01-19 Version: 1.0 Auteur: Claude Code (PD-4) Prochaine révision: 2024-04-19 (trimestrielle)