# infra/prod — Production Hetzner Tout ce qui est nécessaire pour déployer et exploiter Xpeditis en production. > **La procédure pas à pas est dans [`docs/mise-en-prod/`](../../docs/mise-en-prod/README.md).** > Ce README décrit *ce que contient le dossier* ; la doc décrit *quoi faire, dans quel ordre*. --- ## Architecture cible ``` Internet | Cloudflare (DNS + WAF + anti-DDoS) | seules les IP Cloudflare passent le firewall v ┌─────────────────────────────────────────────┐ │ app-01 CPX41 8 vCPU / 16 Go fsn1 │ │ │ │ k3s (mono-nœud) │ │ Traefik ──┬── api.xpeditis.com → backend │ │ ├── app / www / apex → frontend│ │ └── grafana.xpeditis.com │ │ backend NestJS x2 (HPA 2→4) │ │ frontend Next.js x2 │ │ log-exporter │ │ monitoring : Loki, Promtail, Prometheus, │ │ Alertmanager, Grafana │ └──────────────────┬──────────────────────────┘ │ réseau privé 10.10.1.0/24 │ TLS obligatoire (hostssl) v ┌─────────────────────────────────────────────┐ │ db-01 CPX31 4 vCPU / 8 Go fsn1 │ │ │ │ PostgreSQL 15 + WAL-G (Docker Compose) │ │ Redis 7 │ │ postgres-exporter │ │ volume dédié 50 Go │ └──────────────────┬──────────────────────────┘ │ ┌───────────┴────────────┐ v v Hetzner Object Storage Hetzner Storage Box (documents + WAL-G) (dumps chiffrés age) ``` **Pourquoi la base hors de Kubernetes** — PostgreSQL en StatefulSet ajoute des volumes persistants, un ordre de démarrage et des montées de version délicates, sans aucun gain à cette échelle. Hors cluster, les sauvegardes, la restauration à un instant T et les tests de restauration sont triviaux, et le nœud applicatif reste entièrement reconstructible sans toucher aux données. --- ## Contenu du dossier | Chemin | Rôle | |---|---| | `terraform/` | Serveurs, réseau privé, firewalls, volume, clé SSH. La seule source de vérité de l'infrastructure. | | `scripts/00-bootstrap-common.sh` | Durcissement système commun (SSH, UFW, fail2ban, auditd, sysctl, mises à jour auto). | | `scripts/01-setup-data-node.sh` | Installe db-01 : volume, Docker, certificat TLS Postgres, timers de sauvegarde. | | `scripts/02-setup-k3s-server.sh` | Installe k3s durci (secrets chiffrés au repos, audit API, Traefik configuré). | | `scripts/03-install-cluster-addons.sh` | cert-manager, namespaces, accès registre, ClusterIssuer. | | `scripts/secrets-apply.sh` | Déchiffre SOPS → applique sur le cluster, sans passer par le disque. | | `scripts/harden-seed-data.sh` | Secours et audit : neutralise les comptes de démonstration (`admin@xpeditis.com` / `Password123!`) sur une base migrée avant l'ajout de la garde `NODE_ENV`. Le cas nominal est traité par les migrations. | | `scripts/deploy.sh` | Déploiement complet : migrations → images → attente → tests → retour arrière. | | `scripts/ssh-deploy-wrapper.sh` | Restreint la clé SSH de la CI à quatre commandes. Une clé volée ne donne pas un shell. | | `scripts/deploy-monitoring.sh` | Pile d'observabilité. | | `scripts/smoke-test.sh` | Vérifie ce qu'un utilisateur constate réellement, de l'extérieur. | | `scripts/preflight-check.sh` | Contrôle go / no-go avant ouverture au public. | | `scripts/refresh-cloudflare-ips.sh` | Met à jour les rangs IP Cloudflare (firewall + Traefik). | | `data-node/` | Docker Compose, `postgresql.conf`, `pg_hba.conf`, `redis.conf`, sauvegardes WAL-G. | | `k8s/base/` | Namespaces, quotas, ConfigMap, gabarit de secrets, déploiements, Ingress, middlewares, politiques réseau, certificats. | | `k8s/cluster/` | ClusterIssuer Let's Encrypt (DNS-01 Cloudflare). | | `k8s/monitoring/` | Loki, Promtail, Prometheus, Alertmanager, Grafana. | | `env/` | Gabarits de variables d'environnement et liste des secrets GitHub. | | `cloudflare/` | Enregistrements DNS et règles WAF à créer côté Cloudflare. | | `Makefile` | Raccourcis d'exploitation (`make help`). | --- ## Démarrage rapide ```bash cd infra/prod make help # Infrastructure cp terraform/terraform.tfvars.example terraform/terraform.tfvars $EDITOR terraform/terraform.tfvars make tf-init && make tf-plan && make tf-apply make tf-output # IP + enregistrements DNS à créer # ... provisioning des serveurs : voir docs/mise-en-prod/03 et 04 ... # Secrets cp k8s/base/03-secrets.template.yaml /tmp/secrets.yaml $EDITOR /tmp/secrets.yaml sops -e /tmp/secrets.yaml > k8s/base/03-secrets.sops.yaml && shred -u /tmp/secrets.yaml make secrets-apply # Déploiement make deploy TAG=prod-a1b2c3d make deploy-monitoring # Avant d'ouvrir au public make preflight ``` --- ## Règles de sécurité non négociables PostgreSQL : avant de déployer les correctifs TLS, renseigner `DATABASE_SSL_CA` dans le Secret backend chiffré SOPS avec le contenu PEM du certificat **public** `/var/lib/xpeditis/certs/server.crt` de db-01, récupéré par un canal d’administration authentifié. Ne jamais copier `server.key`. Le gabarit de secrets contient le champ à renseigner. Le backend et le Job de migration utilisent ce même Secret. Conserver `DATABASE_SSL=true` et un `DATABASE_HOST` présent dans les SAN du certificat (IP privée ou nom DNS). Les certificats non approuvés et les noms incorrects sont désormais refusés ; le réseau privé ne remplace pas ce contrôle. Lors d’un renouvellement, distribuer le nouveau certificat de confiance avant la bascule serveur et redémarrer les clients concernés. Ne pas désactiver la vérification TLS pour contourner une erreur de certificat. 1. **Aucun secret en clair dans Git.** Uniquement des fichiers `*.sops.yaml` chiffrés avec age. `make secrets-check` refuse le contraire. 2. **La base de données n'est jamais joignable depuis Internet.** Réseau privé, bind explicite sur l'IP privée, UFW, et `pg_hba` en `hostssl` seulement. 3. **Personne ne contourne Cloudflare.** Le firewall Hetzner n'accepte 80/443 que depuis les rangs Cloudflare. 4. **SSH et l'API Kubernetes ne sont ouverts qu'à vos IP d'administration.** Jamais `0.0.0.0/0` — Terraform refuse cette valeur. 5. **Une sauvegarde non restaurée n'est pas une sauvegarde.** Le test de restauration tourne toutes les semaines et doit être passé avant l'ouverture. 6. **Les migrations passent par un Job**, jamais en concurrence entre replicas. 7. **L'image frontend est reconstruite pour la production**, jamais promue depuis la preprod : `NEXT_PUBLIC_API_URL` est figée au build. 8. **Aucun mot de passe d'administrateur n'est stocké nulle part.** `SeedTestUsers` ne s'exécute plus en production, une migration de secours neutralise ses comptes s'ils existent, et le premier administrateur est créé sans mot de passe utilisable — vous définissez le vôtre via « mot de passe oublié ». --- ## Coûts (référence : `Xpeditis_Previsions_Couts.xlsx`, feuille « Hetzner ») | Poste | € HT / mois | |---|---| | app-01 CPX41 | 29 | | db-01 CPX31 | 15 | | Volume 50 Go | 2,40 | | Sauvegardes Hetzner (20 %) | 8,80 | | Storage Box BX11 (1 To) | 3,90 | | Object Storage (documents + WAL-G, < 1 To) | ~6 | | IPv4 supplémentaire | 1 | | Cloudflare Free | 0 | | **Total infrastructure** | **≈ 66 €** | Les services tiers (Brevo, Stripe, Sentry, Pappers, assurance) s'ajoutent : voir la feuille « Synthèse comparatif » du fichier de prévisions.