review: analyse stack/01-suricata-host.yml (2026-07)
Some checks failed
Hugo Build & Deploy / build-deploy (push) Has been cancelled

This commit is contained in:
Betty
2026-07-23 09:00:43 +02:00
parent 4813346dd6
commit 728765d2df

View File

@@ -0,0 +1,124 @@
---
title: "Suricata en mode host sur Docker Swarm : pourquoi on triche (et pourquoi c'est OK)"
date: 2026-07-23
draft: false
tags: ["homelab", "docker", "cybersecurity", "build-in-public", "french-tech", "infosec"]
summary: "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."
author: "Bojemoi"
ShowToc: true
ShowReadingTime: 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 :
```yaml
# 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
```yaml
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 :
```yaml
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.
```yaml
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.
```sh
# 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'