# 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: "" AWS_SECRET_ACCESS_KEY: "" ``` 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= WALG_SECRET_ACCESS_KEY= ``` --- ## 4. Vérifier Avec le client AWS ou `s3cmd` : ```bash export AWS_ACCESS_KEY_ID='' export AWS_SECRET_ACCESS_KEY='' # 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)**