C'est quoi ce Traefik?!
Avant d'aller plus loin dans la technique...
Adepte d'Apache2 sous toutes ses formes notament en reverse proxy
, j'ai néanmoins souhaité installer un nouvel outil pour gérer les accès depuis Internet à destination de mes différents sites.
En effet il faut vivre avec son temps, et j'étais assez agacé de ne pas pouvoir bloquer certains types de requêtes (les scans des sous répertoires .env
ou autres X11\
) qui généraient beaucoup de 404 sans que je puisse bouter l'ennemi hors de mon réseau.
Crowdsec propose bien un composant AppSec très alléchant qui permettrait de dégager ce genre d'attaques, mais uniquement compatibles avec Nginx, openresty ou Traefik.
Ne vous fiez donc pas au titre de cet article, il traitera autant de Traefik que de Crowdsec...
Mise en place de Traefik
Schéma
Un petit schéma de la solution souhaitée:
┌───────────────────────┐
│ Internet │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ Traefik │
│ (entryPoints 80/443) │
└───────────┬───────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ CrowdSec Bouncer │ │ Apache (vhosts) │ │ Docker Apps │
│ + AppSec WAF │ │ /var/www/siteX │ │ (containers) │
└──────────────────┘ │listens 127.0.0.1 │ │ exposed via │
│ :8080,... │ │ traefik labels │
└──────────────────┘ └──────────────────┘
│
▼
┌───────────────────────┐
│ Sites │
└───────────────────────┘
L'intérêt de Traefik est multiple. Comme vu plus haut, mon intérêt principal est bien l'intégration avec Crowdsec en mode WAF
, mais il y en a d'autres:
- Sécurité simplifiée (https auto via Let's Encrypt), Middlewares de sécurité (HSTS, CSP, rate limiting, IP whitelise...)
- Centralisation du routage: plus besoin de gérer un Apache avec 50 vhosts, et des
backends
différents (Apache2, Docker...) - Un Dashboard Web intégré...honnêtement je n'y ai pas trouvé mon intérêt, chacun ses gouts.
Installation
Binaire et service
J'ai lu pas mal de tutoriels sur le net, et la grande majorité indique un Traefik sous docker
.
Je n'ai pas d'explication logique à mon raisonnement, mais je souhaitais pour ma part avoir un Traefik déployé directement sur mon système hôte, et non pas via un conteneur Docker.
Ca été un peu plus compliqué de trouver des informations fiables, dans les grandes lignes je me suis appuyé sur une partie du tutoriel de Stephane Robert qui indique justement les deux cas.
On récupère la dernière version stable
wget https://github.com/traefik/traefik/releases/download/v3.5.1/traefik_v3.5.1_linux_amd64.tar.gz
On extrait l'archive qui contient le binaire, celui ci doit pouvoir être exécuté, on prépare le répertoire contenant les confs:
chmod +x traefik_v3.5.1_linux_amd64
mv traefik_v3.5.1_linux_amd64 /usr/local/bin/traefik
mkdir /etc/traefik/
Enfin le binaire pourra être testé une fois que vous aurez créé à minima un fichier yaml traefik.yaml
(que l'on voit plus loin):
traefik --configFile=/etc/traefik/traefik.yaml
Si tout est valide il est de bon ton de créer un service systemd associé, qui activera Traefik au démarrage du serveur:
nano /etc/systemd/system/traefik.service
qui contiendra:
[Unit]
Description=Traefik Service
After=network.target
[Service]
Type=exec
ExecStart=/usr/local/bin/traefik --configFile=/etc/traefik/traefik.yml
Restart=always
RestartSec=5s
User=root
Group=root
[Install]
WantedBy=multi-user.target
Enfin on redémarre les services systemd
pour s'assurer que tout est ok, et si c'est bien le cas on active le service:
systemctl daemon-reload
systemctl enable traefik
systemctl start traefik
Cerise sur le gâteau on peut voir les logs en direct pour vérifier qu'il n'y a aucun loupé:
journalctl -u traefik -f
A ce stade, on aura un Traefik tout neuf qui fonctionne, sauf qu'on a pas encore vu les fichiers de confs, alors allons y!
Configurations
C'est ici que j'ai vraiment dû fouiner dans les entrailles du réseau mondial pour croiser les sources, et comprendre comment obtenir le schéma indiqué plus haut.
Rien de sorcier en soit, mais aucun des tutoriels trouvés n'évoquait à la fois:
- la gestion des logs
- la sécurisation minimale (via les middlewares)
- la gestion des certificats
- la gestion de l'inteface graphique de Traefik...
J'essaie de regrouper ici toutes les informations nécessaires à la bonne mise en place de l'outil:
/etc/traefik/traefik.yaml
C'est le point d'entrée du reverse proxy, voici ma conf:
entryPoints:
traefik:
address: ":1001" ## port d'écoute du dashboard de Traefik ##
web: ## Ecoute en entrée sur le port 80, je redirige tout sur le https ##
address: ":80"
http:
redirections:
entryPoint:
to: "websecure"
scheme: "https"
permanent: true
websecure:
address: ":443" ## écoute en entrée sur HTTPS ##
providers:
file:
directory: "/etc/traefik/conf.d" ## répertoire contenant mes configurations ##
watch: true
api: ## utile pour l'activation du dashboard, et l'accès en http (limité au seul réseau local dans une autre conf) ##
dashboard: true
insecure: true
log: ## chemin d'accès des logs du serveur ##
filePath: "/var/log/traefik/server.log"
format: json
level: INFO
maxSize: 100
maxBackups: 3
maxAge: 3
compress: true
accessLog: ## chemin d'accès des logs d'accès à Traefik (depuis Internet donc) ##
filePath: "/var/log/traefik/access.log"
format: json
filters:
statusCodes:
- "200"
- "400-404"
- "500-503"
bufferingSize: 0
fields:
headers:
defaultMode: drop
names:
User-Agent: keep
certificatesResolvers: ## gestion automatique des certificats Lets Encrypt pour mes sites. c'est magique! ##
letsencrypt:
acme:
email: "monemail"
storage: "/etc/traefik/acme.json"
httpChallenge:
entryPoint: web
/etc/traefik/conf.d/sites.yml
Dans ce fichier on liste les sites web / conteneurs dockers en écoute:
http:
routers:
site1:
entryPoints:
- web
- websecure
rule: 'Host(`site1.scourzic.net`)'
service: apache
tls:
certResolver: letsencrypt
middlewares:
- secure-headers
site2:
entryPoints:
- web
- websecure
rule: 'Host(`site2.scourzic.net`)'
service: docker
tls:
certResolver: letsencrypt
middlewares:
- secure-headers
docs:
entryPoints:
- web
- websecure
rule: 'Host(`docs.scourzic.net`)'
service: apache
tls:
certResolver: letsencrypt
middlewares:
- secure-headers
services:
apache:
loadBalancer:
servers:
- url: "http://127.0.0.1:8888"
docker:
loadBalancer:
servers:
- url: "http://127.0.0.1:1883"
/etc/traefik/conf.d/middlewares.yml
Dans Traefik, un fichier comme middlewares.yml
n’est pas obligatoire : c’est juste une bonne pratique pour séparer la configuration.
L'idée est de déclarer les middlewares une seule fois (authentification, sécurité, rate limiting, etc.) pour les réappliquer ensuite sur l'ensemble des routeurs.
http:
middlewares:
secure-headers:
headers:
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
contentSecurityPolicy: "default-src 'self' 'unsafe-inline' data: blob: https:; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; img-src * data: blob:; font-src * data:; connect-src 'self' https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
permissionsPolicy: "geolocation=(*), microphone=(*), camera=(*), fullscreen=(*)"
/etc/traefik/conf.d/dashboard.yml
Enfin la dernière conf qui contient les éléments nécessaires à la mise en place de l'interface web de Traefik, communément appelée Dashboard.
http:
routers:
traefik-dashboard:
rule: "Host(`192.168.1.1`)"
entryPoints:
- "websecure"
service: api@internal
middlewares:
- allow-local
middlewares:
allow-local:
ipWhiteList:
sourceRange:
- "192.168.1.0/24"
- "10.8.0.0/24"
A adapter forcément selon votre cas particulier, mais avec ces 4 fichiers de configuration, vous aurez un traefik qui fonctionne bien.
Crowdsec
Déploiement du bouncer Traefik
Génération du bouncer
Avant de s'attaque à la partie AppSec, on va d'abord installer le bouncer Traefik qui aura pour rappel pour fonction d'appliquer les décisions traitées par Crowdsec.
On créé simplement celui ci:
cscli bouncers add traefik-bouncer
notez l'API indiquée dans le bouncer. Elle sera également nécessaire dans une autre conf
Ca va donc générer un fichier de conf dans /etc/crowdsec/bouncer/crowdsec-bouncer-traefik.conf
dans lequel vous allez rajouter:
API_URL=http://127.0.0.1:8080 # Port d'écoute de crowdsec
API_KEY=XXX # clé API précédémment générée pour le bouncer
APPSEC_URL=http://127.0.0.1:7422 # Port d'écoute d'Appse > utile dans la partie suivante
Vous redémarrer le service, on peut ensuite vérifier deux choses:
- Le bouncer est il listé?
cscli bouncers list
- L'API est elle bien interrogeable?
curl -H "X-Api-Key: XXXX" http://127.0.0.1:8080/v1/decisions
Normalement la première commande liste bien le nouveau bouncer, alors que la seconde indiquera la liste des ips bannies.
Modification des confs
Maintenant notre bouncer fonctionnel, on va configurer Traefik pour la prise en charge de ce dernier.
Tout d'abord, il faut rajouter dans la conf existante /etc/traefik/conf.d/middlewares.yml
crowdsec:
plugin:
bouncer:
enabled: true
crowdsecAppsecEnabled: true
crowdsecAppsecHost: 127.0.0.1:7422
crowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
crowdsecLapiKey: XXXXXXXXXXXXXXX # indiquez ici l'API
defaultDecisionSeconds: 60
crowdsecMode: live
crowdsecLapiHost: 127.0.0.1:8080
crowdsecLapiScheme: http
crowdsecLapiTLSInsecureVerify: false
forwardedHeadersTrustedIPs:
# private class ranges
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
clientTrustedIPs:
# private class ranges
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
Et également dans le fichier /etc/traefik/traefik.yml
à la toute fin:
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.4.4
Enfin on n'oublie pas de rajouter également le nom du middleware nouvellement créé dans le fichier /etc/traefik/conf.d/sites.yml
pour l'ensemble des routers:
...
middlewares:
- secure-headers
- crowdsec
...
Avec ça on est bon, Traefik est prêt ! On vérifie?
Je lance une commande curl
depuis un site web pour être sûr d'être hors de mon réseau, je vois en log:
time="2025-09-08T20:29:12+10:00" level=info msg="127.0.0.1 - [Mon, 08 Sep 2025 20:29:12 AEST] \"GET /v1/decisions?ip=64.227.21.251 HTTP/1.1 200 332.493105ms \"Crowdsec-Bouncer-Traefik-Plugin/1.X.X\" \""
Tadam! A peut près tout est indiqué, nous avons bien un Traefik qui route avec un bouncer qui fait le taff. On s'arrête là? Ben non!
J'avais déjà un bouncer (apache) fonctionnel, en soit je n'ai en rien amélioré la détection d'attaques malveillantes. Mais rappelez vous, AppSec s'appuie sur un reverse-proxy
compatible, et Traefik en fin partie contrairement à Apache.
Attaquons donc cette dernière partie.
ChatGPT, mon ami bipolaire
Je fais quand même une petite pause sur cette étape, tellement ça m'a vacciné de l'IA qui nous sauvera tous. Au départ ça a bien démarré, j'indique à CGPT mon souhait de mettre crowdsec derrière un traefik, et lui donne mes confs, je suis curieux de voir s'il peut me mâcher le travail.
A ma grande surprise il me sort des trucs hyper cohérents et pointus, vous voyez le collègue qui t'énerve car il maitrise à peu près toutes les situations.
Très vite, CGPT pointe du cyber doigt le fait - et c'est juste - que le plugin bouncer de Traefik n’arrive pas à joindre le service AppSec.
Et là ça part en sucette. Il m'indique que ma version de Crowdsec n'est pas la bonne, dixit:
Ok, ta version v1.6.11-debian-pragmatic explique tout :
Le suffixe "pragmatic" chez CrowdSec = build minimale faite pour la compatibilité Debian, sans AppSec (pas de support libmodsecurity, pas de champ appsec: dans config.yaml).
Donc, même si tu vois un port 7422 ouvert avec ta config acquis.d/appsec.yaml, ce n’est pas le vrai moteur AppSec, juste un socket défini comme source — et le bouncer Traefik ne peut pas interroger de vraies règles → d’où le appsecQuery:unreachable.
Et bien mon ami, je te crois sur parole, je vais donc chercher la bonne version que tu nommes crowdsec-full
. Et là commence ma traversée du désert.
- j'ai testé la version debian, depuis le Git, compilé les sources
- CGPT me file une url avec soit disant un
.deb
en 1.6.17...qui n'existe pas! - il me fait modifier des confs pour forcer la prise en compte d'Appsec.
Rien ne marche, quedalle. J'ai pété mon crowdsec et toujours pas de prise en compte d'appsec. Et là je commence à douter:
Moi: je crois que crowdsec-full n'existe pas, d'ou sors tu ça?
CGPT:
Tu as tout à fait raison, et je m'excuse pour la confusion dans mes précédentes réponses.
Le paquet crowdsec-full n'existe pas.
Haaaaaaa! Ok, donc j'ai perdu deux heures à chercher un truc qui n'existe pas. De plus ma version pragmatic
fonctionne très bien avec AppSec, donc il m'a raconté que du bullshit l'ami GPT.
AppSec
Après une réinstallation complète de crowdsec, j'ai repris la bonne vieille méthode, à savoir lire des articles sur le net et essayer de comprendre.
Pour rappel je souhaite absolument utiliser AppSec qui permettra de remonter des alertes détectées par le moteur WAF intégré en plus de celles gérées nativement par les parsers/scénarios "classiques" (analyse des logs).
J'ai donc finalement déployé AppSec en reprenant de zéro, il faut absolument que le module soit disponible depuis crowdsec.
Pour cela une commande simple:
root@debian-routeur:/etc/crowdsec# cscli version
version: v1.6.11-debian-pragmatic-amd64-d64ee2ae
...
Built-in optional components: cscli_setup, **datasource_appsec**,...
Si vous avez le composant datasource_appsec
d'indiqué, vous pouvez continuer.
Activation du module
Il est nécessaire de créer le fichier suivant pour que le module AppSec soit chargé sous Crowdsec:
/etc/crowdsec/acquis.d/appsec.yml
listen_addr: 127.0.0.1:7422
appsec_config: crowdsecurity/appsec-default
name: myAppSecComponent
source: appsec
labels:
type: appsec
Déploiement des règles et profils:
cscli hub update
cscli collections install crowdsecurity/appsec-default
cscli scenarios install crowdsecurity/crs
Déclencher les alertes
Il faut ensuite peupler le fichier /etc/crowdsec/profiles.yaml
en rajoutant les alertes souhaitées.
Dans mon exemple je souhaite bannir les ips qui scannent les url en .env
et les bad-user-agent
:
name: ban-appsec
filters:
- Alert.Remediation == false && (
Alert.Scenario contains "vpatch" ||
Alert.Scenario contains "http-bad-user-agent"
)
decisions:
- type: ban
duration: 240h
scope: Ip
Avec ça, on est bon! Un redémarrage du service crowdsec et on peut vérifier si le port est bien en écoute:
ss -lntp | grep 7422
Et également les métrics:
root@debian-routeur:/# cscli metrics | grep appsec
| crowdsecurity/appsec-vpatch | 3 |
| crowdsecurity/crowdsec-appsec-outofband | 4 |
| crowdsecurity/crowdsec-appsec-outofband | crowdsec | ban | 4 |
| crowdsecurity/appsec-vpatch | crowdsec | ban | 3 |cscli metrics | grep appsec
Effectivement vu comme ça, c'est tout bon à déployer, allons y!
Alors, ça ban?
Paramétrage des certificats
Normalement les options indiquées dans les configurations Traefik concernant les certificats permettent d'obtenir une bonne note, vérifions cela depuis le site de référence.
Prenez le temps de tester chacun de vos sites, ça évitera quelques ennuis.
Test de sécurité
Même fâché avec CGPT je lui ai posé la question de dissocier les actions traitées par le bouncer et Appsec, voici son retour avec un exemple concret:
- Un bot tente des attaques SSH → CrowdSec crée une décision pour son IP.
- Cette IP arrive sur ton site Traefik.
- Bouncer Traefik la bloque immédiatement → la requête n’atteint même pas AppSec.
- Sinon, si AppSec devait analyser chaque requête → perte de ressources inutile.
Testons le bouncer
Pour le bouncer, rien de plus simple on va choisir se connecter par VPN et/ou lancer une requête curl depuis un site web externe.
Exemple avec le site https://reqbin.com/curl.
Au préalable on bloque l'ip publique de ce site:
root@debian-routeur:~/# cscli decision add --ip 64.227.21.251
INFO Decision successfully added
Puis on balance la requête curl, qui affiche rapidement une 404.
Testons AppSec
Avec AppSec on peut tenter un accès depuis un VPN, sans bloquer au préalable l'ip.
Je vais tenter d'accéder au site sur lequel vous êtes actuellement, en précisant un sous répertoire interdit:
https://docs.scourzic.net/.env
testez si vous voulez être banni :p
La sanction est immédiate:
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
├──────────┼──────────┼────────────────────┼─────────────────────────────────────────┼────────┼─────────┼───────────────────────────────────────────────────────────┼────────┼────────────┼──────────┤
│ 12671905 │ crowdsec │ Ip:34.40.137.12 │ crowdsecurity/vpatch-env-access │ ban │ AU │ 396982 GOOGLE-CLOUD-PLATFORM │ 1 │ 239h58m9s │ 1287 │
Et on peut vérifier que c'est bien AppSec en mode WAF qui a dégagé l'intru:
time="2025-09-08T21:49:43+10:00" level=info msg="WAF block: crowdsecurity/vpatch-env-access from 34.40.137.12 (127.0.0.1)"
This is the End, my friend
Passage sur crowdsec 1.7.0
Mon article quasi terminé, la version de crowdsec en version 1.7.0
est sortie. Aucune difficulté rencontrée suite au passage sur cette dernière version.
A noter que j'ai dû forcer le passage des collections en dernière version, mais c'est bien indiqué par l'outil à la fin de la maj.
C'est la fin de ce très long article! En tout cas j'ai mis pas mal de temps à l'écrire, en essayant de reprendre le plus clairement possible le déroulé des actions effectuées pour la mise en place d'un WAF solide et évolutif.
Je suis très satisfait du passage à Traefik, finalement c'est lui qui m'a donné le moins de fil à retordre. Crowdsec c'était autre chose, mais vraiment j'adore cet outil puissant, opensource et vraiment très cool.
A bientôt pour un nouvel article... on parlera sauvegardes!