Sécurisation de la Stack Traefik & CrowdSec
Guide de Sécurisation de la Stack Traefik & CrowdSec
Ce document décrit l'architecture de sécurisation d'un reverse-proxy Traefik sous Docker :
1. Détection d'intrusions avec CrowdSec : Analyse des logs JSON et bannissement dynamique via le bouncer Traefik.
2. Durcissement HTTP avec des Security Headers : En-têtes recommandés (HSTS, SAMEORIGIN, nosniff).
3. Certificats TLS via Cloudflare DNS-01 : Certificats Let's Encrypt à courbe elliptique (EC256) sans port HTTP public ouvert.
4. Préservation de l'IPv6 native : Configuration Docker avec ip6tables pour éviter le masquage d'adresses source par le proxy userland.
1. Architecture Globale du Filtrage
Le trafic entrant traverse une série de filtres successifs avant d'atteindre les conteneurs applicatifs (Nextcloud, Immich, Open WebUI, etc.) :
graph TD
Client["Client Web - Navigateur ou Attaquant"] -->|"HTTP 80 ou HTTPS 443"| Traefik["Traefik v3 (Reverse Proxy)"]
subgraph Traefik_Pipeline["Pipeline de Filtrage Traefik"]
Redir{"Port 80 ?"}
Redir -->|"Oui"| Bouncer80["Middleware CrowdSec (Port 80)"]
Bouncer80 -->|"IP Saine"| HTTPS_Redirect["Redirection 301 HTTPS"]
Redir -->|"Non (Port 443)"| Bouncer443["Middleware CrowdSec (Port 443)"]
HTTPS_Redirect --> Bouncer443
Bouncer443 --> Headers["Middleware Security-Headers"]
end
Traefik --> Traefik_Pipeline
subgraph CrowdSec_Engine["Moteur de Détection CrowdSec"]
JSONLog["access.log (JSON)"]
Parser["Parser Traefik et Scénarios Hub"]
Profiles["profiles.yaml (Ban direct 4h)"]
LAPI["Local API (LAPI)"]
JSONLog --> Parser --> Profiles --> LAPI
end
Traefik -.->|"Écriture logs"| JSONLog
Bouncer443 -.->|"Vérification synchrone"| LAPI
Bouncer80 -.->|"Vérification synchrone"| LAPI
Headers -->|"IP Saine"| Apps["Conteneurs Applicatifs Internes"]
Bouncer443 -->|"IP Bannie"| Deny["HTTP 403 Forbidden (Rejet immédiat)"]
Bouncer80 -->|"IP Bannie"| Deny
| Composant | Rôle Technique | Fichier de Configuration |
|---|---|---|
| Traefik | Point d'entrée HTTPS, terminaison TLS, routage Docker | variable-traefik.yaml |
| Bouncer Traefik | Plugin Go (maxlerebourg/crowdsec-bouncer-traefik-plugin) interrogeant la LAPI |
variable-crowdsec-middleware.yaml |
| CrowdSec | Détection d'attaques par analyse heuristique des requêtes | variable-crowdsec-profiles.yaml |
| Security Headers | Conformité HTTP stricte et protection contre les attaques côté navigateur | variable-security-headers.yaml |
2. Durcissement Passif : Security Headers
Le middleware dynamique security-headers est injecté au niveau du point d'entrée sécurisé websecure (port 443). Il s'applique de manière transparente à tous les conteneurs exposés.
Fichier dynamique : variable-security-headers.yaml
http:
middlewares:
security-headers:
headers:
# Empêche l'affichage dans des frames externes (protection contre le Clickjacking)
# SAMEORIGIN permet toutefois l'intégration interne sécurisée
customFrameOptionsValue: "SAMEORIGIN"
# Bloque les tentatives d'interprétation MIME arbitraires par les navigateurs
contentTypeNosniff: true
# Active le filtre XSS hérité pour les anciens clients
browserXssFilter: true
# Limite la fuite d'URL référente vers des domaines tiers
referrerPolicy: "strict-origin-when-cross-origin"
# Force le protocole HTTPS (HSTS) pendant 1 semaine (604800s) avec sous-domaines
stsSeconds: 604800
stsIncludeSubdomains: true
stsPreload: false
forceSTSHeader: true
3. Configuration Active CrowdSec : Bannissement Direct
A. Politique de Remédiation : variable-crowdsec-profiles.yaml
Les profils de remédiation appliquent un bannissement direct de 4 heures pour les adresses IP et les plages réseau :
# Profil par défaut : bannissement strict 4h pour toutes les adresses IP hostiles
name: default_ip_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
on_success: break
---
# Profil plage : bannissement strict 4h pour les attaques coordonnées par sous-réseau
name: default_range_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Range"
decisions:
- type: ban
duration: 4h
on_success: break
B. Configuration du Middleware Bouncer : variable-crowdsec-middleware.yaml
Ce middleware communique directement avec la LAPI (Local API) de CrowdSec via le réseau Docker frontend en mode live :
## Dynamic Traefik middleware for CrowdSec bouncer plugin
http:
middlewares:
crowdsec-bouncer:
plugin:
bouncer:
enabled: true
logLevel: INFO
crowdsecMode: live
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec:8080
crowdsecLapiKeyFile: /etc/traefik/conf/crowdsec-api-key.txt
Création de la clé d'API pour le bouncer
La clé d'API est générée au sein du conteneur CrowdSec et déposée dans le volume partagé de configuration de Traefik sans aucun retour à la ligne :
C. Acquisition des Logs : acquis.yaml
Pour permettre à CrowdSec de lire les requêtes enregistrées par Traefik, le fichier /opt/crowdsec/crowdsec-config/acquis.yaml pointe vers le point de montage partagé :
D. Liste blanche d'accès API : /opt/crowdsec/crowdsec-config/config.yaml
Pour que le bouncer Traefik puisse requêter la LAPI sans risquer de se faire rejeter, les sous-réseaux internes Docker doivent être déclarés de confiance :
api:
server:
trusted_ips:
- 127.0.0.1
- 172.16.0.0/12 # Sous-réseaux Docker internes
- 10.0.0.0/8
- ::1
- fd00::/8
4. Configuration Statique de Traefik (variable-traefik.yaml)
Le fichier statique de Traefik rassemble l'ensemble des garde-fous de sécurité réseau, de journalisation et de génération de certificats :
global:
checkNewVersion: false
sendAnonymousUsage: false
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.5.0
entryPoints:
web:
address: :80
http:
# Le bouncer filtre les requêtes dès le port 80 AVANT la redirection HTTPS
middlewares:
- crowdsec-bouncer@file
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: :443
forwardedHeaders:
trustedIPs:
- "127.0.0.1/32"
- "::1/128"
- "fd00::/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
- "10.0.0.0/8"
transport:
respondingTimeouts:
readTimeout: 120
idleTimeout: 360
http:
middlewares:
- crowdsec-bouncer@file
- security-headers@file
certificatesResolvers:
cloudflare:
acme:
email: "admin@example.com"
storage: /etc/traefik/certs/cloudflare-acme.json
caServer: 'https://acme-v02.api.letsencrypt.org/directory'
# Chiffrement moderne à courbe elliptique
keyType: EC256
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
serversTransport:
insecureSkipVerify: true
providers:
docker:
exposedByDefault: false
endpoint: 'unix:///var/run/docker.sock'
watch: true
file:
directory: /etc/traefik/conf/
watch: true
accessLog:
filePath: "/var/log/traefik/access.log"
# Format JSON obligatoire pour l'extraction fiable par CrowdSec
format: json
fields:
headers:
defaultMode: keep
5. Déploiement Docker Compose (docker-compose.yaml)
Hardening du Conteneur Traefik
- Exécution non-root : Traefik s'exécute sous un utilisateur dédié non privilégié (
user: ${PUID}:${PGID}). - Attribution de capabilités ciblées : Seule la capabilité
NET_BIND_SERVICEest accordée pour autoriser l'écoute sur les ports privilégiés 80 et 443. - Accès au socket Docker : Le conteneur rejoint le groupe système Docker (
group_add: ["110"]) pour lire les événements Docker sans exécuter le binaire enroot. - Attente active réseau au boot : Une boucle shell teste la joignabilité de l'infrastructure de plugins Traefik (
plugins.traefik.io) avant de lancer le binaire. Cela évite que Traefik ne plante lors des redémarrages serveur si la carte réseau ou la résolution DNS WAN n'est pas encore prête.
services:
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: crowdsec
environment:
GID: "${GID-1000}"
COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik"
volumes:
- /opt/crowdsec/crowdsec-db:/var/lib/crowdsec/data/
- /opt/crowdsec/crowdsec-config:/etc/crowdsec/
- /opt/traefik/log:/var/log/traefik/:ro
- $INFRASTRUCTURE_FOLDER_LOCATION/variable-crowdsec-profiles.yaml:/etc/crowdsec/profiles.yaml:ro
networks:
- frontend
restart: always
traefik:
image: traefik:latest
container_name: traefik
restart: always
networks:
- frontend
ports:
- "80:80"
- "443:443"
- "8081:8080"
user: ${PUID}:${PGID}
cap_add:
- NET_BIND_SERVICE
group_add:
- "110" # ID de groupe docker pour accéder à /var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/traefik/certs:/etc/traefik/certs/
- /opt/traefik/conf:/etc/traefik/conf/
- /opt/traefik/log:/var/log/traefik/
- /opt/traefik/plugins-storage:/plugins-storage
- $INFRASTRUCTURE_FOLDER_LOCATION/variable-traefik.yaml:/etc/traefik/traefik.yaml:ro
- $INFRASTRUCTURE_FOLDER_LOCATION/variable-crowdsec-middleware.yaml:/etc/traefik/conf/middleware-crowdsec.yaml:ro
- $INFRASTRUCTURE_FOLDER_LOCATION/variable-security-headers.yaml:/etc/traefik/conf/security-headers.yaml:ro
environment:
- CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
command: >
sh -c "while ! ping -c 1 plugins.traefik.io > /dev/null 2>&1; do sleep 1; done; exec traefik"
6. Routage IPv6 Natif et Prévention du Bannissement de Passerelle
Le Problème du Userland Proxy Docker
Par défaut, lorsque Docker publie un port (ports: ["443:443"]), il utilise un démon en espace utilisateur (docker-proxy). Ce proxy masque l'adresse IPv6 d'origine du client en la remplaçant par l'adresse de passerelle interne de Docker (ex: fd01::1).
Risque de blocage total de l'IPv6
Si un client externe commet une attaque en IPv6 et que CrowdSec ordonne son bannissement, le bouncer Traefik verrait l'adresse IP source fd01::1. Tous les clients IPv6 légitimes du monde entier se retrouveraient immédiatement bannis !
Solution Sysadmin : ip6tables dans le noyau
Pour préserver la véritable adresse IPv6 source jusqu'à Traefik et CrowdSec, il faut déléguer le routage directement au sous-système netfilter du noyau via ip6tables.
Cette étape peut être automatisée via Ansible ou en configurant directement /etc/docker/daemon.json sur l'hôte :
Après redémarrage du démon Docker (systemctl restart docker), les paquets IPv6 traversent directement le pont virtuel sans modification de l'en-tête IP source.
7. Exploitation et Supervision au Quotidien
Commandes CLI Usuelles (cscli)
Toutes les commandes d'administration de CrowdSec sont invoquées dans son conteneur Docker :
# Vérifier l'état de synchronisation du bouncer Traefik
docker exec crowdsec cscli bouncers list
# Consulter la liste des décisions actives (bannissements en cours)
docker exec crowdsec cscli decisions list
# Bannir manuellement une adresse IP hostile pour 24 heures
docker exec crowdsec cscli decisions add --ip 198.51.100.42 -d 24h --type ban -r "Attaque manuelle suspecte"
# Révoquer un bannissement (débloquer une IP)
docker exec crowdsec cscli decisions delete --ip 198.51.100.42
# Afficher les statistiques d'analyse de logs et de détection
docker exec crowdsec cscli metrics
Automatisation des Mises à Jour du Hub
Les scénarios de menaces et les collections CrowdSec évoluent quotidiennement. La mise à jour est automatisée chaque matin à 06h00 par notre conteneur cronjob via le script update_security.sh :