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 :
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:
- 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 logscscli 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 :
- un dashboard local basé sur Metabase
- une console web en mode SAAS : https://www.crowdsec.net/product/console avec une offre gratuite largement suffisante pour superviser une infrastructure de quelques serveurs:
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