apiVersion: v1 kind: ConfigMap metadata: name: {{ include "waf-cluster.fullname" . }}-conf namespace: {{ .Release.Namespace }} labels: {{- include "waf-cluster.labels" . | nindent 4 }} data: app.conf: | server { listen 80; listen [::]:80; server_name _; return 301 https://$host$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name _; server_tokens off; ssl_certificate /etc/nginx/certs/tls.crt; ssl_certificate_key /etc/nginx/certs/tls.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; client_max_body_size 10M; client_body_buffer_size 128k; client_header_buffer_size 1k; large_client_header_buffers 4 8k; client_body_timeout 30s; client_header_timeout 30s; send_timeout 30s; keepalive_timeout 65s; access_log /dev/stdout; error_log /dev/stderr warn; modsecurity on; modsecurity_rules_file /etc/nginx/modsec/custom-rules.conf; # Terminaison TLS ici, puis nouvelle connexion TLS vers l'entryPoint # websecure du Traefik partagé (deploy/chart-gestion/traefik) : aucun # changement requis côté Traefik/IngressRoute, il continue de router # par Host comme si le client s'y connectait directement. Host # préservé pour que le routage par Host de Traefik fonctionne. location / { proxy_pass https://{{ .Values.upstream.host }}:{{ .Values.upstream.port }}; proxy_ssl_server_name on; proxy_http_version 1.1; proxy_set_header Connection ""; 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_connect_timeout 10s; proxy_send_timeout 300s; proxy_read_timeout 300s; } location = /healthz { access_log off; return 200 "ok"; add_header Content-Type text/plain; } } custom-rules.conf: | # Règles maison — le reste (SecRuleEngine, Include CRS, seuils de # paranoïa) est déjà configuré par l'entrypoint de l'image # owasp/modsecurity-crs à partir des variables d'env PARANOIA/ # ANOMALY_INBOUND/ANOMALY_OUTBOUND (voir values.yaml waf.*), même # mécanisme que docker/waf (omnex-prod). SecAuditLogParts ABIFHZ # 911100 (REQUEST-911-METHOD-ENFORCEMENT) n'autorise par défaut que GET # HEAD POST OPTIONS — bloque tout DELETE/PUT/PATCH légitime de l'API # REST. Même correctif appliqué côté docker/waf/custom-rules.conf # (omnex-prod) et côté Coraza/Traefik (deploy/chart-gestion/traefik/ # templates/middleware-waf.yaml). SecRuleRemoveById 911100 SecRule IP:BANNED "@eq 1" \ "id:100000,phase:1,deny,status:403,log,\ msg:'IP is banned'" SecRule IP:REPUTATION_SCORE "@ge 100" \ "id:100099,phase:1,deny,status:403,log,\ msg:'Critical reputation score',\ setvar:'ip.blocked=1',expirevar:'ip.blocked=86400'" SecRule TX:SQL_INJECTION_SCORE "@ge 5" \ "id:100001,phase:2,deny,status:403,log,\ msg:'SQL Injection detected',\ setvar:'ip.banned=1',expirevar:'ip.banned=172800'" SecRule TX:XSS_SCORE "@ge 5" \ "id:100010,phase:2,deny,status:403,log,\ msg:'XSS detected',\ setvar:'ip.banned=1',expirevar:'ip.banned=172800'" SecRule TX:RCE_SCORE "@ge 5" \ "id:100020,phase:2,deny,status:403,log,\ msg:'RCE detected',\ setvar:'ip.banned=1',expirevar:'ip.banned=259200'" SecRule TX:LFI_SCORE "@ge 5" \ "id:100030,phase:2,deny,status:403,log,\ msg:'LFI detected',\ setvar:'ip.banned=1',expirevar:'ip.banned=172800'" SecRule TX:INBOUND_ANOMALY_SCORE "@ge 20" \ "id:100060,phase:2,deny,status:403,log,\ msg:'Critical anomaly score',\ setvar:'ip.banned=1',expirevar:'ip.banned=172800'" # Suppressions CRS pour les routes légitimes de l'app démo (mêmes IDs # que l'ancienne config coraza-http-wasm, voir # deploy/chart-gestion/traefik/templates/middleware-waf.yaml — conservé # à titre d'historique, plus utilisé, coraza-waf retiré de la chaîne). SecRule REQUEST_URI "@streq /api/v2/admin/protected/products" \ "id:399001,phase:2,nolog,pass,ctl:ruleRemoveById=932235" SecRule REQUEST_URI "@streq /api/v2/admin/protected/products" \ "id:399002,phase:2,nolog,pass,ctl:ruleRemoveById=920120,ctl:ruleRemoveById=920121" SecRule REQUEST_URI "@streq /api/v1/panier/remove" \ "id:399010,phase:1,nolog,pass,ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350" SecRule REQUEST_URI "@streq /api/v1/panier/clear" \ "id:399011,phase:1,nolog,pass,ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350" # Fichiers uploadés — WAF désactivé (lecture seule, contenu déjà validé # à l'upload). SecRule REQUEST_URI "@beginsWith /uploads/" \ "id:1000,phase:1,pass,nolog,ctl:ruleEngine=Off" # Rate limit basique par IP (indépendant du RateLimit du control-plane # sur /auth/login, et du middleware rate-limit de Traefik en aval). SecAction \ "id:400161,phase:1,nolog,pass,\ setvar:'ip.request_window_1sec=+1',\ expirevar:'ip.request_window_1sec=1'" SecRule IP:REQUEST_WINDOW_1SEC "@gt 20" \ "id:400160,phase:1,deny,status:429,log,\ msg:'Too many requests'"