xpeditis2.0/docs/mise-en-prod/07-stockage-objet-s3.md
David b22f4e0b74 docs: procedure de mise en production pas a pas
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
2026-09-07 21:40:50 +02:00

174 lines
6.0 KiB
Markdown

# 07 — Stockage objet (Hetzner Object Storage)
**Durée : environ 45 min.**
Hetzner Object Storage remplace MinIO en production. C'est un service
compatible S3 : **aucune ligne de code applicatif ne change**, seules les
variables `AWS_S3_ENDPOINT`, `AWS_REGION` et les clés diffèrent.
| | MinIO auto-hébergé | Hetzner Object Storage |
|---|---|---|
| Coût | « gratuit » + le disque + votre temps | ~6 €/To/mois |
| À sauvegarder | oui, un volume de plus | non, réplication incluse |
| CVE à suivre | oui | non |
| Console à protéger | oui | non |
| Perte si app-01 brûle | **totale** | aucune |
Le dernier point suffit à trancher : les connaissements et confirmations de
réservation sont des documents contractuels.
---
## 1. Créer les buckets
Console Hetzner → projet `xpeditis-prod` → **Object Storage** → région `fsn1`.
Créez **deux** buckets :
| Bucket | Contenu | Visibilité |
|---|---|---|
| `xpeditis-prod-documents` | PDF, connaissements, imports CSV | **Privé** |
| `xpeditis-prod-pgbackup` | Sauvegardes WAL-G | **Privé** |
> **Deux buckets, deux jeux de clés.** Si les clés applicatives fuitent (fuite
> de secret, faille d'injection), l'attaquant atteint les documents mais **pas
> les sauvegardes**. C'est précisément ce qui permet de se relever d'un
> rançongiciel. Ne mutualisez pas.
Vérifiez que les deux sont bien en **Private**. Un bucket public exposerait des
documents commerciaux nominatifs à toute personne devinant une URL.
---
## 2. Créer les clés d'accès
**Object Storage → Credentials → Generate credentials**, deux fois :
| Nom | Bucket | Destination |
|---|---|---|
| `xpeditis-app` | `xpeditis-prod-documents` | Secret Kubernetes `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` |
| `xpeditis-walg` | `xpeditis-prod-pgbackup` | `.env.data` de db-01, `WALG_ACCESS_KEY_ID` / `WALG_SECRET_ACCESS_KEY` |
Le secret n'est affiché **qu'une fois**. Copiez-le immédiatement.
> Hetzner Object Storage ne propose pas encore de politique par bucket aussi
> fine qu'AWS IAM. La séparation repose donc sur l'usage de deux jeux de clés
> distincts, chacun n'étant présent qu'à un seul endroit. Ne copiez jamais les
> clés WAL-G dans un `Secret` Kubernetes.
---
## 3. Configuration applicative
Déjà en place dans `infra/prod/k8s/base/02-configmap-backend.yaml` :
```yaml
AWS_REGION: "fsn1"
AWS_S3_ENDPOINT: "https://fsn1.your-objectstorage.com"
AWS_S3_BUCKET: "xpeditis-prod-documents"
```
Les clés vont dans le `Secret` ([06](./06-secrets-sops.md)) :
```yaml
AWS_ACCESS_KEY_ID: "<clé xpeditis-app>"
AWS_SECRET_ACCESS_KEY: "<secret xpeditis-app>"
```
Et WAL-G dans `.env.data` sur db-01 ([04](./04-noeud-donnees.md)) :
```
WALG_S3_PREFIX=s3://xpeditis-prod-pgbackup
WALG_S3_ENDPOINT=https://fsn1.your-objectstorage.com
WALG_S3_REGION=fsn1
WALG_ACCESS_KEY_ID=<clé xpeditis-walg>
WALG_SECRET_ACCESS_KEY=<secret xpeditis-walg>
```
---
## 4. Vérifier
Avec le client AWS ou `s3cmd` :
```bash
export AWS_ACCESS_KEY_ID='<clé xpeditis-app>'
export AWS_SECRET_ACCESS_KEY='<secret xpeditis-app>'
# Lister
aws --endpoint-url https://fsn1.your-objectstorage.com s3 ls s3://xpeditis-prod-documents/
# Écrire puis relire
echo "test $(date -Is)" > /tmp/test.txt
aws --endpoint-url https://fsn1.your-objectstorage.com s3 cp /tmp/test.txt s3://xpeditis-prod-documents/
aws --endpoint-url https://fsn1.your-objectstorage.com s3 cp s3://xpeditis-prod-documents/test.txt -
aws --endpoint-url https://fsn1.your-objectstorage.com s3 rm s3://xpeditis-prod-documents/test.txt
```
### Le bucket est-il vraiment privé ?
```bash
curl -sI https://fsn1.your-objectstorage.com/xpeditis-prod-documents/test.txt | head -1
```
Attendu : `HTTP/1.1 403 Forbidden`. Si vous obtenez `200`, le bucket est
public : corrigez immédiatement.
### Les clés sont-elles bien cloisonnées ?
```bash
# Les clés applicatives ne doivent PAS voir le bucket de sauvegarde
aws --endpoint-url https://fsn1.your-objectstorage.com s3 ls s3://xpeditis-prod-pgbackup/
```
Un refus est le résultat attendu. S'il réussit, vous avez utilisé les mêmes
clés pour les deux : régénérez-en un jeu séparé.
---
## 5. Durée de conservation
Une règle de cycle de vie évite que les documents s'accumulent indéfiniment —
utile pour la facture comme pour le RGPD ([16](./16-rgpd-conformite.md)).
Attention : les documents contractuels maritimes ont des obligations de
conservation longues (généralement 10 ans en France pour les pièces
comptables). **Ne posez pas de règle de suppression automatique sur
`xpeditis-prod-documents`** sans validation juridique.
Sur `xpeditis-prod-pgbackup`, en revanche, la purge est gérée par WAL-G
(`wal-g delete retain FULL 7`, dans `pg-backup.sh`). N'ajoutez pas de règle de
cycle de vie côté bucket : les deux mécanismes se contrediraient et vous
risqueriez de supprimer des WAL encore nécessaires à une restauration.
---
## 6. Et le CDN ?
Le fichier de prévisions retient Bunny.net (0,005 €/Go, ~1 €/mois en phase 1)
comme CDN si Hetzner est choisi.
**Ce n'est pas nécessaire au lancement.** Cloudflare met déjà en cache les
ressources statiques de Next.js (`/_next/static/*`) gratuitement, et le trafic
de la phase 1 (10 Go/mois selon les hypothèses) ne justifie pas un service
supplémentaire.
Reconsidérez la question quand :
- le trafic sortant dépasse 500 Go/mois ; ou
- des utilisateurs hors d'Europe se plaignent de la latence ; ou
- vous servez des documents volumineux directement depuis Object Storage.
---
## 7. Contrôle
```
[ ] Bucket xpeditis-prod-documents créé, PRIVÉ
[ ] Bucket xpeditis-prod-pgbackup créé, PRIVÉ
[ ] Deux jeux de clés DISTINCTS générés
[ ] Écriture / lecture / suppression testées sur le bucket documents
[ ] Accès HTTP anonyme : 403
[ ] Les clés applicatives ne peuvent PAS lire le bucket de sauvegarde
[ ] Clés applicatives dans le Secret Kubernetes
[ ] Clés WAL-G uniquement dans .env.data de db-01
[ ] Aucune règle de cycle de vie sur le bucket de sauvegarde
```
→ **Suite : [08 — DNS, TLS, Cloudflare](./08-dns-tls-cloudflare.md)**