Featured image of post Threat Intelligence avec Crowdsec

Threat Intelligence avec Crowdsec

Je mets en place Crowdsec pour sécuriser les échanges de mon reverse-proxy (Traefik) avec mes applications.

Introduction

Le serveur sur lequel est hébergé ce site est un serveur public. Ce dernier subit pas mal de tentative d’intrusion.

Les seuls que j’identifie et traite sont les tentatives de connexions SSH avec fail2ban.

On peut voir que sur une heure, il travaille déjà pas mal :

grep Ban /var/log/fail2ban.log | tail

2025-03-08 14:05:26,445 fail2ban.actions [759]: NOTICE [sshd] Ban 45.119.81.249
2025-03-08 14:13:23,740 fail2ban.actions [759]: NOTICE [sshd] Ban 218.92.0.197
2025-03-08 14:18:30,694 fail2ban.actions [759]: NOTICE [sshd] Ban 45.162.145.14
2025-03-08 14:23:39,855 fail2ban.actions [759]: NOTICE [sshd] Ban 45.119.81.249
2025-03-08 14:25:42,066 fail2ban.actions [759]: NOTICE [sshd] Ban 218.92.0.197
2025-03-08 14:38:15,677 fail2ban.actions [759]: NOTICE [sshd] Ban 218.92.0.197
2025-03-08 14:46:55,067 fail2ban.actions [759]: NOTICE [sshd] Ban 58.34.135.138
2025-03-08 14:50:47,738 fail2ban.actions [759]: NOTICE [sshd] Ban 125.124.176.254
2025-03-08 14:51:55,085 fail2ban.actions [759]: NOTICE [sshd] Ban 218.92.0.197
2025-03-08 15:03:29,375 fail2ban.actions [759]: NOTICE [sshd] Ban 218.92.0.197

L’autre porte ouverte de mon serveur est la porte du Web, l’HTTP(s).

Je n’avais pas de connaissances sur les protections possibles sur le sujet. J’ai découvert Crowdsec mais je n’étais pas trop emballé par l’aspect communautaire du truc.
Après un peu de recherche, j’ai compris l’intérêt de ce mode de fonctionnement.

En effet, Crowdsec se base sur les remontés de la communauté afin de traiter plus efficacement les menaces. Pour prendre un exemple simple, si une menace est identifiée par plusieurs utilisateurs, on bénéficie de cette information pour traiter immédiatement le problème s’il se produit dans notre environnement.

Dans mes recherches, j’ai vu que c’était bien fait pour être intégrable avec mon reverse-proxy Traefik. Comme tout mon trafic Web passe dessus, c’est plutôt pratique.
Nous allons donc voir ici comment mettre cela en place.

Mise en place

Fichier crowdsec/compose.yml :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    environment:
      GID: "${GID-1000}"
      COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik"
    security_opt:
      - no-new-privileges:true
    volumes:
      - /etc/crowdsec:/etc/crowdsec/
      - /var/lib/crowdsec/data:/var/lib/crowdsec/data/
      - /var/log/traefik/:/var/log/traefik/:ro
    networks:
      - traefik-net

networks:
  traefik-net:
    external: true
    name: traefik-net

Pour fonctionner avec Traefik, il faut qu’il soit dans le même réseau que ce dernier. Si comme moi, votre composition n’est pas dans celui de Treafik, vous devez spécifier ce réseau externe.
La variable d’environnement COLLECTIONS va préciser des ensembles de règles d’analyses et de scénarios rassemblés en collections. La liste des collections est disponible sur le Hub de Crowdsec.
On ajoute aussi le dossier de logs de Traefik afin que Crowdsec puissent les lire une fois la configuration faite.

Ensuite, on ajoute le fichier de configuration pour Traefik (acquisition par fichier) dans un fichier /etc/crowdsec/acquis.d/traefik.yaml :

filenames:
  - "/var/log/traefik/*.log"
labels:
  type: "traefik"

On démarre donc notre conteneur et on vérifie les logs.

docker compose up -d
docker compose logs

Configuration

Crowdsec

Pour que Crowdsec fonctionne pour Traefik, il faut décider quoi faire avec les données relevées. C’est le rôle d’un décisionnaire que Crowdsec baptise bouncer (“videur” dans le sens “videur de boîte de nuit”).

Pour cela, on va ajouter le bouncer pour Traefik qui est un conteneur.

Je souhaite le mettre dans mon dossier Crowdsec mais ne pas l’ajouter au fichier compose.yml mais dans un fichier différent.
J’ai pensé à faire un fichier compose.override.yml mais cela n’est pas très scalable si je continue j’ajouter des bouncers.

La solution est de passer par un fichier d’environnement. En créant un fichier .env dans le dossier, on peut y configurer une liste de fichier à utiliser par Docker Compose.

Mon fichier crowdsec/.env :

COMPOSE_FILE='compose.yml,traefik.yml'
COMPOSE_PATH_SEPARATOR=','

Et donc mon fichier traefik.yml :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
services:
  bouncer-traefik:
    image: docker.io/fbonalair/traefik-crowdsec-bouncer:latest
    container_name: bouncer-traefik
    restart: unless-stopped
    environment:
      CROWDSEC_BOUNCER_API_KEY: my-api-key
      CROWDSEC_AGENT_HOST: crowdsec:8080
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik-net
    depends_on:
      - crowdsec

Pour obtenir la clé d’API, il va falloir faire un cscli bouncers add <my-bouncer> dans le conteneur.

Je l’ai fait ainsi depuis l’hôte :

docker exec crowdsec cscli bouncers add bouncer-traefik

Avertissement

Comme l’indique le message qui suit cette commande, la clé d’API n’est donnée qu’une seule fois. Pensez donc bien à le noter quelque part.

Traefik

Maintenant que la partie Crowdsec est prêt à traiter les requêtes typées Traefik, il faut que Traefik les transmettent au bouncer.

Pour cela, il faut déléguer les authentifications au bouncer via la définition d’un middleware de type ForwardAuth.

Pour ma part, je le fais en configuration dynamique :

Fichier traefik/config/dynamique/crowdsec.yml :

http:
  middlewares:
    crowdsec-bouncer:
      forwardauth:
        address: http://bouncer-traefik:8080/api/v1/forwardAuth
        trustForwardHeader: true

Utilisation

Une fois tout cela fait, il suffit de démarrer le bouncer et de redémarrer Traefik.

Une fois cela fait, il faut bien vérifier que nos applications sont toujours accessibles.

Ci-dessous, une liste de commandes Crowdsec.

# Lister les métriques
docker exec crowdsec cscli metrics

# Voir les banissements
docker exec crowdsec cscli decisions list

# Mettre à jour du hub
docker exec crowdsec cscli hub update
docker exec crowdsec cscli hub upgrade

# Banir/débanir une adresse IP
docker exec crowdsec cscli decisions add --ip 192.168.17.92
docker exec crowdsec cscli decisions delete --ip 192.168.17.92

Comme vous pouvez le déduire, il faut de temps en temps faire un update/upgrade. J’ai ajouté une tâche CRON pour cela.

Fichier /etc/cron.d/crowdsec_update_hub :

@daily root /usr/bin/docker exec crowdsec cscli hub update && /usr/bin/docker exec crowdsec cscli hub upgrade

Pour comprendre ce qui se passe, au lieu de spammer cscli decisions list, j’ai ajouté une notification via Gotify.

En suivant la documentation, mon fichier /etc/crowdsec/notifications/http.yaml :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type: http          # Don't change
name: http_default # Must match the registered plugin in the profile

# One of "trace", "debug", "info", "warn", "error", "off"
log_level: info

# group_wait:         # Time to wait collecting alerts before relaying a message to this plugin, eg "30s"
# group_threshold:    # Amount of alerts that triggers a message before <group_wait> has expired, eg "10"
# max_retry:          # Number of attempts to relay messages to plugins in case of error
# timeout:            # Time to wait for response from the plugin before considering the attempt a failure, eg "10s"

#-------------------------
# plugin-specific options

# The following template receives a list of models.Alert objects
# The output goes in the http request body
format: |
  {{ range . -}}
  {{ $alert := . -}}
  {
    "extras": {
      "client::display": {
      "contentType": "text/markdown"
    }
  },
  "priority": 3,
  {{range .Decisions -}}
  "title": "{{.Type }} {{ .Value }} for {{.Duration}}",
  "message": "{{.Scenario}}  \n\n[crowdsec cti](https://app.crowdsec.net/cti/{{.Value -}})  \n\n[shodan](https://shodan.io/host/{{.Value -}})"
  {{end -}}
  }
  {{ end -}}

# The plugin will make requests to this url, eg:  https://www.example.com/
url: https://<GOTFIY_URL>/message

# Any of the http verbs: "POST", "GET", "PUT"...
method: POST

headers:
  X-Gotify-Key: <GOTIFY_API_KEY>
  Content-Type: application/json
# skip_tls_verification:  # true or false. Default is false

Et je me rends compte que cela fonctionne :

Notifications Gotify de Crowdsec

Remarque

Il existe aussi un dashboard mais je n’ai pas voulu le mettre en place.

Post-installation

Whitelisting

En publiant cet article, mon CI c’est donc lancé et a eu pour conséquence le bannissement de mon adresse IP !

Pour y remédier, j’ai suivis la doc en créant le fichier /etc/crowdsec/parsers/s02-enrich/my-whitelist.yaml :

name: perso/whitelist
description: "My Whitelist"
whitelist:
  reason: "Moi moi et moi ipv4/ipv6 ip/ranges"
  ip:
    - "92.17.4.23"
  cidr:
    - "cafe:cafe:cafe:cafe::/64"

Ici, je whitelist mon IPv4 publique ainsi que mon préfixe IPv6 publique.

Conclusion

J’ai donc réussi à apporter un premier niveau de filtrage à mes applications exposées en Web.

En vue de ce que je vois sur fail2ban, finalement, je ne suis malheureusement pas très choqué de voir le nombre de bannissements.
Ainsi, je me rends compte que cela n’est que le début. Je pourrais aller plus loin en visant les applications comme Nginx ou autres.
Il y aura donc probablement une suite à cet article.

Licensed under CC BY-NC-SA 4.0
Dernière mise à jour le 2025-03-11 21:40:00
Généré avec Hugo
Thème Stack conçu par Jimmy