Files
blog/content/posts/review-stack-01-suricata-host-yml-2026-07.md
Betty 728765d2df
Some checks failed
Hugo Build & Deploy / build-deploy (push) Has been cancelled
review: analyse stack/01-suricata-host.yml (2026-07)
2026-07-23 09:00:54 +02:00

6.3 KiB

title, date, draft, tags, summary, author, ShowToc, ShowReadingTime
title date draft tags summary author ShowToc ShowReadingTime
Suricata en mode host sur Docker Swarm : pourquoi on triche (et pourquoi c'est OK) 2026-07-23 false
homelab
docker
cybersecurity
build-in-public
french-tech
infosec
Analyse du stack Suricata de Bojemoi Lab : comment intégrer un IDS/IPS en network_mode host dans un environnement Docker Swarm, avec exporter Prometheus et nettoyage automatique des logs. Bojemoi true true

TL;DR

Suricata ne joue pas bien avec Docker Swarm. On le sait. On a quand même trouvé un compromis fonctionnel, et cet article explique pourquoi on a fait ces choix — y compris les moins glorieux.


Le problème fondamental : Swarm et la capture réseau

Docker Swarm est excellent pour orchestrer des services applicatifs. Mais dès qu'on veut faire de la capture de paquets à la couche physique, les choses se compliquent sérieusement.

Un service Swarm tourne dans un réseau overlay (ingress ou custom). Les paquets qu'il voit sont déjà encapsulés, NATés, transformés. Pour un IDS comme Suricata, c'est rédhibitoire : tu n'analyses plus le trafic réel, tu analyses l'ombre d'un trafic.

La solution honnête ? Sortir Suricata du Swarm et lui donner accès direct à l'interface réseau physique.

C'est ce que fait stack/01-suricata-host.yml — et c'est assumé dès le premier commentaire du fichier :

# Suricata IDS/IPS — standalone docker compose (NOT Swarm)
# Requires network_mode: host for real packet capture on eth0.

Architecture du stack : trois rôles, trois conteneurs

1. suricata — le moteur IDS/IPS

network_mode: host
cap_add:
  - NET_ADMIN
  - SYS_NICE
  - NET_RAW

network_mode: host est le choix central. Le conteneur partage la stack réseau du nœud hôte. Suricata peut ainsi écouter sur eth0 directement, comme s'il était installé en bare-metal.

Les capabilities ajoutées sont les trois pilliers de la capture réseau sous Linux :

  • NET_RAW : ouvrir des raw sockets, lire les paquets bruts
  • NET_ADMIN : manipuler les interfaces (mode promiscuité, règles nftables si IPS)
  • SYS_NICE : ajuster la priorité des threads de capture pour éviter les drops sous charge

Les options Suricata méritent attention :

SURICATA_OPTIONS=-i eth0 --set stream.reassembly.depth=0 --set detect.profile=low
  • stream.reassembly.depth=0 : pas de limite sur la profondeur de réassemblage TCP. En homelab avec peu de RAM, c'est un pari — ça peut consommer beaucoup sur des transferts massifs. À monitorer.
  • detect.profile=low : profil de détection économique. On sacrifie de la couverture pour des performances acceptables sur du matériel modeste. C'est honnête pour un lab.

2. suricata-exporter — le pont vers Prometheus

Ce conteneur lit les métriques Suricata via son Unix socket (suricata-command.socket) et les expose au format Prometheus. C'est un pattern propre : Suricata ne connaît pas Prometheus, l'exporter fait le pont sans modifier le moteur.

command:
  - '--suricata.socket-path=/var/run/suricata/suricata-command.socket'

La communication passe par un volume partagé monté en lecture seule côté exporter. Le depends_on garantit que Suricata démarre en premier — même si ça ne garantit pas que le socket existe déjà au moment où l'exporter tente de s'y connecter. Un restart: unless-stopped compense ce race condition de démarrage.

L'exporter rejoint le réseau monitoring (externe, donc géré par le Swarm), ce qui lui permet d'être scraped par Prometheus même si le reste du stack est hors Swarm.

3. eve-cleaner — gestion des logs à l'ancienne

C'est le composant le plus artisanal — et probablement le plus honnête du stack.

Suricata génère un fichier eve.json en append continu. Il n'y a pas de rotation native du fichier actif dans Suricata (contrairement aux fichiers horodatés qu'il crée lui-même). Résultat : sans intervention, eve.json grossit indéfiniment.

La solution ici : un conteneur Alpine qui tourne une boucle shell toutes les heures.

# Suppression des fichiers archivés de plus de 48h
AGE_H=$(( (NOW - MTIME) / 3600 ))
if [ "$AGE_H" -ge "$KEEP_HOURS" ]; then rm -f "$f"; fi

# Troncature de eve.json si > 5GB
if [ "$EVE_KB" -ge "$MAX_KB" ]; then truncate -s 0 "$EVE"; fi

Points notables :

  • truncate -s 0 plutôt que > file ou rm : Suricata conserve son file descriptor ouvert, la troncature vide le fichier sans casser le handle. C'est le bon geste.
  • La compatibilité stat est gérée avec deux syntaxes (-c %Y Linux, -f %m macOS) — vestige probable de dev en local sur Mac.
  • Le logging de l'opération est verbose et structuré, ce qui est bien pour le debug.

Ce qui manque (et on le sait)

Déploiement multi-nœuds manuel

Le commentaire dit tout : Deploy on each node: docker compose -f stack/01-suricata-host.yml up -d. Il n'y a pas d'automatisation de déploiement sur plusieurs nœuds. Sur un cluster de 5 machines, c'est 5 commandes SSH manuelles. Un Ansible playbook serait le prochain palier évident.

Le race condition au démarrage

suricata-exporter démarre après suricata mais le socket Unix peut mettre quelques secondes à apparaître. Les premières tentatives de connexion échouent. Le restart: unless-stopped rattrape ça, mais c'est du bricolage. Une healthcheck sur Suricata testant l'existence du socket serait plus propre.

detect.profile=low en production

Acceptable pour un lab, problématique pour une vraie infrastructure. Ce paramètre réduit la précision de certaines détections comportementales. À revoir si le lab évolue vers de la détection d'incidents réels.

L'enrichissement des alertes est ailleurs

Le commentaire final pointe vers stack/01-service-hl.yml pour le suricata-attack-enricher. Ce découpage est logique (l'enrichisseur a besoin du réseau overlay Swarm et des secrets), mais ça crée une dépendance inter-fichiers non évidente à l'onboarding.


Ce qu'on retient

Ce fichier est un bon exemple de pragmatisme en homelab : on fait ce qui marche, on documente les compromis, et on ne prétend pas que c'est parfait. network_mode: host dans un environnement Swarm n'est pas élégant, mais c'est la seule façon d'avoir un IDS qui voit vraiment le trafic.

L'