review: analyse stack/01-suricata-host.yml (2026-07)
Some checks failed
Hugo Build & Deploy / build-deploy (push) Has been cancelled
Some checks failed
Hugo Build & Deploy / build-deploy (push) Has been cancelled
This commit is contained in:
124
content/posts/review-stack-01-suricata-host-yml-2026-07.md
Normal file
124
content/posts/review-stack-01-suricata-host-yml-2026-07.md
Normal 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'
|
||||
Reference in New Issue
Block a user