Suricata IPS : Inspection inline NFQUEUE sur VyOS

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 drop reç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 :

  1. Builder : compilation Suricata 8.0.5 depuis les sources (Alpine, Rust/Cargo/cbindgen requis)
  2. Go builder : compilation du binaire init Go (healthcheck + signal handling)
  3. Prep : assemblage des binaires + bibliothèques dynamiques + setcap
  4. 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/sh en 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 :

  1. systemctl stop vyos-container-suricata.service

    • Le --queue-bypass nftables prend le relais immédiatement
    • Le trafic continue de passer sans inspection
  2. Exécution du container éphémère suricata-updater

    • Télécharge les nouvelles règles (ET Open, etc.)
    • Applique modify.conf et disable.conf
    • Écrit les règles compilées dans le volume partagé
  3. 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 :

  1. PID file : vérification de l'existence et de la validité du processus Suricata
  2. Unix socket : interrogation du socket suricata-command.socket pour 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

Articles connexes


Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.