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.
Voyons comment configurer cela!
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é.
#!/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"
}
}
root@host:~# nft -f /etc/nftables.conf
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.
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
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
#!/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"
}
}
root@host:~# nft -f /etc/nftables
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.
À 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.
#!/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"
}
}
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.
Contact :