Aller au contenu

🛡️ Sécurisation avec Crowdsec

cs-ban

📔 Introduction

Crowdsec est un IDS/IPS communautaire puissant et relativement simple à déployer. Nous allons voir comment l'intégrer à Traefik afin de bloquer les IP malveillantes que cela soit en mode préventif ou en mode détection.

Dans le cadre de cette documentation, je pars du principe que vous avez déjà déployé un serveur Crowdsec. Si ce n'est pas le cas, vous pouvez vous inspirer de la série d'articles que j'avais rédigé ici. Si vous êtes patient, il est prévu que je reprenne cette série d'articles pour la mettre à jour.

Si ce n'est pas le cas, vous pouvez faire tourner le serveur Crowdsec directement dans Docker Swarm.

Nous en profiterons pour déployer une nouvelle fonctionnalité de Crowdsec : AppSec. AppSec est un composant qui est utilisé pour protéger les applications Web. Cela permet tout simplement de mettre en place un WAF. Et ça c'est génial ! En effet, en plus de détecter par le bais des parsers, de partager avec la communauté Crowdsec et de bloquer les IP malveillantes à travers les bouncers, cette nouvelle fonctionnalité va nous permettre de bénéficier par exemple du mécanisme de virtual pacthing et de la collection bien fournie qui va avec.

Virtual Patching

Lorsque des vulnérabilités sont découvertes sur une application, l’éditeur produit en général un correctif, ou « patch », permettant de corriger les vulnérabilités ou de les rendre inexploitables. Cependant, l’application de ce correctif, nécessite souvent une mise à jour du logiciel en question et celle-ci peut être relativement compliqué dans certains cas (coupure d’activité impossible…). Sans compter le temps de la sortie du patch...

Le virtual patching permet alors de protéger les applications vulnérables beaucoup plus rapidement. Il consiste à développer un correctif protégeant les les applications sans toucher au code des applications.

Configuration du serveur Crowdsec

Appsec

Il faut commencer par vérifier la prise en charge des fichiers contenus dans "/etc/crowdsec/acquis.d". En effet, si vous faites tourner Crowdsec depuis quelques années, il se peut que le fichier de configuration ne soit pas à jour avec les dernières directives.

Configuration du composant Appsec
# Vérification de présence du dossier acquis.d
root@srv-cs:~# ls -d /etc/crowdsec/acquis.d
/etc/crowdsec/acquis.d

# Vérification de la prise en charge de ce dossier
root@srv-cs:~# cat /etc/crowdsec/config.yaml
(...)
crowdsec_service:
  acquisition_path: /etc/crowdsec/acquis.yaml
  acquisition_dir: /etc/crowdsec/acquis.d
  parser_routines: 1
(...)

# Installation des collections pour Appsec
root@srv-cs:~# cscli collections install crowdsecurity/appsec-virtual-patching
root@srv-cs:~# cscli collections install crowdsecurity/appsec-generic-rules

# Configuration de Appsec
root@srv-cs:~# vim /etc/crowdsec/acquis.d/appsec.yaml

listen_addr: IP_LOCALE_DU_SERVEUR_CS:7422
appsec_config: crowdsecurity/appsec-default
name: myAppSecComponent
source: appsec
labels:
  type: appsec

# Relance de Crowdsec
root@srv-cs:~# systemctl restart crowdsec

# Vérification
root@srv-cs:~# tail -f /var/log/crowdsec.log
time="2024-06-02T10:47:33+02:00" level=info msg="1 appsec runner to start" name=myAppSecComponent type=appsec
time="2024-06-02T10:47:33+02:00" level=info msg="creating TCP server on 192.168.1.9:7422" name=myAppSecComponent type=appsec
time="2024-06-02T10:47:33+02:00" level=info msg="Appsec Runner ready to process event" name=myAppSecComponent runner_uuid=8a4e00b6-8805-49cf-ae17-41abc191dd90 type=appsec

Les collections installées fournissent :

  • La configuration du composant AppSec (crowdsecurity/appsec-default).
  • Toutes les règles de patch virtuel.
  • Les parsers.
  • Le(s) scénario(s).

Les bouncers interrogeront le serveur sur la socket "IP_LOCALE_DU_SERVEUR_CS:7422" à chaque requête vers les applications web. Suivant le traffic de vos applications et vos ressources, il faudra donc veiller à régler le mode pour interroger le serveur CS (Stream, Live...).

Si la requête correspond à une règle contenue dans AppSec alors ce dernier répondra par un code HTTP 403 à Traefik indiquant que la requête doit être bloquée. Traefik présentera alors la page "Crowdsec Access Forbidden" fournie par le bouncer.

Pour faire simple : Crowdsec AppSec transforme ici Traefik en firewall d'applications web.

Ajout de la machine agent

Serveur Crowdsec Local API

Le serveur Crowdsec Local API est en charge de centraliser les alertes et les décisions remontées par les clients dits machines, ces dernier faisant tourner les agents Crowdsec. Par souci de simplicité, j'utiliserai "serveur Crowdsec" dans la documentation au lieu de "serveur Crowdsec Local API".

La machine agent, permettant de parser les logs de Traefik et faire remonter les informations au serveur Crowdsec, tournera sur le cluster Swarm.

Selon la documentation officielle, il faut ajouter l'agent directement sur le serveur Crowdsec. Cette partie n'était d'ailleurs pas très claire. Dans tous les cas, il faut préciser une sortie fichier sinon cela écrasera le fichier "/etc/crowdsec/local_api_credentials.yaml" du serveur ou retournera une erreur.

Création de la machine sur le serveur CS
root@srv-cs:~# cscli machines add agent-traefik --auto -f /root/agent-traefik.yaml
Nous verrons plus bas comment intégrer les informations générées dans le fichier "/root/agent-traefik.yaml".

Ajout du bouncer

Il faut maintenant déclarer sur le serveur Crowdsec le bouncer. Nous verrons plus tard comment configrer celui-ci avec Traefik.

Création du bouncersur le serveur CS
root@srv-cs:~# cscli bouncers add traefik-bouncer

Conservez la clé pour la configuration du bouncer plus tard.

Déploiement de Crowdec sur Docker Swarm et Traefik

Configuration et déploiement de l'agent crowdsec

Préparation de l'arborescence
olivier@ds01:/mnt/nfsdatas$ sudo mkdir crowdsec
olivier@ds01:/mnt/nfsdatas$ sudo chown olivier: crowdsec
olivier@ds01:/mnt/nfsdatas$ cd crowdsec/
Configuration du service /mnt/nfsdatas/crowdsec/docker-stack.yml
services:
  crowdsec-agent:
    image: crowdsecurity/crowdsec
    environment:
      COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve"
      GID: "${GID-1000}"
      DISABLE_LOCAL_API: "true"
      TZ: "Europe/Paris"
      # Informations récupérées depuis le fichier /root/agent-traefik.yaml sur le serveur CS
      AGENT_USERNAME: "agent-traefik"
      AGENT_PASSWORD: "PASSWORD"
      LOCAL_API_URL: "http://IP_DU_SERVEUR_CROWDSEC:8080"
    volumes:
      - ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro # Fichier permettant de déclarer les logs de Traefik
      - crowdsec-db:/var/lib/crowdsec/data/
      - crowdsec-config:/etc/crowdsec/
      # Dossier contenant les fichiers de logs de Traefik
      - /mnt/nfsdatas/traefik/traefik/log/:/var/log/traefik/:ro
    deploy:
      placement:
        constraints:
          - node.role == manager
      restart_policy:
        condition: on-failure
      replicas: 1

volumes:
  crowdsec-config:
  crowdsec-db: 

```yaml title="Configuration de /mnt/nfsdatas/crowdsec/acquis.yaml"
olivier@ds01:/mnt/nfsdatas/crowdsec$ vim acquis.yaml

filenames:
  - /var/log/traefik/*
labels:
  type: traefik
Lancement du service
olivier@ds01:/mnt/nfsdatas/crowdsec$ docker stack deploy -c docker-stack.yml -d cs-agent

Configuration et déploiement du bouncer Traefik

Déclaration du plugin Crowsdec
# À la fin du fichier de conf statique de Traefik 
# /mnt/nfsdatas/traefik/traefik/traefik.yml
(...)
experimental:
  plugins:
    bouncer:
      moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
      version: v1.3.1
Configuration du plugin Crowsdec
# Editez le fichier de config dynamique
# /mnt/nfsdatas/traefik/traefik/dyn_traefik/traefik.config.yml

http:
  middlewares:
    crowdsec:
      plugin:
        bouncer:
          enabled: true
          logLevel: DEBUG
          CrowdsecMode: stream # Recommandé pour les performances
          ## Avec le mode stream, les décisions sont contenues dans le cache, màj toutes les 60 sec.
          ## Redis peut être utilisé pour ce cache.
          # Config d'AppSec
          CrowdsecAppsecEnabled: true
          CrowdsecAppsecHost: IP_SERVEUR_CROWDSEC:7422
          CrowdsecAppsecFailureBlock: true
          # Config de LAPI
          CrowdsecLapiScheme: http
          CrowdsecLapiTLSInsecureVerify: false
          CrowdsecLapiHost: IP_SERVEUR_CROWDSEC:8080
          # Déclaration de la clé crée lors de l'ajout du bouncer sur le serveur CS
          CrowdsecLapiKey: LA_CLE_DU_BOUNCER
          banHTMLFilePath: ./ban.html # Ce fichier sera mappé dans la stack de Traefik

Mappage du fichier ban.html

Récupérez le fichier ban.html sur github

Mapping du fichier ban.html
# /mnt/nfsdatas/traefik/docker-stack.yml
version: "3.3"
services:
  traefik:
    image: "traefik:v3.0.1"
    (...)
    volumes:
      (...)
      # Mapping du fichier ban de crowdsec
      - "./traefik/crowdsec/ban.html:/ban.html"
(...)

Déclaration du middleware Crowdsec

Avant de relancer Traefik, il faut déclarer le middleware crowdsec@file sur nos applications web. Ici nous renforcerons la sécurité du dashboard de Traefik ainsi que celle de l'application très sympa Cobalt qui permet de récupérer des vidéos/audios publiées sur les réseaux sociaux.

Accès au dashboard de Traefik

Si vous n'avez pas besoin d'exposer le dasboard de Traefik sur Internet ou/et que vous souhaitez également limiter les accès internes (zero trust), utilisez l'option "ipallowlist.sourcerange" de Traefik permettant de limiter les accès aux IP autorisées.

Déclaration du middleware sur Traefik
# Sur le dashboard de Crowdsec
# /mnt/nfsdatas/traefik/docker-stack.yml
version: "3.3"
services:
  traefik:
    image: "traefik:v3.0.1"
    (...)
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.traefik-dashboard.rule=Host(`d.dev.quercylibre.fr`)"
        - "traefik.http.routers.traefik-dashboard.service=api@internal"
        - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
        - "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
        # Ajout du middleware crowdsec@file
        - "traefik.http.routers.traefik-dashboard.middlewares=traefik-dashboard-auth,crowdsec@file"
    (...)
Déclaration du middleware sur l'application web Cobalt
services:
  cobalt-api:
    image: ghcr.io/wukko/cobalt:7
    init: true
    networks:
      - web
    environment:
      API_URL: "https://cobalt-api.dev.quercylibre.fr/"
      API_NAME: "eu-fr"
    deploy:    
      restart_policy:    
        condition: on-failure      
      replicas: 1    
      placement:    
         constraints:   
            - node.hostname == ds03
      labels:    
        - "traefik.enable=true"    
        - "traefik.docker.network=web"    
        - "traefik.http.routers.cobalt-api.rule=Host(`cobalt-api.dev.quercylibre.fr`)"    
        - "traefik.http.routers.cobalt-api.tls=true"    
        - "traefik.http.routers.cobalt-api.tls.certresolver=letsencrypt"    
        - "traefik.http.routers.cobalt-api.middlewares=crowdsec@file"
        - "traefik.http.services.cobalt-api-service.loadbalancer.server.port=9000"    

  cobalt-web:
    image: ghcr.io/wukko/cobalt:7
    init: true
    networks:
      - web
    environment:
      WEB_URL: "https://cobalt.dev.quercylibre.fr/"
      API_URL: "https://cobalt-api.dev.quercylibre.fr/"
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      placement:
         constraints: 
            - node.hostname == ds03
      labels:
        - "traefik.enable=true"
        - "traefik.docker.network=web"
        - "traefik.http.routers.cobalt-web.rule=Host(`cobalt.dev.quercylibre.fr`)"
        - "traefik.http.routers.cobalt-web.tls=true"
        - "traefik.http.routers.cobalt-web.tls.certresolver=letsencrypt"
        - "traefik.http.routers.cobalt-web.middlewares=crowdsec@file"
        - "traefik.http.services.cobalt-web-service.loadbalancer.server.port=9001"

networks:
  web:
    external: true

Une fois les stacks configurées, il faut les relancer.

Nous retrouvons alors sur le dashboard Traefik le bouncer CS déclaré en tant que middleware :

traefik-bouncer

Test

Pour cela je vais saisir l'url : "https://cobalt.dev.quercylibre.fr/rpc2" Résultat :

test-van

Nous retrouvons l'alerte dans les logs du fichier /var/log/crowdsec.log sur le serveur Crowdsec avec la règle AppSec correspondante :

Logs d'alerte sur Crowdsec
root@srv-cs:~# tail -f /var/log/crowdsec.log
time="2024-06-02T11:42:19+02:00" level=info msg="AppSec block: crowdsecurity/vpatch-CVE-2023-42793 from IP_MALVEILLANTE (IP_CS_BOUNCER)"
# Le warning ci-dessous est normal vu que j'utilise une IP privée
time="2024-06-02T11:42:19+02:00" level=warning msg="No range found for ip 'IP_MALVEILLANTE'" id=nameless-flower method=IpToRange name=crowdsecurity/geoip-enrich stage=s02-enrich
time="2024-06-02T11:42:20+02:00" level=info msg="(45be54d228044cac8ebdc84de749a2617rDlp8mTTjYW2RbS) alert : crowdsecurity/vpatch-CVE-2023-42793 by ip IP_MALVEILLANTE (/0)"

Ici nous voyons la puissance du composant Crowdsec qui permet de bloquer en préventif l'attaque. Seule l'attaque est bloquée dans un premier temps, mais si l'attaque persiste, cela déclenche alors un ban de l'IP.

Vous pouvez également tester d'autres scénarios d'attaque avec la commande "nikto -host URL".

Sources

Plugin Crowdsec Traefik