tuxvador@blog:~/blog/fr/crowdsec-agent-detecting-nothing$

~/blog/fr/crowdsec-agent-detecting-nothing

Mon agent CrowdSec n'a rien détecté pendant quatre mois

5 min de lectureRead in English →

#security#crowdsec#linux#self-hosting

Mon serveur public est la seule machine du lab exposée directement à Internet. Il fait tourner un agent CrowdSec qui lit ses journaux SSH, mail et web, et un bouncer pare-feu qui bloque les IP bannies. Pendant quatre mois, le bouncer a parfaitement fonctionné. L’agent, lui, n’a rien fait du tout, et rien ne l’a signalé.

Pourquoi tout avait l’air sain

Mes décisions CrowdSec sont centralisées : chaque agent envoie ses alertes à une seule Local API dans le homelab, et chaque bouncer en récupère les bannissements. Le bouncer du serveur public appliquait donc des bannissements créés par d’autres agents, plus la blocklist communautaire. Son set nftables contenait environ 17 800 adresses. systemctl status crowdsec était vert. Les tableaux de bord montraient des bannissements arriver sur cette machine tous les jours.

Rien de tout cela n’exigeait que l’agent local détecte quoi que ce soit. Je l’ai découvert en auditant les angles morts du SOC, avec une seule commande :

cscli scenarios list

Elle a affiché un tableau vide. Aucun scénario, aucun parser. L’agent suivait les journaux et ne les comparait à rien.

Des liens cassés

Les éléments du hub CrowdSec (parsers, scénarios, collections) sont installés sous forme de liens symboliques : /etc/crowdsec/scenarios/ssh-bf.yaml pointe vers un répertoire du hub qui contient le vrai fichier. Sur ce serveur, chaque lien pointait vers /usr/share/crowdsec/hub/, qui n’existait plus.

find /etc/crowdsec /var/lib/crowdsec/hub -xtype l | wc -l    # 52 broken links

L’historique des paquets l’expliquait :

Date Ce qui s’est passé
3 juin CrowdSec installé depuis le dépôt Debian. Le paquet Debian livre un instantané du hub dans /usr/share/crowdsec/hub/, et les liens des éléments pointent dedans.
29 juin Passage au dépôt officiel de CrowdSec pour une version plus récente. Le paquet officiel n’a pas d’instantané du hub, donc le répertoire est parti avec l’ancien paquet, et chaque lien a cassé.
Septembre Un apt upgrade de routine a pris une version officielle plus récente. Toujours rien de chargé.

CrowdSec gère un élément du hub manquant en ne le chargeant pas. Avec tous les éléments manquants, il démarre proprement avec zéro parser et zéro scénario, et ne journalise jamais d’erreur qui mérite l’attention.

Les deux dépôts étaient aussi toujours configurés avec la même priorité apt, si bien qu’apt prenait celui qui proposait la version la plus haute. Le dépôt officiel gagnait à chaque fois, ce que je voulais, mais par accident plutôt que par épinglage. L’ancien paquet bouncer de Debian était toujours installé à côté de celui du dépôt officiel, inutilisé, ce qui rendait dpkg -l | grep crowdsec difficile à lire.

La correction

Sauvegarde d’abord. /etc/crowdsec et le répertoire du hub partent tous deux dans une archive tar, si bien que tout le changement peut être annulé en l’extrayant.

Puis supprimer les liens cassés et réinstaller depuis le hub en ligne :

find /etc/crowdsec /var/lib/crowdsec/hub -xtype l -delete
cscli hub update
cscli collections install crowdsecurity/linux crowdsecurity/sshd \
    crowdsecurity/nginx crowdsecurity/postfix crowdsecurity/dovecot
crowdsec -t && systemctl restart crowdsec

Cinq collections ont tiré leurs dépendances : 59 scénarios et 13 parsers. Le paquet officiel livre aussi un crowdsec-hubupdate.timer quotidien, donc ils restent à jour.

Lire chaque événement une seule fois

Tant que j’y étais : le fichier d’acquisition était devenu une liste de tout ce qui ressemblait à un journal. C’est plus important qu’il n’y paraît : rsyslog écrit les lignes mail à la fois dans /var/log/mail.log et dans /var/log/syslog, et sshd écrit à la fois dans auth.log et dans le journal systemd. Un agent qui lit les deux copies voit chaque événement deux fois, donc un scénario qui devrait se déclencher après dix échecs de connexion se déclenche après cinq.

Le nouveau acquis.yaml en lit une copie de chaque :

filenames:
  - /var/log/auth.log
  - /var/log/mail.log
labels:
  type: syslog
---
filenames:
  - /var/log/nginx/access.log
  - /var/log/nginx/error.log
labels:
  type: nginx

La whitelist qui masquait un élément du hub

Le serveur avait un parser local appelé whitelists.yaml, mes propres adresses issues du post-mortem du blocage. Le hub a un élément, crowdsecurity/whitelists, qui installe un fichier du même nom dans le même répertoire. Le mien le masquait. J’ai renommé le mien en home-whitelist.yaml (name: local/home-whitelist), n’y ai gardé que mon IP de sortie et les plages du tailnet, et laissé l’élément du hub couvrir les plages privées.

Prouver qu’il détecte

cscli metrics montrerait normalement les lignes analysées par source. Sur ce serveur, la commande échoue (« prometheus is not enabled »), parce que le point de métriques de l’agent est désactivé. cscli explain fonctionne sans lui et montre le chemin d’une ligne d’exemple à travers les parsers et les scénarios :

cscli explain --type syslog \
  --log 'Oct 19 01:20:00 mail sshd[1234]: Invalid user admin from 203.0.113.50 port 50000'

Cette ligne atteint désormais ssh-bf, ssh-slow-bf et leurs variantes d’énumération d’utilisateurs. Un échec SASL Postfix atteint postfix-spam, et une sonde 404 nginx atteint http-probing.

Le vrai test est arrivé quatre minutes et demie après le redémarrage, quand un scanner a sondé le serveur web à la recherche de fichiers sensibles et de traversée de chemin. L’agent a levé http-sensitive-files, http-path-traversal-probing et http-probing. L’API centrale en a fait un bannissement de sept jours, et l’adresse est apparue dans le set nftables du serveur lui-même. C’étaient les premières alertes que cette machine envoyait à l’API centrale.

Ce que je vérifie maintenant

La leçon ne porte pas sur les liens symboliques. L’application et la détection sont séparées, et un bouncer qui marche cache un agent mort. Un service vert et une liste de bannissements pleine ne disent rien sur la contribution de l’agent local.

Les vérifications que je fais désormais sur chaque agent :

cscli scenarios list | wc -l              # not zero
find /etc/crowdsec -xtype l               # empty
cscli alerts list --since 168h -l 0 -o json | jq '[.[] | .machine_id] | group_by(.) | map({(.[0]): length}) | add'

La dernière tourne sur l’API centrale et compte les alertes récentes par agent. Un agent exposé à Internet avec zéro alerte en une semaine n’est pas calme. Il est cassé.

Pièges

  • Les paquets CrowdSec de Debian et du dépôt officiel organisent le hub différemment. Passer de l’un à l’autre casse chaque élément installé. Réinstallez les collections après tout changement de dépôt.
  • Un élément du hub manquant est ignoré, pas signalé. Un agent sans rien de chargé démarre quand même et a l’air sain.
  • Deux dépôts à la même priorité, c’est la version la plus haute qui gagne. Épinglez celui que vous voulez.
  • Lire le même événement dans deux fichiers double les compteurs, ce qui divise par deux le seuil de chaque scénario.
  • Un fichier local nommé comme un élément du hub le masque. Donnez leurs propres noms aux éléments locaux.
  • cscli metrics a besoin du point Prometheus. Sans lui, testez avec cscli explain.

cd ~/blog