chore: add helm chart and docker
This commit is contained in:
@@ -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 }}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user