# 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; } }