Crowdsec avec Traefik

Crowdsec avec Traefik

Crowdsec est un IPS (Intrusion Prevention System) moderne et collaboratif, associé à un réseau mondial de réputation IP. Une sorte de Fail2ban 2.0 massivement multijoueur…

A moins de sortir d’une grotte, il est difficile en 2022 de passer à côté de cette solution française qui bénéficie d’une large communauté d’utilisateurs. Dans cet article, je vais détailler sa mise en œuvre dans un contexte Docker avec Traefik. Si besoin, vous pouvez vous référer à mes précédents articles sur l’infrastructure déjà en place :

Fonctionnement de Crowdsec

Crowdsec utilise un agent qui va analyser des logs (Parser) à la recherche d’événements pouvant correspondre à des scénarios d’attaques connus. Crowdsec en fournit un grand nombre par l’intermédiaire de collections disponibles sur le hub de Crowdsec

Lorsqu’un scénario est détecté, l’agent génère une alerte qui est envoyée à l’API locale (LAPI) de Crowdsec qui va prendre une décision associée:

  • l’alerte est principalement une information de traçabilité et restera même après l’expiration de la décision.
  • la décision, quant à elle, est de courte durée (4h) et indique quelle action doit être entreprise contre l’IP ou la plage IP incriminée.

Ces informations sont ensuite stockées dans une base de données. Crowdsec va également prévenir l’API centrale (CAPI) pour en faire bénéficier l’ensemble des participants du réseau Crowdsec (après un mécanisme de modération).

Les bouncers sont ensuite chargés d’appliquer les décisions prisent par Crowdsec: bloquer une IP, présenter un captcha, appliquer un MFA à un utilisateur donné, une erreur 403, etc.

Ce diagramme résume le fonctionnement :

Architecture Crowdsec

Fonctionnement avec traefik

Pour Traefik, il existe un bouncer spécifique qui utilise le middleware ForwardAuth pour valider l’accès aux applications en fonction des décisions prisent par Crowdsec.

Plutôt qu’une longue description, je vous ai fait un schéma du fonctionnement:

Traefik avec Crowdsec

  • les logs générés par Traefik sont parsés par le conteneur Crowdsec qui contient l’API locale
  • le conteneur bouncer consulte l’API Crowdsec pour connaitre les IP à bannir
  • le middleware ForwardAuth de Traefik sollicite le conteneur bouncer pour savoir si il doit permettre l’accès à l’application

Configuration de Traefik

L’intégration de Crowdsec nécessite quelques modifications dans le fichier docker-compose de Traefik:

  • activer la gestion des logs et définir un volume docker pour les stocker
  • configurer le middleware ForwardAuth avec l’adresse du bouncer Crowdsec
  • intercaler le middleware ForwardAuth directement après le point d’entrée en HTTPS (je n’expose pas de service en HTTP)
 1version: '3'
 2services:
 3  traefik:
 4    container_name: traefik
 5    image: traefik:v2.8.0
 6    restart: unless-stopped
 7    command:
 8      - "--providers.docker=true"
 9      - "--providers.docker.exposedbydefault=false"
10      - "--providers.file.directory=/etc/traefik/dynamic-conf"
11      - "--providers.file.watch=true"
12      - "--api.dashboard=true"
13      - "--entrypoints.web.address=:80"
14      - "--entrypoints.websecure.address=:443"
15      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
16      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
17      - "--certificatesResolvers.letsencrypt.acme.email=john.doe@domain.tld" # à modifier
18      - "--certificatesResolvers.letsencrypt.acme.storage=acme.json"
19      - "--certificatesResolvers.letsencrypt.acme.tlsChallenge=true"
20      - "--entrypoints.websecure.http.middlewares=crowdsec-bouncer@docker"
21      - "--log=true"
22      - "--log.level=INFO"
23      - "--log.filepath=/var/log/traefik.log"
24      - "--accesslog.filepath=/var/log/traefix-access.log"
25    labels:
26      - "traefik.enable=true"
27      - "traefik.http.routers.dashboard.rule=Host(`traefik.domain.tld`)"
28      - "traefik.http.routers.dashboard.service=api@internal"
29      - "traefik.http.routers.dashboard.entrypoints=websecure"
30      - "traefik.http.routers.dashboard.middlewares=auth-dashboard"
31      - "traefik.http.middlewares.auth-dashboard.basicauth.users=admin:$$xxxxxxxxx$$xxxxxxx/" # à modifier
32      - "traefik.http.routers.dashboard.tls=true"
33      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
34      - "traefik.http.middlewares.crowdsec-bouncer.forwardauth.address=http://crowdsec-bouncer:8080/api/v1/forwardAuth"
35      - "traefik.http.middlewares.crowdsec-bouncer.forwardauth.trustForwardHeader=true"
36    networks:
37      - traefik_lan
38    ports:
39      - 80:80
40      - 443:443
41    volumes:
42      - /var/run/docker.sock:/var/run/docker.sock:ro
43      - ./config/acme.json:/acme.json
44      - ./config:/etc/traefik:ro
45      - logs:/var/log/
46
47networks:
48  traefik_lan:
49    external: true
50
51volumes:
52  logs:

Installation de Crowdsec

Comme d’habitude, je commence par créer l’arborescence dans /opt:

1sudo mkdir /opt/crowdsec  && cd $_
2sudo touch acquis.yaml docker-compose.yaml

Le fichier acquis.yaml va permettre d’indiquer à l’agent Crowdsec l’emplacement des logs à analyser

1filenames:
2  - /var/log/traefik/*
3labels:
4  type:    traefik

Le fichier docker-compose.yaml ne présente pas de problème particulier. Il faut juste être vigilant sur la cohérence des chemins vers les fichiers de logs générés par Traefik.

 1version: '3'
 2services:
 3  crowdsec:
 4    image: docker.io/crowdsecurity/crowdsec:latest
 5    container_name: crowdsec
 6    restart: unless-stopped
 7    environment:
 8      - COLLECTIONS=crowdsecurity/traefik crowdsecurity/http-cve
 9      - CUSTOM_HOSTNAME=crowdsec
10    volumes:
11      - config:/etc/crowdsec
12      - db:/var/lib/crowdsec/data/
13      - /var/lib/docker/volumes/traefik_logs/_data/:/var/log/traefik/:ro
14      - ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
15    networks:
16      - traefik_lan
17    restart: unless-stopped
18
19  crowdsec-bouncer:
20    image: fbonalair/traefik-crowdsec-bouncer:latest
21    container_name: crowdsec-bouncer
22    restart: unless-stopped
23    environment:
24      CROWDSEC_BOUNCER_API_KEY: xxxxxxxxxxxxxxxxxxxxxx  # à modifier
25      CROWDSEC_AGENT_HOST: crowdsec:8080
26    expose:
27      - 8080
28    networks:
29      - traefik_lan
30
31networks:
32  traefik_lan:
33    external: true        
34
35volumes:
36  config:
37  db:    

Il suffit maintenant de lancer le docker-compose:

1docker-compose -f /opt/crowdsec/docker-compose.yaml up -d

A ce stade, il nous manque toujours la clé d’API permettant au bouncer de contacter l’API locale de Crowdsec. Pour cela, il faut utiliser la ligne de commande Crowdsec (cscli) et le plus simple est d’ajouter un alias à votre shell pour éviter de préfixer vos commandes avec un docker exec

1alias cscli='docker exec -t crowdsec cscli'

Vous pouvez alors ajouter le bouncer Traefix et obtenir en retour la clé d’API (attention, il est impossible de l’obtenir ultérieurement)

1cscli bouncers add traefik-bouncer

Après avoir renseigné la clé d’API obtenue dans le docker-compose, il vous suffit de relancer les services.

Vérifier le fonctionnement

Quelques commandes cscli permettent de rapidement vérifier le bon fonctionnement:

  • cscli bouncer list
  • cscli collections list
  • cscli metrics permet de vérifier la bonne prise en charge des logs
  • cscli decisions list ne retourne rien puisqu’il est peu probable que vous ayez déjà subi une attaque
 1$ cscli bouncer list
 2-----------------------------------------------------------------------------------------------
 3 NAME             IP ADDRESS   VALID  LAST API PULL         TYPE            VERSION  AUTH TYPE
 4-----------------------------------------------------------------------------------------------
 5 traefik-bouncer  172.19.0.10  ✔️      2022-07-30T16:19:59Z  Go-http-client  1.1      api-key   
 6-----------------------------------------------------------------------------------------------
 7
 8$ cscli collections list
 9COLLECTIONS
10------------------------------------------------------------------------------------------------------------
11 NAME                               📦 STATUS   VERSION  LOCAL PATH                                         
12------------------------------------------------------------------------------------------------------------
13 crowdsecurity/base-http-scenarios  ✔️  enabled  0.6      /etc/crowdsec/collections/base-http-scenarios.yaml
14 crowdsecurity/http-cve             ✔️  enabled  1.0      /etc/crowdsec/collections/http-cve.yaml            
15 crowdsecurity/linux                ✔️  enabled  0.2      /etc/crowdsec/collections/linux.yaml               
16 crowdsecurity/sshd                 ✔️  enabled  0.2      /etc/crowdsec/collections/sshd.yaml                
17 crowdsecurity/traefik              ✔️  enabled  0.1      /etc/crowdsec/collections/traefik.yaml             
18------------------------------------------------------------------------------------------------------------
19
20$ cscli metrics
21INFO[30-07-2022 04:21:35 PM] Buckets Metrics:                             
22+-------------------------------------------+---------------+-----------+--------------+--------+---------+
23|                  BUCKET                   | CURRENT COUNT | OVERFLOWS | INSTANTIATED | POURED | EXPIRED |
24+-------------------------------------------+---------------+-----------+--------------+--------+---------+
25| crowdsecurity/http-crawl-non_statics      | 2             | 43        | 838          | 2.69k  | 793     |
26| crowdsecurity/http-cve-2021-41773         | -             | 1         | 1            | -      | -       |
27| crowdsecurity/http-path-traversal-probing | -             | -         | 1            | 1      | 1       |
28| crowdsecurity/http-probing                | 2             | 171       | 277          | 1.99k  | 104     |
29| crowdsecurity/http-sensitive-files        | -             | 16        | 17           | 84     | 1       |
30| crowdsecurity/thinkphp-cve-2018-20062     | -             | 1         | 1            | -      | -       |
31+-------------------------------------------+---------------+-----------+--------------+--------+---------+
32INFO[30-07-2022 04:21:35 PM] Acquisition Metrics:                         
33+------------------------------------------+------------+--------------+----------------+------------------------+
34|                  SOURCE                  | LINES READ | LINES PARSED | LINES UNPARSED | LINES POURED TO BUCKET |
35+------------------------------------------+------------+--------------+----------------+------------------------+
36| file:/var/log/traefik/traefik.log        | 6.62k      | -            | 6.62k          | -                      |
37| file:/var/log/traefik/traefix-access.log | 18.37k     | 3.41k        | 14.96k         | 4.77k                  |
38+------------------------------------------+------------+--------------+----------------+------------------------+
39
40$ cscli decisions list
41No active decisions

Ca fait le Job ?

Bannir manuellement une adresse IP:

Depuis mon poste client, je teste l’accès à l’application avant de bannir l’adresse IP

1curl -I https://app.domain.tld
2
3HTTP/2 200
4accept-ranges: bytes
5content-type: text/html
6date: Sat, 30 Jul 2022 16:33:19 GMT
7etag: "62865c91-b67e"
8last-modified: Thu, 19 May 2022 15:04:49 GMT
9server: nginx/1.19.2

J’obtiens un code 200: l’application est accessible

Sur le serveur, j’ajoute l’IP publique de mon poste client dans Crowdsec

1$ cscli decisions add --ip xxx.xxx.xxx.xxx
2INFO[30-07-2022 04:38:32 PM] Decision successfully added
3
4$ cscli decision list
5+--------+--------+-------------------+------------------------------+--------+---------+----+--------+--------------------+----------+
6|   ID   | SOURCE |    SCOPE:VALUE    |            REASON            | ACTION | COUNTRY | AS | EVENTS |     EXPIRATION     | ALERT ID |
7+--------+--------+-------------------+------------------------------+--------+---------+----+--------+--------------------+----------+
8| 154892 | cscli  | Ip:xx.xx.xx.xx    | manual 'ban' from 'crowdsec' | ban    |         |    |      1 | 3h59m32.102173374s |       18 |
9+--------+--------+-------------------+------------------------------+--------+---------+----+--------+--------------------+----------+

Sur mon poste client, je retente l’accès à l’application

1curl -I https://app.domain.tld
2HTTP/2 403
3content-type: text/plain; charset=utf-8
4date: Sat, 30 Jul 2022 16:41:07 GMT
5content-length: 9

Cette fois, j’obtiens un code 403: l’accès est bien réfusé !

Pour lever le banissement, il suffit de faire:

1cscli decision delete -i xxx.xxx.xxx.xxx
2INFO[30-07-2022 04:44:45 PM] 1 decision(s) deleted  

Simuler une attaque

Sur mon poste client, je vais utiliser un outil de pentest courant: dirsearch

1dirsearch  -u https://app.domain.tld

Sur le serveur, la réponse de Crowdsec ne se fait pas attendre:

1cscli decision list
2+--------+----------+------------------+------------------------------------+--------+---------+--------------+--------+--------------------+----------+
3|   ID   |  SOURCE  |   SCOPE:VALUE    |               REASON               | ACTION | COUNTRY |      AS      | EVENTS |     EXPIRATION     | ALERT ID |
4+--------+----------+------------------+------------------------------------+--------+---------+--------------+--------+--------------------+----------+
5| 154895 | crowdsec | Ip:xx.xx.xx.xx   | crowdsecurity/http-sensitive-files | ban    | FR      | xxxx xxxxxxx |      5 | 3h59m55.854995367s |       21 |
6+--------+----------+------------------+------------------------------------+--------+---------+--------------+--------+--------------------+----------+

L’adresse IP du client est bannie pour 4h !

Explorer une attaque

 1$ cscli decisions list
 2+-------+----------+------------------+-----------------------------------+--------+---------+--------------------------------+--------+------------------+----------+
 3|  ID   |  SOURCE  |   SCOPE:VALUE    |              REASON               | ACTION | COUNTRY |               AS               | EVENTS |    EXPIRATION    | ALERT ID |
 4+-------+----------+------------------+-----------------------------------+--------+---------+--------------------------------+--------+------------------+----------+
 5| 98825 | crowdsec | Ip:185.7.214.104 | crowdsecurity/http-cve-2021-41773 | ban    | RU      | 57523 Chang Way Technologies   |      1 | 3h5m7.769336653s |       13 |
 6|       |          |                  |                                   |        |         | Co. Limited                    |        |                  |          |
 7+-------+----------+------------------+-----------------------------------+--------+---------+--------------------------------+--------+------------------+----------+
 8
 9
10$ cscli alerts inspect -d 13
11################################################################################################
12
13 - ID         : 13
14 - Date       : 2022-07-30T09:28:43Z
15 - Machine    : crowdsec
16 - Simulation : false
17 - Reason     : crowdsecurity/http-cve-2021-41773
18 - Events Count : 1
19 - Scope:Value: Ip:185.7.214.104
20 - Country    : RU
21 - AS         : Chang Way Technologies Co. Limited
22 - Begin      : 2022-07-30 09:28:42.9820528 +0000 UTC
23 - End        : 2022-07-30 09:28:42.98205299 +0000 UTC
24
25 - Active Decisions  :
26+-------+------------------+--------+-----------------+----------------------+
27|  ID   |   SCOPE:VALUE    | ACTION |   EXPIRATION    |      CREATED AT      |
28+-------+------------------+--------+-----------------+----------------------+
29| 98825 | Ip:185.7.214.104 | ban    | 4m37.095425038s | 2022-07-30T09:28:43Z |
30+-------+------------------+--------+-----------------+----------------------+
31
32 - Events  :
33
34- Date: 2022-07-30 09:28:42 +0000 UTC
35+---------------------+-------------------------------------+
36|         KEY         |                VALUE                |
37+---------------------+-------------------------------------+
38| ASNNumber           |                               57523 |
39| ASNOrg              | Chang Way Technologies Co.          |
40|                     | Limited                             |
41| IsInEU              | false                               |
42| IsoCode             | RU                                  |
43| SourceRange         | 185.7.214.0/24                      |
44| datasource_path     | /var/log/traefik/traefix-access.log |
45| datasource_type     | file                                |
46| http_args_len       |                                   0 |
47| http_path           | /cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh |
48| http_status         |                                 308 |
49| log_type            | http_access-log                     |
50| service             | http                                |
51| source_ip           | 185.7.214.104                       |
52| timestamp           | 2022-07-30T09:28:42Z                |
53| traefik_router_name | web-to-websecure@internal           |
54| user                | -                                   |
55+---------------------+-------------------------------------+

Crowdsec fournit des informations précieuses comme la date de début et de fin de l’attaque, l’origine de l’attaque et le motif du bannissement.

Allergique à la ligne de commande ?

Personnellement, je trouve la ligne de commande cscli vraiment bien faite et facile à prendre en main mais si vous êtes allergique, Crowdsec propose deux solutions :

Crowdsec Console

Pour utiliser ce service, il suffit de créer un compte et saisir sur votre serveur la commande indiquée après authentification:

1sudo cscli console enroll xxxxxxxxxxxxxxxxxx

Ca consomme beaucoup ?

Crowdsec est une solution très légère avec une empreinte mémoire de seulement 114 Mo pour les deux conteneurs.

1 NAME                   CID             CPU    MEM           NET RX/TX   IO R/W    PIDS
2  ◉  crowdsec           038d042adccd     0%    97M / 1.89G   4M / 3M     0B / 0B    40
3✚ ◉  crowdsec-bouncer   6304ff58a3f5     0%    17M / 1.89G   7M / 3M     0B / 0B     5
4  ◉  traefik            dc12a4f5679d     0%    50M / 1.89G   70M / 75M   0B / 0B     9

Conclusion

J’ai rarement vu une solution IPS aussi facile à mettre en place et avec une efficacité redoutable ! L’aspect communautaire avec le réseau global de réputation est un plus indéniable. En moins de 48h, Crowdsec a déjà déjoué 8 attaques sur mon humble serveur. Enfin, sachez qu’il existe des plugins de notifications par mail, Slack, Telegram….

Photo by Nahel Abdul Hadi on Unsplash