chore: add helm chart and docker

This commit is contained in:
Nuxgrid
2026-07-27 21:06:11 +02:00
parent 23a4f280c6
commit 356604fba4
46 changed files with 1390 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v2
name: gestion-traefik
description: Traefik ingress + Coraza WAF (OWASP CRS) — gestion-commande
type: application
version: 0.1.0
appVersion: "3.1.0"
dependencies:
- name: traefik
version: ">=30.0.0"
repository: https://helm.traefik.io/traefik
@@ -0,0 +1,12 @@
{{- define "gestion-traefik.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "gestion-traefik.certResolver" -}}
{{- if eq .Values.tls.mode "letsencrypt" -}}
letsencrypt
{{- end }}
{{- end }}
@@ -0,0 +1,84 @@
---
# HTTP → HTTPS redirect
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: gestion-http
namespace: {{ .Release.Namespace }}
labels:
{{- include "gestion-traefik.labels" . | nindent 4 }}
spec:
entryPoints:
- web
routes:
- match: Host(`{{ .Values.domain }}`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: noop@internal
kind: TraefikService
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: {{ .Release.Namespace }}
spec:
redirectScheme:
scheme: https
permanent: true
---
# HTTPS — routes principales
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: gestion-https
namespace: {{ .Release.Namespace }}
labels:
{{- include "gestion-traefik.labels" . | nindent 4 }}
spec:
entryPoints:
- websecure
routes:
# API backend — WAF + headers + rate limit
- match: Host(`{{ .Values.domain }}`) && (PathPrefix(`/api/`) || PathPrefix(`/webhook/`))
kind: Rule
priority: 20
middlewares:
- name: waf-chain
services:
- name: {{ .Values.backend.serviceName }}
namespace: {{ .Values.servicesNamespace }}
port: {{ .Values.backend.servicePort }}
# Fichiers uploadés — WAF désactivé dans les règles Coraza pour ce path
- match: Host(`{{ .Values.domain }}`) && PathPrefix(`/uploads/`)
kind: Rule
priority: 15
middlewares:
- name: security-headers
services:
- name: {{ .Values.backend.serviceName }}
namespace: {{ .Values.servicesNamespace }}
port: {{ .Values.backend.servicePort }}
# Frontend SPA — WAF + headers + rate limit
- match: Host(`{{ .Values.domain }}`)
kind: Rule
priority: 1
middlewares:
- name: waf-chain
services:
- name: {{ .Values.frontend.serviceName }}
namespace: {{ .Values.servicesNamespace }}
port: {{ .Values.frontend.servicePort }}
tls:
{{- if eq .Values.tls.mode "letsencrypt" }}
certResolver: letsencrypt
{{- else }}
secretName: {{ .Values.tls.secretName }}
{{- end }}
domains:
- main: {{ .Values.domain }}
@@ -0,0 +1,13 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-chain
namespace: {{ .Release.Namespace }}
labels:
{{- include "gestion-traefik.labels" . | nindent 4 }}
spec:
chain:
middlewares:
- name: rate-limit
- name: coraza-waf
- name: security-headers
@@ -0,0 +1,27 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: security-headers
namespace: {{ .Release.Namespace }}
labels:
{{- include "gestion-traefik.labels" . | nindent 4 }}
spec:
headers:
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
frameDeny: true
contentTypeNosniff: true
referrerPolicy: "strict-origin-when-cross-origin"
permissionsPolicy: "geolocation=(), microphone=(), camera=(), payment=()"
customResponseHeaders:
X-XSS-Protection: "0"
Content-Security-Policy: >-
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https:;
font-src 'self' data:;
connect-src 'self' https:;
frame-ancestors 'self';
Server: ""
@@ -0,0 +1,17 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: rate-limit
namespace: {{ .Release.Namespace }}
labels:
{{- include "gestion-traefik.labels" . | nindent 4 }}
spec:
rateLimit:
average: {{ .Values.rateLimit.average }}
burst: {{ .Values.rateLimit.burst }}
period: 1m
sourceCriterion:
ipStrategy:
# depth=1 : utilise la première IP de X-Forwarded-For
# (Traefik pose lui-même ce header depuis la vraie IP TCP, non spoofable)
depth: 1
@@ -0,0 +1,61 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: coraza-waf
namespace: {{ .Release.Namespace }}
labels:
{{- include "gestion-traefik.labels" . | nindent 4 }}
spec:
plugin:
coraza-traefik:
directives:
# -------------------------------------------------------
# Moteur et body inspection
# -------------------------------------------------------
- "SecRuleEngine On"
- "SecRequestBodyAccess On"
- "SecRequestBodyLimit 104857600"
- "SecRequestBodyInMemoryLimit 131072"
- "SecResponseBodyAccess Off"
- "SecDebugLogLevel 0"
# -------------------------------------------------------
# OWASP CRS — inclus dans le plugin Coraza
# -------------------------------------------------------
- "Include @coraza.conf-recommended"
- "Include @crs-setup.conf.example"
- "Include @owasp_crs/*.conf"
# -------------------------------------------------------
# Niveau de paranoia {{ .Values.waf.paranoia }}
# -------------------------------------------------------
- "SecAction \"id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level={{ .Values.waf.paranoia }}\""
- "SecAction \"id:900001,phase:1,nolog,pass,t:none,setvar:tx.inbound_anomaly_score_threshold={{ .Values.waf.anomalyInbound }}\""
- "SecAction \"id:900002,phase:1,nolog,pass,t:none,setvar:tx.outbound_anomaly_score_threshold={{ .Values.waf.anomalyOutbound }}\""
# -------------------------------------------------------
# Suppressions CRS pour les routes légitimes
# -------------------------------------------------------
- "SecRuleRemoveById 932235"
- "SecRuleRemoveById 911100"
- "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\""
# Désactiver le WAF sur les uploads (fichiers statiques en lecture)
- "SecRule REQUEST_URI \"@beginsWith /uploads/\" \"id:1000,phase:1,pass,nolog,ctl:ruleEngine=Off\""
# -------------------------------------------------------
# IP banning automatique sur attaques détectées
# Note : en-mémoire par pod — pour un ban distribué utiliser Redis + Coraza persistence
# -------------------------------------------------------
- "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'\""
- "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'\""
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: TLSStore
metadata:
name: default
namespace: {{ .Release.Namespace }}
labels:
{{- include "gestion-traefik.labels" . | nindent 4 }}
spec:
{{- if eq .Values.tls.mode "secret" }}
defaultCertificate:
secretName: {{ .Values.tls.secretName }}
{{- end }}
defaultGeneratedCert:
resolver: {{ include "gestion-traefik.certResolver" . | default "letsencrypt" }}
domain:
main: {{ .Values.domain }}
+107
View File
@@ -0,0 +1,107 @@
# =============================================================
# Paramètres spécifiques à ce chart (IngressRoutes, Middlewares)
# =============================================================
# Namespace des services backend et frontend
servicesNamespace: gestion
domain: example.com
tls:
# "letsencrypt" | "secret"
mode: letsencrypt
email: admin@example.com
# Si mode=secret : nom du Secret TLS Kubernetes
secretName: gestion-tls
backend:
serviceName: gestion-backend
servicePort: 8080
frontend:
serviceName: gestion-frontend
servicePort: 80
waf:
paranoia: 2
anomalyInbound: 5
anomalyOutbound: 4
rateLimit:
# requêtes moyennes par seconde par IP
average: 20
burst: 50
# =============================================================
# Valeurs passées au subchart officiel traefik
# https://github.com/traefik/traefik-helm-chart
# =============================================================
traefik:
# Expose les ports 80 et 443
ports:
web:
port: 8000
expose:
default: true
redirectTo:
port: websecure
websecure:
port: 8443
expose:
default: true
tls:
enabled: true
# Logs
logs:
general:
level: INFO
access:
enabled: true
# Plugin Coraza WAF — Traefik télécharge le plugin au démarrage
# (le cluster doit avoir accès à internet ou utiliser un plugin local)
experimental:
plugins:
coraza-traefik:
moduleName: "github.com/corazawaf/coraza-traefik"
version: "v0.3.0"
# Cert resolver Let's Encrypt (ignoré si tls.mode=secret)
certResolvers:
letsencrypt:
email: admin@example.com
storage: /data/acme.json
httpChallenge:
entryPoint: web
# ACME storage persistant
persistence:
enabled: true
size: 128Mi
# Traefik lit les IngressRoutes dans tous les namespaces
providers:
kubernetesCRD:
enabled: true
allowCrossNamespace: true
# IP réelle via X-Forwarded-For (profondeur 1 = proxy direct)
# Le rate-limiter et les logs utilisent cette IP
service:
spec:
externalTrafficPolicy: Local
# Métriques Prometheus
metrics:
prometheus:
enabled: true
# Ressources
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi