Files
ENI-projet-piscine/infra/proxy/conf.d/enervision.conf
Johan LEROY bc75528616 fix(infra): lève les points de revue du reverse proxy
Compose interpole tout le fichier avant n'importe quelle sous-commande : la garde
`${PUBLIC_HOST:?}` de l'overlay cassait `stack-down` et `stack-logs` autant que le
démarrage. La valeur retombe sur `enervision.local`, et `stack-up` vérifie à la place
que le certificat présent couvre l'hôte demandé, ce qui est la condition réelle à tenir.

La CSP `script-src 'self'` bloquait le gestionnaire `onload` que l'inlining du CSS
critique d'Angular pose sur la feuille de styles : l'application se serait affichée sans
style derrière le proxy. `inlineCritical` passe à faux, le build de production ne produit
plus aucun script en ligne.

La zone de limitation resserrée ne couvre plus que les routes qui vérifient un secret.
Derrière le NAT de l'école, où une seule adresse porte toute la promotion, `/auth/me` et
`/auth/refresh` y auraient produit des 429 en usage normal.

Enfin `certbot/certbot` est épinglé en v5.8.0 pour que Dependabot puisse le suivre, le
proxy attend une API saine plutôt que démarrée, et la redirection vers `$host` est actée
comme risque accepté : figer un nom canonique couperait l'accès par adresse IP, seule
voie ouverte sur la machine cible.
2026-09-21 12:11:32 +02:00

70 lines
2.8 KiB
Plaintext

# Piège : `X-Forwarded-For` se construit avec `$proxy_add_x_forwarded_for`, qui ajoute l'IP
# réelle en fin de chaîne. `get_client_ip()` (apps/backend/app/api/deps.py) ne lit que le
# dernier élément : toute autre forme rend la limitation de débit par IP globale, donc le
# déni de service auto-infligé que ce code cherche précisément à éviter.
# Piège : un nom d'hôte littéral dans `proxy_pass` fige l'IP du conteneur au démarrage de
# nginx, et recréer `backend` seul donnerait des 502 jusqu'au rechargement du proxy. D'où la
# variable et le résolveur interne de Docker : la résolution redevient dynamique.
# Pourquoi : la redirection 80 vers 443 conserve `$host` plutôt qu'un nom canonique, faute de
# quoi l'accès par IP cesserait de fonctionner sur la cible. Risque acté dans l'ADR 0007.
server {
listen 80 default_server;
server_name _;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl default_server;
http2 on;
server_name _;
resolver 127.0.0.11 valid=10s ipv6=off;
ssl_certificate /etc/nginx/tls/fullchain.pem;
ssl_certificate_key /etc/nginx/tls/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# L'application refuse délibérément de poser ces deux en-têtes, verrouillé par
# tests/api/test_hardening.py. Ils appartiennent au terminateur TLS, c'est-à-dire ici.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
# Piège : la zone `auth` ne couvre que les routes qui vérifient un secret. Derrière le NAT de
# l'école, `/auth/me` et `/auth/refresh` y produiraient des 429 à chaque chargement de page.
location ~ ^/api/v1/auth/(login|password|forgot-password|reset-password)$ {
limit_req zone=auth burst=20 nodelay;
set $cible_api http://backend:8000;
proxy_pass $cible_api$request_uri;
}
location /api/ {
limit_req zone=api burst=40 nodelay;
set $cible_api http://backend:8000;
proxy_pass $cible_api$request_uri;
}
location / {
set $cible_web http://frontend:3000;
proxy_pass $cible_web$request_uri;
}
}