rss logo

Comment configurer depuis zéro un jeu de règles nftables sur GNU/Linux

Logo nftables

Intro

Dans cet article, je vais détailler une méthode que j'utilise pour configurer un ensemble de règles nftables sur des ordinateurs GNU/Linux. Le but étant d'avoir un jeu de règles le plus restrictifs et précis possibles. Cette méthode est adaptée à tout type de configuration comme des serveurs, des stations de travail, des routeurs, etc… Elle consiste à tracer le trafic potentiellement bloqué à partir d’un jeu de règles "classique" puis d'ajouter de nouvelles règles au fur et à mesure de ce que l'on peut observer dans les logs.

  • Cela se fait en trois étapes :
    • Ajouter une configuration de base et activer le traçage.
    • Analyser les journaux et ajouter de nouvelles règles si du trafic légitime n'est pas déjà explicitement autorisé.
    • Une fois que tout le trafic légitime a été autorisé, activer le blocage par défaut.

Voyons comment configurer cela!

Configurer les règles par défaut et connues

  • Ici, nous configurons simplement un jeu de règles classiques (⚠️penser à remplacer enp2s0 par le nom de son interface réseau!⚠️) :
    • des règles pour l’interface locale
    • pour autoriser l’ICMP
    • et pour accepter le trafic web

Ce qui correspond à un profil typique de la plupart des PCs aujourd'hui. Notez la présence des lignes avec "log prefix". Ces règles, en "accept" autorisent (pour l'instant) le trafic tout en collectant toutes les informations de chaque trame les traversants. Cela nous permettra de tracer le trafic qui aurait été bloqué si le blocage par défaut avait été activé.

  • Éditez le fichier /etc/nftables.conf:
#!/usr/bin/nft -f flush ruleset # We declare an interface variable associated with our network card so that we can use $interface in our rules instead of "enp2s0". define interface = { enp2s0 } # ----- IPv4 and IPv6 ----- table inet filter { chain INPUT { type filter hook input priority 0; policy drop; # Local interface rules ct state invalid counter drop comment "Drop invalid connections" iif != lo ip daddr 127.0.0.1/8 counter drop comment "drop connections to loopback not coming from loopback" iif != lo ip6 daddr ::1/128 counter drop comment "drop connections to loopback not coming from loopback" iif lo accept comment "Accept any localhost traffic" # ICMP traffic ip protocol icmp counter accept comment "accept all ICMP types" meta l4proto ipv6-icmp counter accept comment "accept all ICMPv6 types" # Accept web traffic (ALL to PC : if related or established) : HTTP + HTTPS + HTTP/3 QUIC + DNS + NTP iif $interface tcp sport { http, https, domain, ntp } ct state { related, established } counter accept comment "Accept Web Traffic TCP" iif $interface udp sport { https, domain } ct state { related, established } counter accept comment "Accept Web Traffic UDP" # Accept but trace all other INPUT traffic log prefix "INPUT : " accept comment "log INPUT" } chain FORWARD { type filter hook forward priority 0; policy drop; # FORWARD traffic is dropped and logged. There should be no network frames if the Linux PC is not in router mode. log prefix "FORWARD : " drop comment "log FORWARD" } chain OUTPUT { type filter hook output priority 0; policy drop; # Local interface rules oif lo accept comment "Accept any localhost traffic" # ICMP traffic ip protocol icmp counter accept comment "accept all ICMP types" meta l4proto ipv6-icmp counter accept comment "accept all ICMPv6 types" # Accept web traffic (PC to ALL : new, related and established) : HTTP + HTTPS + HTTP/3 QUIC + DNS + NTP oif $interface tcp dport { http, https, domain, ntp } ct state { new, related, established } counter accept comment "Accept Web Traffic" oif $interface udp dport { https, domain } ct state { new, related, established } counter accept comment "Accept Web Traffic" # Accept but trace all other OUTPUT traffic log prefix "OUTPUT : " accept comment "log out of rules traffic" } }
  • Appliquez le fichier de configuration nftables :
root@host:~# nft -f /etc/nftables.conf

Analyse des journaux

  • Ici nous utilisons la commande journalctl afin d'afficher les trames traversant nos règles "log prefix". Pour cela nous utilisons l'option "grep" pour filtrer les lignes contenant les mots INPUT, OUTPUT ou FORWARD et concernant le noyau (option -k) :
root@host:~# journalctl -k --grep="INPUT|OUTPUT|FORWARD" Dec 14 15:57:09 WK-STD kernel: INPUT : IN=ens33 OUT= MAC=00:50:56:80:4f:3d:34:56:dc:b7:ec:ec:08:00 SRC=192.168.1.90 DST=192.168.1.88 LEN=88 TOS=0x00 PREC=0x00 TTL=62 ID=59819 DF PROTO=TCP SPT=58616 DPT=22 WINDOW=1091 RES=0x00 ACK PSH URGP=0 Dec 14 15:57:09 WK-STD kernel: OUTPUT : IN= OUT=ens33 SRC=192.168.1.88 DST=192.168.1.90 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=10678 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK URGP=0 Dec 14 15:57:10 WK-STD kernel: OUTPUT : IN= OUT=ens33 SRC=192.168.1.88 DST=192.168.1.90 LEN=592 TOS=0x10 PREC=0x00 TTL=64 ID=10679 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK PSH URGP=0 Dec 14 15:57:10 WK-STD kernel: OUTPUT : IN= OUT=ens33 SRC=192.168.1.88 DST=192.168.1.90 LEN=1208 TOS=0x10 PREC=0x00 TTL=64 ID=10680 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK PSH URGP=0 Dec 14 15:57:10 WK-STD kernel: INPUT : IN=ens33 OUT= MAC=00:50:56:80:4f:3d:34:56:dc:b7:ec:ec:08:00 SRC=192.168.1.90 DST=192.168.1.88 LEN=52 TOS=0x00 PREC=0x00 TTL=62 ID=59820 DF PROTO=TCP SPT=58616 DPT=22 WINDOW=1078 RES=0x00 ACK URGP=0 Dec 14 15:57:10 WK-STD kernel: OUTPUT : IN= OUT=ens33 SRC=192.168.1.88 DST=192.168.1.90 LEN=336 TOS=0x10 PREC=0x00 TTL=64 ID=10681 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK PSH URGP=0 Dec 14 15:57:10 WK-STD kernel: OUTPUT : IN= OUT=ens33 SRC=192.168.1.88 DST=192.168.1.90 LEN=632 TOS=0x10 PREC=0x00 TTL=64 ID=10682 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK PSH URGP=0 Dec 14 15:57:10 WK-STD kernel: INPUT : IN=ens33 OUT= MAC=00:50:56:80:4f:3d:34:56:dc:b7:ec:ec:08:00 SRC=192.168.1.90 DST=192.168.1.88 LEN=52 TOS=0x00 PREC=0x00 TTL=62 ID=59821 DF PROTO=TCP SPT=58616 DPT=22 WINDOW=1072 RES=0x00 ACK URGP=0 Dec 14 15:57:11 WK-STD kernel: OUTPUT : IN= OUT=ens33 SRC=192.168.1.88 DST=192.168.1.90 LEN=880 TOS=0x10 PREC=0x00 TTL=64 ID=10683 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK PSH URGP=0 Dec 14 15:57:11 WK-STD kernel: INPUT : IN=ens33 OUT= MAC=00:50:56:80:4f:3d:34:56:dc:b7:ec:ec:08:00 SRC=192.168.1.90 DST=192.168.1.88 LEN=52 TOS=0x00 PREC=0x00 TTL=62 ID=59822 DF PROTO=TCP SPT=58616 DPT=22 WINDOW=1066 RES=0x00 ACK URGP=0 Dec 14 15:57:11 WK-STD kernel: OUTPUT : IN= OUT=ens33 SRC=192.168.1.88 DST=192.168.1.90 LEN=632 TOS=0x10 PREC=0x00 TTL=64 ID=10684 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK PSH URGP=0 Dec 14 15:57:11 WK-STD kernel: INPUT : IN=ens33 OUT= MAC=00:50:56:80:4f:3d:34:56:dc:b7:ec:ec:08:00 SRC=192.168.1.90 DST=192.168.1.88 LEN=52 TOS=0x00 PREC=0x00 TTL=62 ID=59823 DF PROTO=TCP SPT=58616 DPT=22 WINDOW=1062 RES=0x00 ACK URGP=0

Nous pouvons voir du trafic entre les adresses 192.168.1.90 (source) et 192.168.1.88 (destination) sur le port SSH (22 TCP). Dans mon cas, cela est parfaitement normal, car je suis connecté à la machine 192.168.1.88 depuis la machine 192.168.1.90 via ce protocole. Regardons donc ce qu'il va falloir mettre en œuvre pour autoriser ce trafic via l'ajout de règles spécifiques.

  • Si nous examinons en détail la première ligne, nous pouvons voir toutes les informations nécessaires pour ajouter notre règle nftables :
    • Concerne la chaîne INPUT.
    • L'adresse IP source est 192.168.1.88.
    • L'adresse IP de destination est 192.168.1.90.
    • Le protocole est TCP.
    • Le port de destination est 22.
Dec 14 15:57:09 WK-STD kernel: INPUT : IN=enp2s0 OUT= MAC=00:50:56:80:4f:3d:34:56:dc:b7:ec:ec:08:00 SRC=192.168.1.90 DST=192.168.1.88 LEN=88 TOS=0x00 PREC=0x00 TTL=62 ID=59819 DF PROTO=TCP SPT=58616 DPT=22 WINDOW=1091 RES=0x00 ACK PSH URGP=0
  • Nous pouvons faire de même avec la deuxième ligne, qui concerne la chaîne OUTPUT :
    • Concerne la chaîne OUTPUT.
    • L'adresse IP source est 192.168.1.90.
    • L'adresse IP de destination est 192.168.1.88.
    • Le protocole est TCP.
    • Le port source est 22.
Dec 14 15:57:09 WK-STD kernel: OUTPUT : IN= OUT=enp2s0 SRC=192.168.1.88 DST=192.168.1.90 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=10678 DF PROTO=TCP SPT=22 DPT=58616 WINDOW=524 RES=0x00 ACK URGP=0

Ajout des règles

  • Une fois l'analyse terminée, nous devons mainteant éditer notre fichier de configuration /etc/nftables.conf pour y ajouter nos deux nouvelles règles (une en INPUT et l'autre en OUTPUT) et les placer juste avant nos règles "log prefix" :
#!/usr/bin/nft -f flush ruleset # We declare an interface variable associated with our network card so that we can use $interface in our rules instead of "enp2s0". define interface = { enp2s0 } # ----- IPv4 and IPv6 ----- table inet filter { chain INPUT { type filter hook input priority 0; policy drop; # Local interface rules ct state invalid counter drop comment "Drop invalid connections" iif != lo ip daddr 127.0.0.1/8 counter drop comment "drop connections to loopback not coming from loopback" iif != lo ip6 daddr ::1/128 counter drop comment "drop connections to loopback not coming from loopback" iif lo accept comment "Accept any localhost traffic" # ICMP traffic ip protocol icmp counter accept comment "accept all ICMP types" meta l4proto ipv6-icmp counter accept comment "accept all ICMPv6 types" # Accept web traffic (ALL to PC : if related or established) : HTTP + HTTPS + HTTP/3 QUIC + DNS + NTP iif $interface tcp sport { http, https, domain, ntp } ct state { related, established } counter accept comment "Accept Web Traffic TCP" iif $interface udp sport { https, domain } ct state { related, established } counter accept comment "Accept Web Traffic UDP" # NEW RULE iif $interface ip saddr 192.168.1.88 ip daddr 192.168.1.90 tcp dport { 22 } accept comment "INPUT SSH" # Accept but trace all other INPUT traffic log prefix "INPUT : " accept comment "log INPUT" } chain FORWARD { type filter hook forward priority 0; policy drop; # FORWARD traffic is dropped and logged. There should be no network frames if the Linux PC is not in router mode. log prefix "FORWARD : " drop comment "log FORWARD" } chain OUTPUT { type filter hook output priority 0; policy drop; # Local interface rules oif lo accept comment "Accept any localhost traffic" # ICMP traffic ip protocol icmp counter accept comment "accept all ICMP types" meta l4proto ipv6-icmp counter accept comment "accept all ICMPv6 types" # Accept web traffic (PC to ALL : new, related and established) : HTTP + HTTPS + HTTP/3 QUIC + DNS + NTP oif $interface tcp dport { http, https, domain, ntp } ct state { new, related, established } counter accept comment "Accept Web Traffic" oif $interface udp dport { https, domain } ct state { new, related, established } counter accept comment "Accept Web Traffic" # NEW RULE oif $interface ip saddr 192.168.1.90 tcp sport { 22 } ip daddr 192.168.1.88 accept comment "OUTPUT SSH" # Accept but trace all other OUTPUT traffic log prefix "OUTPUT : " accept comment "log out of rules traffic" } }
  • Nous appliquons ensuite la nouvelle configuration :
root@host:~# nft -f /etc/nftables
  • Utilisez de nouveau la commande journalctl pour vérifier qu'aucun autre trafic légitime ne passe par nos règles "log prefix" pour les chaînes INPUT, OUTPUT ou FORWARD :
root@host:~# journalctl -k --grep="INPUT|OUTPUT|FORWARD"

Si du trafic légitime est repéré, ajoutez simplement de nouvelles règles nftables, comme nous l'avons fait pour le service SSH.

Finaliser la configuration de nftables

À ce stade, nous devrions avoir créé toutes les règles nécessaires pour autoriser le trafic légitime. Il est donc temps de sécuriser notre configuration en modifiant le comportement de nos règles "log prefix" d'accept en drop.

  • Pour cela, éditez une nouvelle fois le fichier /etc/nftables.conf :
#!/usr/bin/nft -f flush ruleset # We declare an interface variable associated with our network card so that we can use $interface in our rules instead of "enp2s0". define interface = { enp2s0 } # ----- IPv4 and IPv6 ----- table inet filter { chain INPUT { type filter hook input priority 0; policy drop; # Local interface rules ct state invalid counter drop comment "Drop invalid connections" iif != lo ip daddr 127.0.0.1/8 counter drop comment "drop connections to loopback not coming from loopback" iif != lo ip6 daddr ::1/128 counter drop comment "drop connections to loopback not coming from loopback" iif lo accept comment "Accept any localhost traffic" # ICMP traffic ip protocol icmp counter accept comment "accept all ICMP types" meta l4proto ipv6-icmp counter accept comment "accept all ICMPv6 types" # Accept web traffic (ALL to PC : if related or established) : HTTP + HTTPS + HTTP/3 QUIC + DNS + NTP iif $interface tcp sport { http, https, domain, ntp } ct state { related, established } counter accept comment "Accept Web Traffic TCP" iif $interface udp sport { https, domain } ct state { related, established } counter accept comment "Accept Web Traffic UDP" # NEW RULE iif $interface ip saddr 192.168.1.88 ip daddr 192.168.1.90 tcp dport { 22 } accept comment "INPUT SSH" # Accept but trace all other INPUT traffic log prefix "INPUT : " drop comment "log INPUT" } chain FORWARD { type filter hook forward priority 0; policy drop; # FORWARD traffic is dropped and logged. There should be no network frames if the Linux PC is not in router mode. log prefix "FORWARD : " drop comment "log FORWARD" } chain OUTPUT { type filter hook output priority 0; policy drop; # Local interface rules oif lo accept comment "Accept any localhost traffic" # ICMP traffic ip protocol icmp counter accept comment "accept all ICMP types" meta l4proto ipv6-icmp counter accept comment "accept all ICMPv6 types" # Accept web traffic (PC to ALL : new, related and established) : HTTP + HTTPS + HTTP/3 QUIC + DNS + NTP oif $interface tcp dport { http, https, domain, ntp } ct state { new, related, established } counter accept comment "Accept Web Traffic" oif $interface udp dport { https, domain } ct state { new, related, established } counter accept comment "Accept Web Traffic" # NEW RULE oif $interface ip saddr 192.168.1.90 tcp sport { 22 } ip daddr 192.168.1.88 accept comment "OUTPUT SSH" # Accept but trace all other OUTPUT traffic log prefix "OUTPUT : " drop comment "log out of rules traffic" } }
  • Réappliquez ensuite les règles :
root@host:~# nft -f /etc/nftables

Nous avons maintenant une configuration parfaitement sécurisé et maitrisé. Et vous savez maintenant quoi faire si certains services réseau ne fonctionnent pas ou ne sont pas accessibles. Il suffira en effet dans ce cas de procéder à une nouvelle analyse des journaux avec la commande journalctl et d'y ajouter de nouvelles règles, comme nous l'avons fait précédemment pour le service SSH.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Contact :

contact mail address