Suricata hardened : image Docker durcie pour NFQUEUE

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
Base FROM scratch (aucun OS)
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)

L'image suit le pattern générique des images durcies, décrit dans l'article dédié ; les étapes de construction sont dans le Dockerfile du dépôt.

Ce qui est propre à cette image, en revanche, c'est son poids relatif : c'est la plus lourde du parc en nombre de chemins, et la seule dont le contenu propre au service dépasse le socle de données inertes que toutes portent. La raison tient en un mot : Python. L'interpréteur complet représente à lui seul 472 chemins et 65 modules d'extension, pour un unique consommateur, suricata-update.

Les modules dynamiques de l'interpréteur sont d'ailleurs triés : ceux dont les bibliothèques ne sont pas dans l'image ont été retirés plutôt que publiés dans un état où ils ne peuvent pas s'importer.

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.


La mise à jour des règles appartient au service

L'outil de mise à jour est embarqué dans cette image plutôt que dans une seconde — c'est la raison pour laquelle elle porte un interpréteur. Mais ce qu'il télécharge, ce qu'on promeut en blocage et ce qu'on éteint relève de la politique de détection, pas de l'image : tout cela, y compris le piège de la version mal détectée, est dans la stratégie de détection.


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.


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
  • Pas de système de fichiers en lecture seule, contrairement à d'autres images du parc : le moteur écrit ses journaux et ses fichiers d'état. C'est une contrainte assumée, pas un oubli — l'annoncer immuable serait faux.

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).


Ce que ce montage donne

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