chore: add helm chart and docker
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: gestion-backend
|
||||
description: Go API backend — gestion-commande
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.0.0"
|
||||
@@ -0,0 +1,47 @@
|
||||
# =========================================================
|
||||
# Stage 1: Build Go
|
||||
# =========================================================
|
||||
FROM golang:1.24-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git ca-certificates tzdata gcc musl-dev
|
||||
|
||||
COPY backend/gestion/go.mod backend/gestion/go.sum ./
|
||||
|
||||
ENV GOPROXY=https://proxy.golang.org,direct
|
||||
ENV GOSUMDB=sum.golang.org
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY backend/gestion/ .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-ldflags="-w -s -X main.Version=1.0.0 -X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
-o /app/server .
|
||||
|
||||
# =========================================================
|
||||
# Stage 2: Runtime
|
||||
# =========================================================
|
||||
FROM alpine:latest AS runtime
|
||||
|
||||
RUN apk --no-cache add ca-certificates tzdata wget
|
||||
|
||||
RUN addgroup -g 101 app && adduser -u 101 -S app -G app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/server .
|
||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
|
||||
COPY helm/backend/entrypoint.sh .
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
RUN mkdir -p /app/uploads/images /app/uploads/videos && \
|
||||
chown -R app:app /app
|
||||
|
||||
USER app
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Créer le dossier uploads s'il n'existe pas (le volume le crée en root)
|
||||
# Puis créer les sous-dossiers
|
||||
if [ ! -d "/app/uploads" ]; then
|
||||
mkdir -p /app/uploads
|
||||
fi
|
||||
|
||||
# Créer les sous-répertoires
|
||||
mkdir -p /app/uploads/images /app/uploads/videos 2>/dev/null || true
|
||||
|
||||
echo "✅ Répertoires uploads prêts"
|
||||
|
||||
# Lancer l'application
|
||||
exec ./server
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- define "backend.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "backend.fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "backend.labels" -}}
|
||||
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||
app.kubernetes.io/name: {{ include "backend.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "backend.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "backend.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,68 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "backend.fullname" . }}
|
||||
labels:
|
||||
{{- include "backend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "backend.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "backend.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: backend
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "backend.fullname" . }}-secrets
|
||||
env:
|
||||
{{- range $k, $v := .Values.env }}
|
||||
- name: {{ $k }}
|
||||
value: {{ $v | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.persistence.uploads.enabled }}
|
||||
volumeMounts:
|
||||
- name: uploads
|
||||
mountPath: /app/uploads
|
||||
{{- end }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/app-settings
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/app-settings
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- if .Values.persistence.uploads.enabled }}
|
||||
volumes:
|
||||
- name: uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "backend.fullname" . }}-uploads
|
||||
{{- end }}
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "backend.fullname" . }}
|
||||
labels:
|
||||
{{- include "backend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "backend.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{- if .Values.persistence.uploads.enabled }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "backend.fullname" . }}-uploads
|
||||
labels:
|
||||
{{- include "backend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.uploads.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.uploads.size }}
|
||||
{{- if .Values.persistence.uploads.storageClass }}
|
||||
storageClassName: {{ .Values.persistence.uploads.storageClass }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "backend.fullname" . }}-secrets
|
||||
labels:
|
||||
{{- include "backend.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
{{- range $k, $v := .Values.secrets }}
|
||||
{{ $k }}: {{ $v | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "backend.fullname" . }}
|
||||
labels:
|
||||
{{- include "backend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
selector:
|
||||
{{- include "backend.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,70 @@
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: backend-mln
|
||||
tag: helm
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 2
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 70
|
||||
|
||||
persistence:
|
||||
uploads:
|
||||
enabled: true
|
||||
size: 5Gi
|
||||
storageClass: ""
|
||||
accessMode: ReadWriteOnce
|
||||
|
||||
env:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: "5432"
|
||||
DB_USER: postgres
|
||||
DB_NAME: gestion_db
|
||||
DB_SSLMODE: disable
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
API_PORT: "8080"
|
||||
|
||||
# Valeurs sensibles — à surcharger via --set-string ou un Secret Manager externe
|
||||
secrets:
|
||||
DB_PASSWORD: ""
|
||||
SESSION_SECRET: ""
|
||||
USER_JWT_SECRET: ""
|
||||
USER_JWT_SECRET_OLD: ""
|
||||
ADMIN_JWT_SECRET: ""
|
||||
ADMIN_JWT_SECRET_OLD: ""
|
||||
REDIS_PASSWORD: ""
|
||||
TOMTOM_API_KEY: ""
|
||||
TELEGRAM_WEBHOOK_URL: ""
|
||||
TELEGRAM_WEBHOOK_SECRET: ""
|
||||
NOWPAYMENTS_IPN_SECRET: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
fsGroup: 101
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
echo "▶ Build"
|
||||
docker build -f "$SCRIPT_DIR/frontend/Dockerfile" -t gestion-frontend:latest "$PROJECT_ROOT"
|
||||
|
||||
echo "▶ Export"
|
||||
docker save gestion-frontend:latest -o /tmp/gestion-frontend.tar
|
||||
|
||||
for NODE in 192.168.1.43 192.168.1.44 192.168.1.45; do
|
||||
echo "→ $NODE"
|
||||
ssh xor@$NODE "sudo k3s ctr images rm docker.io/library/gestion-frontend:latest 2>/dev/null; true"
|
||||
scp /tmp/gestion-frontend.tar xor@$NODE:/tmp/
|
||||
ssh xor@$NODE "sudo k3s ctr images import /tmp/gestion-frontend.tar && rm /tmp/gestion-frontend.tar"
|
||||
done
|
||||
|
||||
rm -f /tmp/gestion-frontend.tar
|
||||
echo "✓ Done"
|
||||
@@ -0,0 +1,15 @@
|
||||
:8080 {
|
||||
root * /srv
|
||||
encode gzip
|
||||
|
||||
# SPA fallback — toutes les routes inconnues renvoient index.html
|
||||
try_files {path} /index.html
|
||||
|
||||
file_server
|
||||
|
||||
header {
|
||||
-Server
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: gestion-frontend
|
||||
description: React/Vite frontend — gestion-commande
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.0.0"
|
||||
@@ -0,0 +1,28 @@
|
||||
# =========================================================
|
||||
# Stage 1: Build React/Vite
|
||||
# =========================================================
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY frontend-prep/package.json frontend-prep/package-lock.json* ./
|
||||
RUN npm ci --ignore-scripts
|
||||
|
||||
COPY frontend-prep/ .
|
||||
RUN npm run build
|
||||
|
||||
# =========================================================
|
||||
# Stage 2: Caddy — SPA static server (TLS terminé par Traefik)
|
||||
# =========================================================
|
||||
FROM caddy:alpine AS runtime
|
||||
|
||||
# Retire la file capability CAP_NET_BIND_SERVICE — incompatible avec
|
||||
# allowPrivilegeEscalation: false. On écoute sur 8080 à la place.
|
||||
RUN apk add --no-cache libcap && setcap -r /usr/bin/caddy && apk del libcap
|
||||
|
||||
COPY helm/frontend/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=builder /app/dist /srv
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- define "frontend.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "frontend.fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "frontend.labels" -}}
|
||||
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||
app.kubernetes.io/name: {{ include "frontend.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "frontend.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "frontend.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "frontend.fullname" . }}-config
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
data:
|
||||
config.js: |
|
||||
window.__APP_CONFIG__ = {
|
||||
apiUrl: {{ .Values.apiUrl | quote }}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "frontend.fullname" . }}
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "frontend.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "frontend.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: frontend
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: caddy-data
|
||||
mountPath: /data
|
||||
- name: app-config
|
||||
mountPath: /srv/config.js
|
||||
subPath: config.js
|
||||
readOnly: true
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: caddy-data
|
||||
emptyDir: {}
|
||||
- name: app-config
|
||||
configMap:
|
||||
name: {{ include "frontend.fullname" . }}-config
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "frontend.fullname" . }}
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "frontend.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "frontend.fullname" . }}
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
selector:
|
||||
{{- include "frontend.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,38 @@
|
||||
replicaCount: 2
|
||||
|
||||
apiUrl: ""
|
||||
|
||||
image:
|
||||
repository: frontend-mln
|
||||
tag: helm
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 2
|
||||
maxReplicas: 4
|
||||
targetCPUUtilizationPercentage: 70
|
||||
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: gestion-postgresql
|
||||
description: PostgreSQL 16 — gestion-commande
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "16"
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- define "postgresql.fullname" -}}
|
||||
{{- printf "%s-postgresql" .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "postgresql.labels" -}}
|
||||
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||
app.kubernetes.io/name: postgresql
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "postgresql.selectorLabels" -}}
|
||||
app.kubernetes.io/name: postgresql
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "postgresql.fullname" . }}-config
|
||||
labels:
|
||||
{{- include "postgresql.labels" . | nindent 4 }}
|
||||
data:
|
||||
postgresql.conf: |
|
||||
max_connections = {{ .Values.config.maxConnections }}
|
||||
shared_buffers = {{ .Values.config.sharedBuffers }}
|
||||
effective_cache_size = {{ .Values.config.effectiveCacheSize }}
|
||||
maintenance_work_mem = {{ .Values.config.maintenanceWorkMem }}
|
||||
wal_buffers = {{ .Values.config.walBuffers }}
|
||||
default_statistics_target = {{ .Values.config.defaultStatisticsTarget }}
|
||||
random_page_cost = {{ .Values.config.randomPageCost }}
|
||||
log_min_duration_statement = {{ .Values.config.logMinDurationStatement }}
|
||||
log_timezone = 'UTC'
|
||||
timezone = 'UTC'
|
||||
# Connexions sécurisées uniquement depuis le réseau interne cluster
|
||||
listen_addresses = '*'
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "postgresql.fullname" . }}-data
|
||||
labels:
|
||||
{{- include "postgresql.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
{{- if .Values.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.persistence.storageClass }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "postgresql.fullname" . }}
|
||||
labels:
|
||||
{{- include "postgresql.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
POSTGRES_PASSWORD: {{ .Values.auth.password | quote }}
|
||||
POSTGRES_USER: {{ .Values.auth.username | quote }}
|
||||
POSTGRES_DB: {{ .Values.auth.database | quote }}
|
||||
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "postgresql.fullname" . }}
|
||||
labels:
|
||||
{{- include "postgresql.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
selector:
|
||||
{{- include "postgresql.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: postgresql
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: postgresql
|
||||
protocol: TCP
|
||||
---
|
||||
# Service headless pour le StatefulSet
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "postgresql.fullname" . }}-headless
|
||||
labels:
|
||||
{{- include "postgresql.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
selector:
|
||||
{{- include "postgresql.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: postgresql
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: postgresql
|
||||
@@ -0,0 +1,70 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "postgresql.fullname" . }}
|
||||
labels:
|
||||
{{- include "postgresql.labels" . | nindent 4 }}
|
||||
spec:
|
||||
serviceName: {{ include "postgresql.fullname" . }}-headless
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "postgresql.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "postgresql.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: postgresql
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: postgresql
|
||||
containerPort: 5432
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "postgresql.fullname" . }}
|
||||
env:
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
- name: config
|
||||
mountPath: /etc/postgresql/postgresql.conf
|
||||
subPath: postgresql.conf
|
||||
args:
|
||||
- "-c"
|
||||
- "config_file=/etc/postgresql/postgresql.conf"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "postgresql.fullname" . }}-data
|
||||
- name: config
|
||||
configMap:
|
||||
name: {{ include "postgresql.fullname" . }}-config
|
||||
@@ -0,0 +1,50 @@
|
||||
image:
|
||||
repository: postgres
|
||||
tag: "16-alpine"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
auth:
|
||||
username: postgres
|
||||
database: gestion_db
|
||||
# À surcharger via --set auth.password=xxx
|
||||
password: ""
|
||||
|
||||
persistence:
|
||||
size: 10Gi
|
||||
storageClass: ""
|
||||
accessMode: ReadWriteOnce
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 5432
|
||||
|
||||
# Paramètres PostgreSQL (postgresql.conf)
|
||||
config:
|
||||
maxConnections: "200"
|
||||
sharedBuffers: "256MB"
|
||||
effectiveCacheSize: "768MB"
|
||||
maintenanceWorkMem: "64MB"
|
||||
walBuffers: "16MB"
|
||||
defaultStatisticsTarget: "100"
|
||||
randomPageCost: "1.1"
|
||||
logMinDurationStatement: "1000"
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: gestion-redis
|
||||
description: Redis 7 — gestion-commande
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "7"
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- define "redis.fullname" -}}
|
||||
{{- printf "%s-redis" .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "redis.labels" -}}
|
||||
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "redis.selectorLabels" -}}
|
||||
app.kubernetes.io/name: redis
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "redis.fullname" . }}-config
|
||||
labels:
|
||||
{{- include "redis.labels" . | nindent 4 }}
|
||||
data:
|
||||
redis.conf: |
|
||||
maxmemory {{ .Values.config.maxmemory }}
|
||||
maxmemory-policy {{ .Values.config.maxmemoryPolicy }}
|
||||
appendonly {{ .Values.config.appendonly }}
|
||||
appendfsync {{ .Values.config.appendfsync }}
|
||||
{{- if .Values.config.save }}
|
||||
save {{ .Values.config.save }}
|
||||
{{- else }}
|
||||
save ""
|
||||
{{- end }}
|
||||
protected-mode no
|
||||
loglevel notice
|
||||
# Le mot de passe est injecté au démarrage via --requirepass
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "redis.fullname" . }}-data
|
||||
labels:
|
||||
{{- include "redis.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
{{- if .Values.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.persistence.storageClass }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "redis.fullname" . }}
|
||||
labels:
|
||||
{{- include "redis.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
REDIS_PASSWORD: {{ .Values.auth.password | quote }}
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "redis.fullname" . }}
|
||||
labels:
|
||||
{{- include "redis.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
selector:
|
||||
{{- include "redis.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: redis
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: redis
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "redis.fullname" . }}-headless
|
||||
labels:
|
||||
{{- include "redis.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
selector:
|
||||
{{- include "redis.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: redis
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: redis
|
||||
@@ -0,0 +1,76 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "redis.fullname" . }}
|
||||
labels:
|
||||
{{- include "redis.labels" . | nindent 4 }}
|
||||
spec:
|
||||
serviceName: {{ include "redis.fullname" . }}-headless
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "redis.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "redis.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: redis
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: redis
|
||||
containerPort: 6379
|
||||
protocol: TCP
|
||||
command:
|
||||
- redis-server
|
||||
- /etc/redis/redis.conf
|
||||
- "--requirepass"
|
||||
- "$(REDIS_PASSWORD)"
|
||||
env:
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "redis.fullname" . }}
|
||||
key: REDIS_PASSWORD
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: config
|
||||
mountPath: /etc/redis/redis.conf
|
||||
subPath: redis.conf
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- redis-cli --no-auth-warning -a $REDIS_PASSWORD ping | grep PONG
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- redis-cli --no-auth-warning -a $REDIS_PASSWORD ping | grep PONG
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "redis.fullname" . }}-data
|
||||
- name: config
|
||||
configMap:
|
||||
name: {{ include "redis.fullname" . }}-config
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
@@ -0,0 +1,46 @@
|
||||
image:
|
||||
repository: redis
|
||||
tag: "7-alpine"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
auth:
|
||||
# À surcharger via --set auth.password=xxx
|
||||
password: ""
|
||||
|
||||
persistence:
|
||||
size: 2Gi
|
||||
storageClass: ""
|
||||
accessMode: ReadWriteOnce
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 6379
|
||||
|
||||
config:
|
||||
maxmemory: "256mb"
|
||||
# allkeys-lru : expulse les clés les moins récemment utilisées quand la mémoire est pleine
|
||||
maxmemoryPolicy: "allkeys-lru"
|
||||
appendonly: "yes"
|
||||
appendfsync: "everysec"
|
||||
# Désactiver les snapshots RDB (AOF suffit)
|
||||
save: ""
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -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