<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>#crowdsec — Blog d'Ange Bonkoski</title><link>https://journal.ange-bonkoski.fr/tags/crowdsec/</link><description>Journal de mon homelab et write-ups de labs de cybersécurité.</description><language>fr</language><copyright>Ange Bonkoski, licence CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/deed.fr)</copyright><lastBuildDate>Sun, 18 Oct 2026 12:00:00 +0200</lastBuildDate><atom:link href="https://journal.ange-bonkoski.fr/tags/crowdsec/index.xml" rel="self" type="application/rss+xml"/><item><title>CrowdSec, et le test qui ne prouvait rien</title><link>https://journal.ange-bonkoski.fr/journal/crowdsec-le-test-qui-ne-prouvait-rien/</link><pubDate>Sun, 18 Oct 2026 12:00:00 +0200</pubDate><guid>https://journal.ange-bonkoski.fr/journal/crowdsec-le-test-qui-ne-prouvait-rien/</guid><category>homelab</category><category>crowdsec</category><category>détection</category><category>docker</category><category>discord</category><description>&lt;p&gt;J&amp;rsquo;ai installé &lt;strong&gt;CrowdSec&lt;/strong&gt; sur mon homelab pour une raison simple : je&#10;voulais savoir si quelqu&amp;rsquo;un s&amp;rsquo;en prenait à mon serveur. L&amp;rsquo;installation&#10;s&amp;rsquo;est bien passée. Les alertes, elles, n&amp;rsquo;arrivaient pas sur Discord. Et la&#10;commande censée les tester me répondait… rien. Ni succès, ni erreur.&lt;/p&gt;&#10;&lt;p&gt;Cet article raconte comment j&amp;rsquo;ai trouvé la panne, et pourquoi la leçon&#10;dépasse largement CrowdSec.&lt;/p&gt;&#10;&lt;h2 id="crowdsec-en-deux-minutes"&gt;CrowdSec en deux minutes&lt;/h2&gt;&#10;&lt;p&gt;CrowdSec est un outil de détection d&amp;rsquo;intrusion. Il fonctionne comme une&#10;chaîne :&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journaux → analyseurs → scénarios → alerte → notification&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(SSH, web) (lisent (reconnaissent (décision) (Discord)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; chaque ligne) une attaque)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#10;&lt;li&gt;Il &lt;strong&gt;lit les journaux&lt;/strong&gt; du serveur : ici, les connexions SSH et les&#10;accès au reverse proxy.&lt;/li&gt;&#10;&lt;li&gt;Des &lt;strong&gt;analyseurs&lt;/strong&gt; découpent chaque ligne (adresse IP, utilisateur,&#10;résultat).&lt;/li&gt;&#10;&lt;li&gt;Des &lt;strong&gt;scénarios&lt;/strong&gt; reconnaissent un comportement suspect, par exemple&#10;une rafale d&amp;rsquo;échecs de connexion SSH depuis la même adresse en&#10;quelques secondes : une attaque par force brute.&lt;/li&gt;&#10;&lt;li&gt;Une &lt;strong&gt;alerte&lt;/strong&gt; est créée, et un &lt;strong&gt;plugin&lt;/strong&gt; l&amp;rsquo;envoie ailleurs, ici vers&#10;un salon Discord privé.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;CrowdSec peut aussi &lt;strong&gt;bloquer&lt;/strong&gt; les adresses fautives, grâce à un composant&#10;séparé (un &lt;em&gt;bouncer&lt;/em&gt;). Je ne l&amp;rsquo;ai pas installé, volontairement : mon&#10;serveur n&amp;rsquo;a aucun port ouvert sur Internet, donc aucune attaque extérieure&#10;ne peut l&amp;rsquo;atteindre. Mon but était d&amp;rsquo;apprendre la chaîne de détection et de&#10;surveiller ce qui se passe, pas de risquer de me bloquer moi-même.&lt;/p&gt;&#10;&lt;h2 id="le-symptôme"&gt;Le symptôme&lt;/h2&gt;&#10;&lt;p&gt;CrowdSec propose une commande pour tester une notification :&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli notifications &lt;span class="nb"&gt;test&lt;/span&gt; http_default&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Elle s&amp;rsquo;exécutait sans erreur. Sa sortie montrait le plugin qui démarre,&#10;puis qui s&amp;rsquo;arrête. Et sur Discord : rien. J&amp;rsquo;ai relancé plusieurs fois, en&#10;modifiant la configuration entre deux essais. Toujours rien.&lt;/p&gt;&#10;&lt;h2 id="séparer-les-couches"&gt;Séparer les couches&lt;/h2&gt;&#10;&lt;p&gt;Quand une chaîne de plusieurs maillons ne fonctionne pas, deviner ne sert à&#10;rien. J&amp;rsquo;ai testé chaque maillon &lt;strong&gt;seul&lt;/strong&gt;, en partant du bout.&lt;/p&gt;&#10;&lt;h3 id="maillon-1--discord-reçoit-il-quoi-que-ce-soit-"&gt;Maillon 1 : Discord reçoit-il quoi que ce soit ?&lt;/h3&gt;&#10;&lt;p&gt;Un webhook Discord est une simple adresse à laquelle on envoie un message.&#10;On peut donc le tester sans CrowdSec. Problème : l&amp;rsquo;image de CrowdSec ne&#10;contient pas &lt;code&gt;curl&lt;/code&gt;. Plutôt que d&amp;rsquo;installer quoi que ce soit, j&amp;rsquo;ai utilisé&#10;un &lt;strong&gt;conteneur jetable&lt;/strong&gt; qui ne contient que &lt;code&gt;curl&lt;/code&gt; et disparaît après&#10;usage :&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run --rm --env-file .env curlimages/curl &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sh -c &lt;span class="s1"&gt;&amp;#39;curl -s -o /dev/null -w &amp;#34;%{http_code}\n&amp;#34; \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; -H &amp;#34;Content-Type: application/json&amp;#34; \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; -d &amp;#34;{\&amp;#34;content\&amp;#34;:\&amp;#34;Test du webhook\&amp;#34;}&amp;#34; &amp;#34;$DISCORD_WEBHOOK&amp;#34;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Réponse : &lt;code&gt;204&lt;/code&gt;, et le message est apparu sur Discord. Le webhook&#10;fonctionnait. Le problème était donc &lt;strong&gt;en amont&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;h3 id="maillon-2--crowdsec-lit-il-le-bon-fichier-"&gt;Maillon 2 : CrowdSec lit-il le bon fichier ?&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;cscli notifications list&lt;/code&gt; indiquait bien &lt;code&gt;http_default&lt;/code&gt; comme actif. Mais&#10;« actif » voulait seulement dire qu&amp;rsquo;&lt;strong&gt;un&lt;/strong&gt; fichier portait ce nom. En&#10;regardant le fichier réellement présent &lt;strong&gt;dans le conteneur&lt;/strong&gt;, sa date et&#10;sa taille, j&amp;rsquo;ai compris : c&amp;rsquo;était le fichier &lt;strong&gt;par défaut&lt;/strong&gt; livré avec&#10;l&amp;rsquo;image, pas le mien. Ma configuration n&amp;rsquo;avait jamais été chargée.&lt;/p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&lt;div class="encadre encadre-lecon" role="note"&gt;&#10; &lt;p class="encadre-titre"&gt;&#10; &#10; &#10; &lt;svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"&gt;&lt;path d="M9 18h6"/&gt;&lt;path d="M10 22h4"/&gt;&lt;path d="M12 2a7 7 0 0 0-4 12.7c.6.5 1 1.3 1 2.1V17h6v-.2c0-.8.4-1.6 1-2.1A7 7 0 0 0 12 2z"/&gt;&lt;/svg&gt;&#10; &#10; &lt;span&gt;« Actif » n&amp;#39;est pas « chargé »&lt;/span&gt;&#10; &lt;/p&gt;&#10; &#10; &lt;div class="encadre-contenu"&gt;&#10; &lt;p&gt;Une commande qui liste une configuration ne prouve pas que c&amp;rsquo;est &lt;strong&gt;la&#10;vôtre&lt;/strong&gt;. Allez voir le fichier réellement utilisé par le programme : sa&#10;date de modification et sa taille disent la vérité.&lt;/p&gt;&#10;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;h3 id="maillon-3--la-commande-de-test-teste-t-elle-vraiment-quelque-chose-"&gt;Maillon 3 : la commande de test teste-t-elle vraiment quelque chose ?&lt;/h3&gt;&#10;&lt;p&gt;En corrigeant le fichier, j&amp;rsquo;ai aussi compris pourquoi le test restait&#10;muet. La commande démarre le plugin, lui confie le message… et s&amp;rsquo;arrête&#10;avant que l&amp;rsquo;envoi ait eu le temps de se faire. Ma configuration ajoutait&#10;en plus un délai de regroupement de 60 secondes avant chaque envoi : le&#10;test ne pouvait &lt;strong&gt;jamais&lt;/strong&gt; aboutir. J&amp;rsquo;ai retiré ce délai, et j&amp;rsquo;ai cessé de&#10;me fier à cette commande.&lt;/p&gt;&#10;&lt;h2 id="la-vraie-preuve--une-vraie-alerte"&gt;La vraie preuve : une vraie alerte&lt;/h2&gt;&#10;&lt;p&gt;Pour prouver que toute la chaîne fonctionne, il faut un test qui la&#10;&lt;strong&gt;traverse&lt;/strong&gt; entièrement, comme le ferait une vraie attaque. J&amp;rsquo;ai donc&#10;créé une décision manuelle contre une adresse fictive :&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli decisions add --ip 203.0.113.99 --reason &lt;span class="s2"&gt;&amp;#34;test de bout en bout&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;L&amp;rsquo;alerte est arrivée sur Discord en quelques secondes. Puis j&amp;rsquo;ai supprimé&#10;cette décision de test.&lt;/p&gt;&#10;&lt;p&gt;Pourquoi &lt;code&gt;203.0.113.99&lt;/code&gt; ? Parce que cette plage (&lt;code&gt;203.0.113.0/24&lt;/code&gt;) est&#10;&lt;strong&gt;réservée à la documentation&lt;/strong&gt; : elle n&amp;rsquo;appartient à personne. C&amp;rsquo;est une&#10;bonne habitude, dans un test comme dans un article, pour ne jamais viser&#10;l&amp;rsquo;adresse de quelqu&amp;rsquo;un par erreur.&lt;/p&gt;&#10;&lt;h2 id="le-piège-suivant--les-adresses-privées"&gt;Le piège suivant : les adresses privées&lt;/h2&gt;&#10;&lt;p&gt;J&amp;rsquo;ai voulu aller plus loin et simuler une vraie attaque par force brute&#10;SSH depuis mon propre PC. Rien ne s&amp;rsquo;est déclenché. Normal : par défaut,&#10;CrowdSec &lt;strong&gt;ignore&lt;/strong&gt; les adresses privées (le réseau local, Docker,&#10;Tailscale). Une attaque venant de l&amp;rsquo;intérieur passe donc sous ses radars.&lt;/p&gt;&#10;&lt;p&gt;Pour vérifier qu&amp;rsquo;un scénario reconnaît bien une ligne de journal, CrowdSec&#10;propose &lt;code&gt;cscli explain&lt;/code&gt;. On lui donne une fausse ligne avec une adresse de&#10;documentation, et il montre chaque étape : quel analyseur l&amp;rsquo;a lue, quels&#10;scénarios se sont activés.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli explain --type syslog &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --log &lt;span class="s2"&gt;&amp;#34;Oct 18 10:00:00 serveur sshd[1234]: Failed password for invalid user test from 203.0.113.5 port 4242 ssh2&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="ce-que-jen-retiens"&gt;Ce que j&amp;rsquo;en retiens&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Un test qui ne dit rien ne prouve rien.&lt;/strong&gt; Ni succès ni erreur, ce&#10;n&amp;rsquo;est pas « ça marche ».&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Tester chaque maillon seul, en partant du bout.&lt;/strong&gt; Le webhook, puis le&#10;fichier, puis le plugin : chaque étape élimine une hypothèse.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Préférer une vraie alerte à une commande de test.&lt;/strong&gt; Elle emprunte le&#10;même chemin qu&amp;rsquo;une attaque réelle.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Connaître les angles morts de son outil.&lt;/strong&gt; Ignorer les adresses&#10;privées est un choix raisonnable, mais il faut le savoir.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Un secret peut fuiter par un message d&amp;rsquo;erreur.&lt;/strong&gt; Une erreur d&amp;rsquo;envoi&#10;peut afficher l&amp;rsquo;adresse complète du webhook, qui suffit à quiconque pour&#10;écrire dans mon salon Discord. Avant de partager un journal, je masque&#10;toujours ce genre d&amp;rsquo;information.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>