Introduction
Suricata est déployé en mode IPS inline sur le routeur VyOS via le mécanisme Linux NFQUEUE. Contrairement au mode IDS passif (copie miroir du trafic), le mode IPS permet de bloquer activement les paquets malveillants avant qu'ils n'atteignent leur destination.
Cet article détaille l'architecture choisie, la construction de l'image durcie, la gestion des règles et les métriques de production.
Pourquoi NFQUEUE ?
Alternatives évaluées et rejetées
| Mode | Avantage | Inconvénient | Verdict |
|---|---|---|---|
| AF_PACKET bridge | Pas besoin de host network | Perte WAN si container down, complexe | Rejeté |
| TC redirect | Léger | Perd la capacité IPS (mode IDS seulement) | Rejeté |
| macvlan IDS | Simple | IDS only, pas de blocage | Rejeté |
| NFQUEUE | IPS natif + failsafe | Requiert host network | Retenu |
Le failsafe --queue-bypass
L'argument décisif pour NFQUEUE : l'option --queue-bypass des règles nftables. Si aucun processus ne consomme la queue (container arrêté, crash, mise à jour), le kernel laisse passer le trafic automatiquement au lieu de le bloquer.
C'est un failsafe natif inégalable — aucune des alternatives ne propose un mécanisme aussi robuste pour maintenir la connectivité pendant les maintenances.
Architecture
flowchart LR
Packet["Paquet entrant<br/>WAN eth1"] --> NFT["nftables<br/>NFQUEUE"]
NFT --> Suricata["Suricata IPS<br/>50K rules"]
Suricata -->|"NF_ACCEPT"| Forward["Forward vers destination"]
Suricata -->|"NF_DROP"| Drop["Paquet supprime"]
NFT -.->|"queue-bypass<br/>si container down"| Forward
Mode nfq.mode: accept
Suricata utilise le mode accept (et non repeat). Dans ce mode :
- Les paquets qui matchent une règle
dropreçoivent un verdict NF_DROP → le kernel les supprime - Tous les autres paquets reçoivent NF_ACCEPT → le kernel les route normalement
Le mode repeat (ré-injection avec mark) est inutile ici et cause une charge CPU 3x supérieure avec 50K+ règles. À éviter sauf sur une infrastructure dédiée (8+ vCPUs).
Image durcie FROM scratch
Spécifications
| Propriété | Valeur |
|---|---|
| Image | suricata-hardened:8.0.5 |
| Base | FROM scratch (aucun OS) |
| Taille | ~45 MB compressée |
| PID 1 | tini-static |
| Init | Binaire Go custom |
| User | UID 8000 (non-root) |
| Capabilities | cap_net_admin,cap_sys_nice+ep (setcap) |
| Host network | Obligatoire (NFQUEUE) |
Construction multi-stage
L'image suit le pattern 4-stage standard des images durcies :
- Builder : compilation Suricata 8.0.5 depuis les sources (Alpine, Rust/Cargo/cbindgen requis)
- Go builder : compilation du binaire init Go (healthcheck + signal handling)
- Prep : assemblage des binaires + bibliothèques dynamiques + setcap
- Scratch : image finale sans shell, sans package manager, sans outils de debug
Flags de compilation durcis
-fstack-protector-strong
-D_FORTIFY_SOURCE=2
-fPIE -pie
-Wl,-z,relro,-z,now
Pourquoi allow-host-networks est obligatoire
NFQUEUE opère au niveau du netfilter du kernel hôte. Le processus Suricata doit être dans le même network namespace que les interfaces réseau pour pouvoir consommer les paquets de la queue. Un container avec son propre namespace réseau ne verrait pas les queues nftables de l'hôte.
Gestion des règles
suricata-update via container éphémère
L'image FROM scratch ne contient pas Python — impossible d'exécuter suricata-update directement. La solution : une image Alpine éphémère suricata-updater contenant Python + suricata-update :
# Arrêter Suricata (--queue-bypass maintient le trafic)
systemctl stop vyos-container-suricata.service
# Exécuter suricata-update dans un container éphémère
podman run --rm --user root --network host \
-v /config/containers/suricata/rules:/var/lib/suricata/rules \
-v /config/containers/suricata/etc:/etc/suricata \
suricata-updater update -f --no-test
# Redémarrer Suricata avec les nouvelles règles
systemctl start vyos-container-suricata.service
Note : l'erreur
FileNotFoundError: /bin/shen fin d'exécution est bénigne (reload-command échoue car le binaire suricata est dans l'image scratch, pas l'updater). Les règles sont écrites AVANT cette erreur.
Stratégie modify.conf : alert → drop
Le fichier modify.conf convertit les règles alert en drop pour les catégories à haut risque :
# Convertir en DROP
re:"classtype:trojan-activity" alert drop
re:"classtype:attempted-admin" alert drop
"ET DROP" alert drop
"ET MALWARE" alert drop
"ET CNC" alert drop
"ET TROJAN" alert drop
"ET EXPLOIT" alert drop
"ET SCAN Zmap" alert drop
# Attaques web
"PHP" alert drop
"SQL Injection" alert drop
"Remote Code Execution" alert drop
"WebShell" alert drop
# DNS recon
"GPL DNS" alert drop
Faux positifs (disable.conf)
Certaines règles génèrent des alertes légitimes dans le contexte du homelab :
# Squid proxy transparent (CONNECT légitime)
2013926
2013927
2013928
# BingBot crawler
2032981
# Android connectivity check
2036220
2046370
2046428
# WAF retourne 403 (ModSecurity block = faux positif IPS)
2010516
2101201
# TeamViewer (usage légitime)
2030668
2060624
2060625
2060626
2060627
2060628
2060629
2060630
2060631
2060632
Métriques des règles
| Type | Nombre |
|---|---|
Rules drop |
~23 000 |
Rules alert |
~27 000 |
| Rules désactivées | 13 |
| Total actives | ~50 000 |
Performance
Verdict direct vs ré-injection
En mode accept, chaque paquet reçoit un verdict unique (ACCEPT ou DROP). Pas de ré-injection, pas de mark, pas de second passage dans nftables. C'est le mode le plus performant pour un routeur avec un nombre élevé de règles.
Impact CPU
Avec 50K règles actives et le trafic d'un homelab (~100 Mbps peak), Suricata consomme :
- Idle : <5% CPU (1 vCPU)
- Charge normale : 15-30% CPU
- Burst : 60-80% CPU (téléchargements volumineux)
Le routeur VyOS dispose de 4 vCPUs dédiés à cet usage.
Stratégie de mise à jour
La mise à jour des règles suit un cycle stop → update → start :
-
systemctl stop vyos-container-suricata.service- Le
--queue-bypassnftables prend le relais immédiatement - Le trafic continue de passer sans inspection
- Le
-
Exécution du container éphémère
suricata-updater- Télécharge les nouvelles règles (ET Open, etc.)
- Applique
modify.confetdisable.conf - Écrit les règles compilées dans le volume partagé
-
systemctl start vyos-container-suricata.service- Suricata charge les nouvelles règles
- Reprend l'inspection NFQUEUE
Durée totale : ~30 secondes de fenêtre sans inspection (acceptable pour un homelab).
Healthcheck
L'init Go intégré fournit deux méthodes de healthcheck :
- PID file : vérification de l'existence et de la validité du processus Suricata
- Unix socket : interrogation du socket
suricata-command.socketpour confirmation que le moteur d'inspection est actif
Le socket Unix permet également le reload-rules à chaud (via suricatasc) sans redémarrage complet — utile pour les mises à jour urgentes de signatures.
Sécurité de l'image
Surface d'attaque minimale
- Aucun shell : pas de
/bin/sh, pas de bash - Aucun package manager : pas d'apk, apt, ou pip
- Aucun outil de debug : pas de curl, wget, nc, ou cat
- Non-root : UID 8000, capabilities minimales via setcap
- Read-only : le filesystem est immuable (seuls les volumes sont en écriture)
Capabilities justifiées
| Capability | Justification |
|---|---|
cap_net_admin |
Obligatoire pour recevoir les verdicts NFQUEUE |
cap_sys_nice |
Priorité thread pour le traitement temps-réel des paquets |
cap_net_raw n'est PAS nécessaire en mode NFQUEUE (contrairement au mode AF_PACKET).
Conclusion
NFQUEUE offre le meilleur compromis pour un IPS inline sur un routeur VyOS :
- Blocage actif des menaces (verdict NF_DROP)
- Failsafe natif (--queue-bypass)
- Pas de point de défaillance unique (le réseau fonctionne sans Suricata)
- 50K règles avec un impact CPU acceptable
- Image minimale (FROM scratch, ~45 MB) réduisant la surface d'attaque
La combinaison VyOS + Suricata NFQUEUE + image durcie FROM scratch fournit un niveau de sécurité réseau comparable aux appliances commerciales, avec la flexibilité et la transparence de l'open source.
Liens
- Code source : suricata-hardened
- Image Docker : Docker Hub
Laisser un commentaire