Rapatrie la #118 (DAGs Airflow). Quatre conflits, tous additifs sauf un : - `.env.example` et `.gitignore` : les blocs Airflow et proxy cohabitent. - `Makefile` : `AIRFLOW` rejoint les variables de dossier, les cibles Airflow et TLS cohabitent dans `.PHONY`. - `00-vue-ensemble.md` : la ligne ML de `dev` est retenue, la ligne Infra de cette branche aussi, chacune portant sa propre mise à jour. L'interface Airflow rejoint la base et Mailpit sur `127.0.0.1` dans l'overlay : elle n'a pas d'authentification à publier derrière le proxy.
Reverse proxy
Terminaison TLS et routage de la stack déployée. Seul composant publié sur le réseau : il écoute en 80 et 443, et rien d'autre ne sort du réseau Compose.
nginx.conf: blochttp, journalisation, compression, zones de limitation de débit.conf.d/enervision.conf: redirection 80 vers 443, terminaison TLS, en-têtes de sécurité, routage.tls/: les deux fichiers que nginx lit,fullchain.pemetprivkey.pem. Ignorés par git.acme-deploy-hook.sh: recopie le résultat de certbot danstls/.
Pas de Dockerfile : l'image officielle nginx:1.28-alpine est utilisée telle quelle et la
configuration est montée en volume par docker-compose.prod.yml.
Routage
| Chemin | Destination | Remarque |
|---|---|---|
/.well-known/acme-challenge/ |
/var/www/certbot sur le port 80 |
Seul chemin non redirigé vers HTTPS |
/api/v1/auth/ |
backend:8000 |
Limitation de débit resserrée, 30 requêtes par minute |
/api/ |
backend:8000 |
Préfixe /api/v1 préservé tel quel |
/ |
frontend:3000 |
Le SPA, qui renvoie index.html sur les routes inconnues |
L'interface Airflow, celle de Mailpit et la base ne passent pas par le proxy : l'overlay les
ramène sur 127.0.0.1, donc joignables par tunnel SSH et pas autrement. Les publier derrière le
proxy demanderait une authentification propre, qui n'est pas la leur.
/docs, /redoc, /openapi.json, /static et /metrics sont montés par l'API à la racine,
pas sous /api. Ils tombent donc dans location /, donc sur le SPA : ils ne sont pas joignables
depuis l'extérieur, sans qu'aucune règle de blocage ait à être écrite. Y toucher, c'est les
exposer.
Certificat : deux modes, un seul emplacement
nginx lit toujours tls/fullchain.pem et tls/privkey.pem. Seule leur fabrication change, la
configuration n'a jamais à bouger.
Démonstration, certificat auto-signé
make tls-selfsigned PUBLIC_HOST=enervision.local
make stack-up
Le navigateur avertira d'un émetteur inconnu : c'est attendu, et c'est le seul mode exploitable tant que la machine cible n'a pas de nom de domaine public.
Let's Encrypt
Le défi HTTP-01 exige un nom de domaine résolvable publiquement et le port 80 joignable
depuis Internet. La cible documentée aujourd'hui (ssh_host = "10.0.0.10", serveur de l'école)
ne remplit ni l'une ni l'autre condition : le chemin ci-dessous est livré et documenté, il n'a
pas été exercé.
make stack-up # nginx doit tourner pour servir le défi
make tls-acme PUBLIC_HOST=enervision.fr ACME_EMAIL=ops@enervision.fr
Renouvellement, à passer en tâche planifiée sur la machine :
17 3 * * * cd /srv/enervision && make tls-renew >> /var/log/enervision-tls.log 2>&1
Pour un domaine sans port 80 entrant, le défi DNS-01 est l'alternative : elle demande un greffon certbot propre au fournisseur DNS et un jeton d'API, hors périmètre à ce jour.
Vérifier la configuration sans démarrer la stack
docker run --rm \
-v "$PWD/infra/proxy/nginx.conf:/etc/nginx/nginx.conf:ro" \
-v "$PWD/infra/proxy/conf.d:/etc/nginx/conf.d:ro" \
-v "$PWD/infra/proxy/tls:/etc/nginx/tls:ro" \
nginx:1.28-alpine nginx -t
Monter infra/proxy/ entier sur /etc/nginx échouerait : mime.types vient de l'image.