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!