Aller au contenu

🛡️ Sécurisation de l'accès à Teleport

🛡️ Sécurisation avec Crowdsec

Je vais partir du principe qu'il n'existe pas de serveur Crowdsec déjà installé. L'installation de Crowdsec se fera uniquement avec Docker.

Préparation de l'arborescence de Crowdsec
mkdir -p crowdsec/crowdsec/data
cd crowdsec/
crowdsec/compose.yml
---
networks:
  crowdsec:
    external: true

services:
  crowdsec-agent:
    image: crowdsecurity/crowdsec:latest-debian
    container_name: crowdsec
    networks:
      - crowdsec
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./crowdsec:/etc/crowdsec
      - ./crowdsec/data:/var/lib/crowdsec/data
      # log bind mounts into crowdsec
      - /srv/docker/traefik/logs/:/var/log/traefik/:ro
      - /srv/docker/teleport/data/log/:/var/log/teleport/:ro
    environment:
      COLLECTIONS: "crowdsecurity/traefik crowdsecurity/teleport crowdsecurity/http-cve crowdsecurity/http-dos crowdsecurity/base-http-scenarios crowdsecurity/appsec-generic-rules crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-crs"
      GID: "${GID-1000}"
      TZ: "Europe/Paris"
      BOUNCER_KEY_traefik: ga89tRRpn9HDPORyQrvV4KuCngm4UXuaLkIuya/Kc2LwqJFET0xJwH1+hA== # <- Voir ci-dessous pour générer la clé
    restart: unless-stopped
Génération de la clé du bouncer traefik
openssl rand -base64 43
# Sortie
ga89tRRpn9HDPORyQrvV4KuCngm4UXuaLkIuya/Kc2LwqJFET0xJwH1+hA==
Création du réseau crowdsec
docker network create crowdsec

Configuration des "acquis" Crowdsec

crowdsec/acquis.yaml
listen_addr: 0.0.0.0:7422
appsec_config: crowdsecurity/appsec-default
name: myAppSecComponent
source: appsec
labels:
  type: appsec
crowdsec/acquis.d/traefik.yaml
poll_without_inotify: false
filenames:
  - /var/log/traefik/*.log
labels:
  type: traefik
crowdsec/acquis.d/teleport.yaml
filenames:
  - /var/log/teleport/*/*.log
labels:
  type: teleport

🛠️ Configuration de Traefik

Se placer dans le répertoire de travail docker de Traefik.

Configuration du conteneur Traefik

Récupération du fichier ban.html
mkdir config/crowdsec
wget -O config/crowdsec/ban.html https://raw.githubusercontent.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/refs/heads/main/examples/custom-ban-page/ban.html
Configuration du réseau Crowdsec et mapping du fichier ban.html
# traefik/compose.yml
---
networks:
  traefik:
    external: true
  crowdsec:
    external: true
(...)
services:
  traefik:
    image: "traefik:v3"
    networks:
      - traefik
      - crowdsec
    (...)
    volumes:
      (...)
      # Mapping du fichier ban de crowdsec
      - "./traefik/crowdsec/ban.html:/ban.html"
(...)

Redémarrage du conteneur Traefik : docker compose down && docker compose up -d

Une fois relancé, lancez le conteneur crowdsec.

Si on vérifie le bouncer créé précédemment nous verrons que ce dernier est valide sur Crowdsec mais qu'il n'a pas encore communiqué.

Vérification de la présence du bouncer
docker exec crowdsec cscli bouncers list
---------------------------------------------------------------------
 Name     IP Address  Valid  Last API pull  Type  Version  Auth Type 
---------------------------------------------------------------------
 traefik              ✔️                                   api-key   
---------------------------------------------------------------------

Il nous reste donc à configurer le bouncer sur Traefik.

Création du middleware Crowdsec

config/dyn_traefik/crowdsec.yaml
---
http:
  middlewares:
    crowdsec:
      plugin:
        bouncer:
          enabled: true
          logLevel: DEBUG # Pour vérifier au début le bon fonctionnement avec docker logs traefik -f / Passer à INFO si OK 
          defaultDecisionSeconds: 60
          crowdsecMode: live
          crowdsecAppsecEnabled: true # <--- activation du waf appsec
          crowdsecAppsecHost: crowdsec:7422
          crowdsecAppsecFailureBlock: true
          crowdsecAppsecUnreachableBlock: true
          crowdsecLapiKey: ga89tRRpn9HDPORyQrvV4KuCngm4UXuaLkIuya/Kc2LwqJFET0xJwH1+hA== # key du bouncer
          crowdsecLapiHost: crowdsec:8080
          crowdsecLapiScheme: http
          crowdsecLapiTLSInsecureVerify: false
          banHTMLFilePath: ./ban.html # Ce fichier sera mappé dans la stack de Traefik

🛠️ Configuration du middleware sur Teleport

Modifiez la configuration du compose.yml de Teleport en ajoutant la ligne traefik.http.routers.teleport-prod-https.middlewares

teleport/compose.yml
---
---
networks:
  traefik:
    external: true
services:
  teleport:
    image: public.ecr.aws/gravitational/teleport-distroless:17.4.2
    container_name: teleport
    volumes:
      - ./config:/etc/teleport
      - ./data:/var/lib/teleport
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.teleport-prod.loadbalancer.server.port=3080"
      - "traefik.http.services.teleport-prod.loadbalancer.server.scheme=https"
      - "traefik.http.routers.teleport-prod-https.entrypoints=websecure"
      - "traefik.http.routers.teleport-prod-https.rule=Host(`teleport.prod-1.domaine.fr`) || HostRegexp(`^.+.teleport.prod-1.domaine.fr$`)"
      - "traefik.http.routers.teleport-prod-https.middlewares=crowdsec@file"
      - "traefik.http.routers.teleport-prod-https.tls=true"
      - "traefik.http.routers.teleport-prod-https.tls.certresolver=letsencrypt"
      - "traefik.http.routers.teleport-prod-https.tls.domains[0].main=teleport.prod-1.domaine.fr"
      - "traefik.http.routers.teleport-prod-https.tls.domains[0].sans=*.teleport.prod-1.domaine.fr"
    networks:
      - traefik
    restart: unless-stopped

Redémarrage du conteneur : docker compose down && docker compose up -d

📝 Vérifications

En utilisant la commande suivante vous devez voir la dernière connexion du bouncer traefik à Crowdsec:

Vérification de la présence du bouncer
docker exec crowdsec cscli bouncer list
------------------------------------------------------------------------------------------------------------------
 Name                IP Address  Valid  Last API pull         Type                             Version  Auth Type 
------------------------------------------------------------------------------------------------------------------ 
 traefik@172.20.0.2  172.20.0.2  ✔️     2025-04-12T12:28:03Z  Crowdsec-Bouncer-Traefik-Plugin  1.X.X    api-key   
------------------------------------------------------------------------------------------------------------------
Si vous vous connectez sur l'URL modifiée suivante de Teleport : https://teleport.prod-1.domaine.fr/.env, vous devriez alors obtenir une alerte générée par AppSec :

Crowdsec Appsec

Et une alerte dans les logs de Crowdsec (docker logs crowdsec -f) :

time="2025-04-12T15:11:06+02:00" level=info msg="AppSec block: crowdsecurity/vpatch-env-access from 192.168.1.100 (172.20.0.2)"
time="2025-04-12T15:11:07+02:00" level=info msg="(localhost) alert : crowdsecurity/vpatch-env-access by ip 192.168.1.100 (/0)"
time="2025-04-12T15:11:07+02:00" level=info msg="127.0.0.1 - [Sat, 12 Apr 2025 15:11:07 CEST] \"POST /v1/alerts HTTP/1.1 201 666.631104ms \"crowdsec/v1.6.8-f209766e-docker\" \""
time="2025-04-12T15:11:13+02:00" level=info msg="Signal push: 1 signals to push"

🐞 Bug

Le parser Teleport fourni par Crowdsec ne semble pas fonctionner correctement avec la version Docker de Teleport comme visible dans les logs de Crowdsec remontés par la partie agent de ce dernier :

time="2025-04-12T15:15:30+02:00" level=warning msg="failed to run RunTimeValue : interface conversion: interface {} is nil, not bool (1:1)\n | evt.Unmarshaled.teleport.success ? 'true' : 'false'\n | ^" id=winter-paper name=crowdsecurity/teleport-logs stage=s01-parse
time="2025-04-12T15:15:30+02:00" level=warning msg="failed to run RunTimeValue : interface conversion: interface {} is nil, not bool (1:1)\n | evt.Unmarshaled.teleport.success ? 'auth_success' : 'auth_failed'\n | ^" id=winter-paper name=crowdsecurity/teleport-logs stage=s01-parse
time="2025-04-12T15:15:30+02:00" level=warning msg="failed to run RunTimeValue : interface conversion: interface {} is nil, not string (1:1)\n | Split(evt.Unmarshaled.teleport[\"addr.remote\"], ':')[0]\n | ^" id=winter-paper name=crowdsecurity/teleport-logs stage=s01-parse

🏁 Conclusion

Avec le bug du parser teleport de Crowdsec, nous perdons le bénéfice de la détection même si nous conservons tout de même le bénéfice de la prévention par le biais de la blacklist communautaire de Crowdsec et de son mode WAF avec AppSec. Il me faut creuser la question (mauvaise config, parser non adapté...?)