Nftables
Les exemples de configuration doivent être validés par des personnes experimentées, je ne suis pas sûre de moi.
Passer de iptables à nftables
Déjà, sur toute nouvelle version de Debian, par défaut c'est nftables derrière (commande nft
). Ensuite, même si on installe le paquet “iptables” en vrai c'est une surcouche à présent, qui traduira les règles de façon invisible pour l'utilisatrice. Ce qui permet de continuer à utiliser ses config iptables sans rien changer quand on n'a pas le temps, et c'est cool. Mais il faut aussi varier ;)
Installer le paquet iptables permet d'avoir accès à un outil intéressant : iptables-translate. Cette commande nous permet de savoir comment une règle iptable se traduit en terme de syntaxe pour nftables. Cela permet d'adapter les anciens tutoriels plus rapidement…
Plus d'infos ici : https://wiki.nftables.org/wiki-nftables/index.php/Moving_from_iptables_to_nftables
nftables.conf
Ce fichier /etc/nftables.conf
est un peu l'équivalent du fichier de conf de “iptable-persistant”. Il faut quand même déclarer qu'il faut le charger automatiquement, mais une fois fait, il va lire les règles là lors du redémarrage du serveur.
Fichier par défaut sur ma Debian :
#!/usr/sbin/nft -f flush ruleset table inet filter { chain input1 { type filter hook input priority filter; } chain forward1 { type filter hook forward priority filter; } chain output1 { type filter hook output priority filter; } }
Organisation de ce fichier :
- Il doit commencer par
#!/usr/sbin/nft -f
qui précise le programme à exécuter - On peut mettre une commande directement (pas n'importe quoi bien sûr). Généralement on commence avec
flush ruleset
qui purge tout vieux truc précédent. table
: c'est là qu'on déclare les tables, c'est à dire une catégorie pour organiser des lots de règles.inet
: famille d'adresses ipv4 ET ipv6. On peut aussi déclarer juste pour ipv4 avecip
et ipv6 avecip6
.- Ici “filter” est le nom de la table. On peut donner le nom qu'on veut à une table.
- Les chaines sont une suite de règles à appliquer dans un contexte précis. Ici, les noms “input1, forward1 et output1” sont juste des conventions (auquel j'ai rajouté “1” pour éviter toute confusion), on peut aussi les appeler comme on veut. Là où elles agissent est défini ensuite avec
type filter hook input
(et là, par contre, les mots-clés sont importants et figés).- Les type peuvent être :
filter
,route
,nat
. - Les hook peuvent être :
prerouting
,input
,forward
,output
,postrouting
.
priority filter
: quand tu ne sais pas, tu met filter, mais y'a d'autres options.filter
ça précise les règles qui vont suivrent et qui ici sont donc de filtrer… Sinon il y a des chiffres (voir la doc) pour préciser dans quel ordre tout va s'appliquer.- Peu importe l'ordre des
chain
et destable
. Par contre il faut déclarer le type de filtre dans les chaines, même si d'autres type de règles sont possibles.
Ça c'est donc la version de base et terriblement trop permissive ; suivant les cas d'usages on va choisir diverses options.
Quelque soit la version, je préfère une politique où je refuse tout, en dehors de ce qui est explicitement autorisé.
Autres éléments de syntaxe
Je copie des “hook” pour comprendre ce que chaque petit morceau fait.
Il est possible de déclarer en une ligne plusieurs redirections du même type. Par exemple si suivant les ports tcp redirigent vers diverses ip internes (ou externes !), ça donne ça (exemple issu de /usr/share/doc/nftables/examples/nat.nft
:
table ip nat { chain prerouting { type nat hook prerouting priority 0; #Thanks to nftables maps, if you have a previous iptables NAT (destination NAT) ruleset like this: # % iptables -t nat -A PREROUTING -p tcp --dport 1000 -j DNAT --to-destination 1.1.1.1:1234 # % iptables -t nat -A PREROUTING -p udp --dport 2000 -j DNAT --to-destination 2.2.2.2:2345 # % iptables -t nat -A PREROUTING -p tcp --dport 3000 -j DNAT --to-destination 3.3.3.3:3456 # It can be easily translated to nftables in a single line: dnat tcp dport map { 1000 : 1.1.1.1, 2000 : 2.2.2.2, 3000 : 3.3.3.3} : tcp dport map { 1000 : 1234, 2000 : 2345, 3000 : 3456 } } chain postrouting { type nat hook postrouting priority 0; #Likewise, in iptables NAT (source NAT): # % iptables -t nat -A POSTROUTING -s 192.168.1.1 -j SNAT --to-source 1.1.1.1 # % iptables -t nat -A POSTROUTING -s 192.168.2.2 -j SNAT --to-source 2.2.2.2 # % iptables -t nat -A POSTROUTING -s 192.168.3.3 -j SNAT --to-source 3.3.3.3 # Translated to a nftables one-liner: snat ip saddr map { 192.168.1.1 : 1.1.1.1, 192.168.2.2 : 2.2.2.2, 192.168.3.3 : 3.3.3.3 } } }
On peut aussi définir des variables avec define
(au début du fichier plutôt) et ensuite les appeler.
define tcp_ports = {80, 443} table inet firewall { chain inbound { type filter hook input priority 0; policy drop; tcp dport { $tcp_ports } ct state new accept
Exemples de fichiers suivant les cas
Machine terminale
Premier cas : serveur “seul”. Ou encore config dans une vm après un proxy etc. Cette machine est placée derrière un routeur (box, proxy…) qui sera un peu plus complet, et surtout qui empêche déjà de rentrer n'importe comment.
- /etc/nftables.conf
#!/usr/sbin/nft -f flush ruleset table inet firewall { # Règles spécifiques ipv6 chain inbound_ipv6 { # accept neighbour discovery otherwise connectivity breaks # source : https://wiki.nftables.org/wiki-nftables/index.php/Simple_ruleset_for_a_server icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept # TODO comprendre ! ICMPv6 packets which must not be dropped, see https://tools.ietf.org/html/rfc4890#section-4.4.1 meta nfproto ipv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-reply, echo-request, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, 148, 149 } accept ip6 saddr fe80::/10 icmpv6 type { 130, 131, 132, 143, 151, 152, 153 } accept } # Règles génériques entrantes chain inbound { # Par défaut, on rejette tout à moins de suivre une des règles ci-dessous. type filter hook input priority 0; policy drop; # Autoriser le trafic de paquets "etablished" et associés, supprimer les paquets non valides ct state vmap { established : accept, related : accept, invalid : drop } # Autorise le loopback iifname "lo" accept # Autorise le ping avec une limite pour les abus icmp type echo-request limit rate 5/second accept # Applique les règles en plus suivant le protocole(ici ipv6, on pourrait ajouter des trucs sur ipv4) meta protocol vmap { ip6 : jump inbound_ipv6 } # Autoriser certains ports quand les services liés sont actifs (donc à adapter), ici le web et ssh tcp dport { 22, 80, 443 } ct state new accept # Uncomment to enable logging of denied inbound traffic # log prefix "[nftables] Inbound Denied: " counter drop # TODO ! count and drop any other traffic #counter drop } # En sortie (output) par défaut ça autorise tout et en théorie c'est ok. # forward, nat etc : pas utile dans ce cas de figure. }
Si serveur mail : remplacer
# Autoriser certains ports quand les services liés sont actifs (donc à adapter), ici le web et ssh tcp dport { 22, 80, 443 } ct state new accept
Par
# Autoriser certains ports quand les services liés sont actifs : web (80/443), ssh (22), # smtp (25), SMTP TLS (587), IMAPS (993), Sieve (4190) tcp dport { 22, 80, 443, 25, 587, 993, 4190 } ct state new accept
Machine proxy, configuration sécuritaire
Cette machine redirige une partie du trafic vers d'autres machines (probablement localement, mais ça peut être ailleurs) et on va donc ajouter NAT et Forward. Cas typique des proxy, xen, proxmox, etc.
Ici on va desservir l'ip interne (une VM) 10.10.10.69
(adresse à adapter suivant les machines derrière le proxy, donc). L'adresse ipv4 externe d'exemple est 66.66.66.66.
Faudra améliorer sur ipv6.
L'exemple est assez parano sur le trafic sortant et ce qui passe par l'autre machine, par défaut rien n'est permis sauf ce qui est explicite. On peut évidement être plus souple sur output/forward suivant les cas d'usage.
- /etc/nftables.conf
#!/usr/sbin/nft -f flush ruleset # Table sur le trafic de type input/output/forward. De base on rejette tout, sauf ce qui est explicitement autorisé. table inet firewall { # Règles spécifiques ipv6 chain input_ipv6 { # accept neighbour discovery otherwise connectivity breaks # source : https://wiki.nftables.org/wiki-nftables/index.php/Simple_ruleset_for_a_server icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept # TODO comprendre ! ICMPv6 packets which must not be dropped, see https://tools.ietf.org/html/rfc4890#section-4.4.1 meta nfproto ipv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-reply, echo-request, nd-router-solicit,> ip6 saddr fe80::/10 icmpv6 type { 130, 131, 132, 143, 151, 152, 153 } accept } # Règles génériques entrantes (valables pour ipv6 et ipv4) chain input_all { # Par défaut, on rejette tout à moins de suivre une des règles ci-dessous. type filter hook input priority 0; policy drop; # Autoriser le trafic de paquets "etablished" et associés, supprimer les paquets non valides ct state vmap { established : accept, related : accept, invalid : drop } # Autorise le loopback iifname "lo" accept # Autorise le ping avec une limite pour les abus icmp type echo-request limit rate 5/second accept # Applique les règles en plus suivant le protocole(ici ipv6, on pourrait ajouter des trucs sur ipv4) meta protocol vmap { ip6 : jump input_ipv6 } # Autoriser certains ports quand les services liés sont actifs (donc à adapter), ici le web et ssh tcp dport { 22, 80, 443 } ct state new accept # Uncomment to enable logging of denied inbound traffic # log prefix "[nftables] Inbound Denied: " counter drop # TODO ! count and drop any other traffic #counter drop } # Règles génériques sortantes chain output { # Par défaut, on rejette tout à moins de suivre une des règles ci-dessous. type filter hook output priority 0; policy drop; # Autoriser le trafic de paquets "etablished" et associés, supprimer les paquets non valides ct state established accept # Autorise le loopback sortant oifname "lo" accept # Autorise le ping sortant ip protocol icmp accept # Autoriser d'aller sur internet et de contacter les dns (domain) tcp dport { http, https } accept udp dport domain accept } # Règles génériques "forward" (ce qui circule vers les autres adresses routées) chain forward { # Par défaut, on rejette tout à moins de suivre une des règles ci-dessous. type filter hook forward priority 0; policy drop; # Autoriser le trafic de paquets "etablished" et associés, supprimer les paquets non valides ct state established accept # transfère le web ? TODO différence daddr/saddr ip daddr 10.10.10.69 tcp dport { http, https } accept ip saddr 10.10.10.69 udp dport domain accept ip saddr 10.10.10.69 tcp dport { http, https } accept } } # Table sur le trafic de type prerouting/postrouting, par défaut sur accept pour que les connexions puissent s'initialiser table ip nat { chain prerout { type nat hook prerouting priority 0; policy accept; # ce qui va sur les ports web doit être redirigé vers la VM tcp dport { http, https } dnat to 10.10.10.69 } chain postrout { type nat hook postrouting priority 0; policy accept; # Ce qui vient de la VM et qui tente de se connecter au web doit sortir par l'ip de notre proxy # Sinon on est redirigé sur nous-même... ip saddr 10.10.10.69 snat to 66.66.66.66 } }
Vérification et mise en place
Pour vérifier ce fichier une fois modifié, avant de le mettre en place :
sudo nft -cof /etc/nftables.conf
La commande va donner des informations si y'a besoin.
- -c : vérifier s'il y a des erreurs syntaxes
- -o : optimiser les règles
- -f : indique le fichier à lire (ici l'emplacement par défaut sur debian).
Une fois qu'on a configuré son fichier :
sudo systemctl enable nftables.service sudo service nftables start
Pour lister les règles en place :
sudo nft list ruleset