chore: build
ci-api / test (push) Successful in 25m49s

This commit is contained in:
Xor290
2026-08-18 20:07:52 +02:00
parent aa1aa60f00
commit dfc62c305d
25 changed files with 741 additions and 58 deletions
@@ -0,0 +1,16 @@
{{- define "waf-cluster.fullname" -}}
{{ .Release.Name }}
{{- end -}}
{{- define "waf-cluster.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: Helm
{{- end -}}
{{- define "waf-cluster.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
@@ -0,0 +1,147 @@
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
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'"
@@ -0,0 +1,105 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "waf-cluster.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "waf-cluster.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "waf-cluster.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "waf-cluster.selectorLabels" . | nindent 8 }}
annotations:
# Force un rollout des pods quand la ConfigMap (nginx.conf,
# custom-rules.conf) change — sinon nginx garde l'ancienne config en
# mémoire jusqu'au prochain redémarrage manuel (les changements de
# ConfigMap seule ne déclenchent pas de rollout par défaut).
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
# Pas de readOnlyRootFilesystem ici : l'image owasp/modsecurity-crs
# écrit à plusieurs endroits internes à /etc/nginx/modsec (activation
# des plugins CRS, génération de la config paranoia/anomaly) au
# démarrage — voir les logs "Running CRS plugin activation" /
# "Running CRS rule configuration". Même posture que docker/waf
# (omnex-prod), qui tourne avec cette même image sans souci. À
# durcir plus tard une fois le mapping exact des chemins écrits
# identifié (cf. la mésaventure readOnlyRootFilesystem sur le
# frontend, deploy/chart-gestion/frontend).
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
seccompProfile:
type: RuntimeDefault
containers:
- name: waf
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
env:
- name: DISABLE_MODSEC_ENV_SUBST
value: "true"
- name: PARANOIA
value: "{{ .Values.waf.paranoia }}"
- name: ANOMALY_INBOUND
value: "{{ .Values.waf.anomalyInbound }}"
- name: ANOMALY_OUTBOUND
value: "{{ .Values.waf.anomalyOutbound }}"
- name: MODSEC_AUDIT_LOG
value: "/var/log/modsec/modsec_audit.log"
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
volumeMounts:
- name: conf
mountPath: /etc/nginx/conf.d/app.conf
subPath: app.conf
- name: conf
mountPath: /etc/nginx/modsec/custom-rules.conf
subPath: custom-rules.conf
- name: certs
mountPath: /etc/nginx/certs
readOnly: true
- name: modsec-log
mountPath: /var/log/modsec
livenessProbe:
httpGet:
path: /healthz
port: https
scheme: HTTPS
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthz
port: https
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: conf
configMap:
name: {{ include "waf-cluster.fullname" . }}-conf
- name: certs
secret:
secretName: {{ .Values.tls.secretName }}
- name: modsec-log
emptyDir: {}
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "waf-cluster.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "waf-cluster.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
# Cluster (pas Local) : même raison que Traefik (voir
# deploy/chart-gestion/traefik/values.yaml) — bare-metal multi-nœuds, tout
# nœud contacté doit pouvoir router vers le pod WAF où qu'il tourne.
externalTrafficPolicy: Cluster
selector:
{{- include "waf-cluster.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
- name: https
port: 443
targetPort: https
protocol: TCP