Nftables : remplacant de Iptables
Nftables : remplacant de Iptables jppNOTE février 2022 : CE "LIVRE" EST TOUJOURS EN COURS DE REALISATION.
Le noyau utilise Nftables mais, par manque de temps, j'utilise toujours l'interface iptables + ipset, pour le moment cela fonctionne encore très bien.
En plus je n'ai pas encore bien compris la philosophie sous tendue par Nftables et j'ai du mal à concevoir un pare-feu à la fois simple, complet et facile à maintenir.
Nouveauté Mai 2016 : utilisation notation CIDR et intervalles dans un "set nommé", voir l'article spécifique.
"NFTABLES" :
C'est le remplaçant prévu des commande IPTABLES et IPSET utilisées par tous les parefeux sour Linux.
Le développement ne semble pas hyper actif ni réactif et de nombreuses commandes ne sont pas encore, à mon goût, complètes, notamment au niveau des "sets" (remplaçants de Ipset) ou suffisament souples.
Tout est prêt au niveau du noyau Linux depuis déjà quelques versions (3.16 en principe) mais je pense que pour des tests (NFTABLES ne semble pas encore prêt pour mise en exploitation) il vaut mieux utiliser une version très récente du noyau.
Ici j'utiliserai une 3.19-rc6, la dernière disponible au moment de ces tests.
Il faut utiliser un moyau compilé avec tous les modules spécifiques à NFTABLES, malheureusement dans "make menuconfig" les sifférentes options sont un peu dispersées. Il faut aussi se rappeler que NFTABLES concernera IpV6 aussi bien que IpV4, donc configurer le tout.
Il faut avoir configuré dans le noyau (>= 3.16, ici 3.19) les extensions pour "nftables":
<M> Netfilter nf_tables support
<M> Netfilter nf_tables mixed IPv4/IPv6 tables support
<M> Netfilter nf_tables IPv6 exthdr module
<M> Netfilter nf_tables meta module
<M> Netfilter nf_tables conntrack module
<M> Netfilter nf_tables rbtree set module
<M> Netfilter nf_tables hash set module
<M> Netfilter nf_tables counter module
<M> Netfilter nf_tables log module
<M> Netfilter nf_tables limit module
<M> Netfilter nf_tables masquerade support
<M> Netfilter nf_tables redirect support
<M> Netfilter nf_tables nat module
<M> Netfilter nf_tables queue module
<M> Netfilter nf_tables reject support
Ajouter Ethernet bridge nf_tables support
--- Ethernet Bridge nf_tables support
<M> Netfilter nf_table bridge meta support
<M> Netfilter nf_tables bridge reject support
Il est conseillé de charger tous les modules nécessaires au démarrage en constituant une liste des modules dans un fichier dédié (ici nftables.conf) dans /etc/modules-load.d pour une Debian "Testing" ou dans le fichier /etc/modules pour une Debian "stable".
Les modules se situent dans /lib/modules/$(uname -r)/kernel/net/netfilter et commencent presque tous par "nft" sauf les deux plus importants "nf_tables_inet" et "nf_tables".
Liste des modules chargés :
nf_tables
nf_tables_inet
nft_chain_nat_ipv6
nft_chain_nat_ipv4
nft_chain_route_ipv6
nft_chain_route_ipv4
nft_counter
nft_ct
nft_exthdr
nft_hash
nft_limit
nft_log
nft_masq
nft_meta
nft_nat
nft_queue
nft_rbtree
nft_redir
nft_reject_inet
nft_reject
nft_reject_ipv4
nft_reject_ipv6
On sera ainsi parés pour toutes les éventualités.
Quelques références :
http://wiki.gentoo.org/wiki/Nftables
http://wiki.nftables.org/wiki-nftables/index.php/Main_Page
NFTABLES : un peu de vocabulaire
NFTABLES : un peu de vocabulaire jppAucune "table" n'est définie par défaut comme avec "iptables", c'est à vous de les définir !
Rappel :
Plus de différence entre IPV4 et IPV6, tout peut être traité dans le même flot, si on utilise des tables "inet", par défaut c'est "ip" (IPV4) qui est choisi, on peut aussi utiliser "ip6" pour du spécifique IPV6.
Commandes disponibles :
list
ruleset
tables [famille]
table [famille] <nom>
chain [famille] <table> <nom>
add
table [famille] <nom>
chaine [famille] <table> <nom> [paramètres de la chaîne]
rule [famille] <table> <chaine> <paramètres de la règle>
element [famille] <table> <set> { 1..n elements }
table [famille] <nom> (raccourci pour "add table`)
insert
rule [famille] <table> <chaine> <paramètres de la règle>
delete
table [famille] <nom>
chain [famille] <table> <nom>
rule [famille] <table> <numero handle>
set [famille] <table> <set> : il ne doit plus être référencé
element [famille] <table> <set> { 1..N elements a enlever }
flush
table [famille] <nom>
chain [famille] <table> <nom>
Remarques :
La famille "ip" (ipv4) est prise par défaut.
Attention à la syntaxe de "list" "list tables" et "list table ENTREE".
Liste des familles de "tables" et l'outil iptables correspondant
ip iptables
ip6 ip6tables
inet iptables and ip6tables
arp arptables
bridge ebtables
Liste des "hooks" existants, tiens tiens ! Les noms me rappellent quelque chose :
INPUT paquet entrant dans le système local
OUTPUT paquet sortant du système local
FORWARD paquet traversant
PREROUTING paquet entrant
POSTROUTING paquet sortant
Liste des "types" de tables existants, là aussi les noms semblent familiers :
filter Valeur par défaut
nat
route (mangle), ne semble utilisable qu'avec le hook "output".
Ordres supportés par la commande "table"
add
delete
list
flush
Ordres supportés par la commande "chain" :
add
create
delete
list
flush
rename
Ordres supportés par la commande "rule" :
add ajoute une règle en fin de chaîne
insert ajoute une règle en début de chaîne
delete détruit une règle, utilisé avec un "handle" cf "nft -a list ...."
Ordres supportés par la commande "ruleset" :
flush efface tout
list liste tout
Destination des paquets
accept
reject
drop
snat
dnat
log
counter
return
jump <chaine>
goto <chaine>
Quelques "mots clefs" pour les règles :
oif interface sortie suivi d'un numéro
iif interface entree suivi d'un numéro
oifname nom de l'interface de sortie
iifname nom de l'interface d'entree
type suivi d'un type ICMP par exemple "echo-request"
protocol tcp, udp ...
daddr adresse destination
saddr adresse source
dport port de destination
sport port source
ct connexion tracking + state ( NEW, ESTABLISHED, RELATED, INVALID)
C'est un bon début.
NFTABLES : premier test Debian testing
NFTABLES : premier test Debian testing jppPour ce premier essai j'ai utilisé les paquets disponibles à ce jour sur "testing" :
libjansson4_2.7-1_amd64.deb
libmxml1_2.6-2_amd64.deb
libnftnl0_1.0.3-4_amd64.deb
nftables_0.4-2_amd64.deb
Une fois tout ce petit monde en place on peut commencer à tenter de se servir de ce nouvel outil remplaçant désigné de "iptables".
Références :
Accéder à "http://people.netfilter.org/wiki-nftables/index.php/Main_Page" pour tous les détails sur nftables.
Un autre Wiki bien fait est celui d'Archlinux accessible à "https://wiki.archlinux.org/index.php/Nftables".
Premier essai :
"nft list tables" ou "nft list tables ip6" ne donnent aucune sortie puisque avec Nftables rien n'est créé par défaut !
Par contre dès qu'une chaine existe une sortie est fournie :
nft add table entree
nft list tables
-->
table entree
L'ensemble a donc l'air parfaitement fonctionnel. On va pouvoir passer à des choses plus sérieuses.
NFTABLES : quelques tests
NFTABLES : quelques tests jppPoints importants :
- Nftables est prévu pour fonctionner avec l'option "-f" avec laquelle les données sont contenues dans un fichier. Ce fichier peut être généré par la commande "list" et utilisé ensuite en entrée de la commande "nftables -f ".
- A l'origine, rien n'est créé, les différentes tables et chaines existant par défaut avec "iptables" n'existent pas ici par défaut. Il faut donc créer toutes les "tables" et y attacher toutes les "chaines" nécessaires.
Quelques exemples :
- Création d'une table "ENTREE" (Ipv4 par défaut le parametre "ip" étant omis) :
nft add table ENTREE
Liste des tables :
nft list tables
--> table ENTREE
- Ajout d'une table "SORTIE"
nft add table SORTIE
nft list tables
-->
table ENTREE
table SORTIE
- Destruction de la table "SORTIE" :
nft delete table SORTIE
nft list tables
-->
table ENTREE
Ajout d'une chaine nommée "INTERNE" dans la table ENTREE (type filter et Ipv4 par défaut) :
nft add chain ENTREE INTERNE
Destruction de la chaine "INTERNE" de la table ENTREE
nft delete chain ENTREE INTERNE
Addition d'une règle dans notre table ENTREE (à la fin) :
nft add table ENTREE
nft add chain ENTREE INTERNE
nft add rule ENTREE INTERNE ip saddr 127.0.0.1 accept
nft list table ENTREE
-->
table ip ENTREE {
chain INTERNE {
ip saddr 127.0.0.1 accept
}
}
Remarque : On aurait pu ici utiliser aussi "nft list ruleset" pour lister l'ensemble des tables.
- On accepte le port 22
nft add rule ENTREE INTERNE tcp dport 22 accept
nft list table ENTREE
-->
table ip ENTREE {
chain INTERNE {
ip saddr 127.0.0.1 accept
tcp dport ssh accept
}
}
- Insérer une règle en début de chaîne
nft insert rule ENTREE INTERNE ct state established, related accept
On fait une liste avec les numeros de "handle" (option "-a") :
nft -a list table ENTREE
-->
table ip ENTREE {
chain INTERNE {
ct state established,related accept # handle 5
ip saddr 127.0.0.1 accept # handle 2
tcp dport ssh accept # handle 4
}
}
- On ne peut détruire une règle que par son numéro (handle), Ici on détruit la règle 4
nft delete rule ENTREE INTERNE handle 4
nft -a list table ENTREE
-->
table ip ENTREE {
chain INTERNE {
ct state established,related accept # handle 5
ip saddr 127.0.0.1 accept # handle 2
}
}
- On sauvegarde notre table :
nft list table ENTREE >SAUVE
- On efface tout
nft delete table ENTREE
nft list tables
--> ne retourne rien
- On restaure notre jeu de test
nft -f sauve
nft list table ENTREE
-->
table ip ENTREE {
chain INTERNE {
ct state established,related accept
ip saddr 127.0.0.1 accept
}
}
On retrouve notre table telle qu'avant destruction, la sauvegarde d'un jeu de règles et sa restauration sont d'une belle simplicité.
- On rajoute une autorisation pour SSH et on logue la connexion :
nft add rule ENTREE INTERNE tcp dport ssh log prefix \"MSG=Connect SSH \"
nft -a list ruleset
-->
table ip ENTREE {
chain INTERNE {
ct state established,related accept # handle 2
ip saddr 127.0.0.1 accept # handle 3
tcp dport ssh log prefix "Connect SSH" # handle 4
}
}
NFTABLES : on passe par la case COMPIL
NFTABLES : on passe par la case COMPIL jppLes tests de "map" et autres joyeusetés n'ayant pas fonctionné avec les versions "standard" il faut passer par la case "compil" et récupérer les morceaux dans un petit répertoire bien tranquille (pour moi "/usr/src/PGM/BUILD/NFT"):
git clone git://git.netfilter.org/nftables.git
git clone git://git.netfilter.org/libmnl.git
git clone git://git.netfilter.org/libnftnl.git
Pré-requis :
bison
flex
libgmp-dev
libreadline-dev
libmnl0
libmnl-dev
Remarque : il peut y avoir d'autres pré-requis, la machine ayant servi à la compil est une Debian "stable" avec "backports" qui a déjà beaucoup vécu et comporte déjà pas mal de packages "-dev".
Compilation de "LIBMNL" le script :
#!/bin/bash cd NFT/libmnl ...... le répertoire source make clean ./autogen.sh # configure libmnl ./configure ${CONFOPT} 2>&1 | tee LOG.CONF make 2>&1 | tee LOG.MAKE |
Ne pas oublier ensuite (en "root") le "make install".
Compilation de "LIBNFTNL" le script :
#!/bin/bash cd NFT/libnftnl make clean ./autogen.sh # configure libnftnl ./configure ${CONFOPT} 2>&1 | tee LOG.CONF make 2>&1 | tee LOG.MAKE |
Ne pas oublier (toujours) le "make install" en "root".
Une fois tout vérifié j'ai copié :
/usr/local/libmnl/lib/libmnl.* dans /lib/x86_64-linux-gnu
/usr/local/libnftnl/lib/libnftm* dans /lib/x86_64-linux-gnu
et vérifié que la version Debian de "nft" fonctionnait toujours --> OK
Compilation de "NFTABLES" le script :
#!/bin/bash PKG_CONFIG_PATH=${PKG_CONFIG_PATH}':/usr/local/libnftnl/lib/pkgconfig' cd /usr/src/PGM/BUILD/NFT/nftables make clean ./autogen.sh # configure nftables CONFOPT=' --prefix=/usr/local/nftables --exec-prefix=/usr/local/nftables/bin ' ./configure ${CONFOPT} 2>&1 | tee LOG.CONFIGURE make 2>&1 | tee LOG.MAKE echo 'PKG_CONFIG_PATH=('${PKG_CONFIG_PATH}')' echo '' >./doc/nft.8 |
Ne pas oublier (encore) le "make install", puis on expédie "/usr/local/nftables/bin/sbin/nft" dans "/usr/sbin".
On pourra ainsi reprendre nos essais ... avec la dernière version.
NFTABLES : version compilée tests
NFTABLES : version compilée tests jppCette petite série de tests a été conduite avec la version compilée localement de "nft" car les autres versions essayées ne "comprenaient" pas certaines nouvelles fonctionnalités.
Premier test : réaliser du NAT :
Si la table existe un petit "flush" remet tout dans l'ordre, si la table n'existe pas cela déclenche une erreur fatale et rien n'est fait ! C'est très dommage pour l'atomicité des opérations.
Pour être tranquilles il faut donc tester l'existence de la table et la créer (vide) si besoin. Cela peut se faire avec le petit bout de script suivant :
# # On teste l'existence de la table ! # nft list table ip POSTROUTE 2>/dev/null >/dev/null ret=$? # # On crée la table si elle n'existe pas !!! # if [ $ret -ne 0 ] then nft add table ip POSTROUTE fi On peut maintenant lancer la suite du script avec : nft -f Mon fichier contient dans notre cas : flush table ip POSTROUTE; table ip POSTROUTE { chain DEFAUT { type nat hook postrouting priority 0; ip saddr 192.168.2.0/24 masquerade; ip saddr 192.168.3.0/24 masquerade; } } |
On peut noter la création d'une table avec un nom "en français" pour faire plus beau !
Deuxième test, effectuer une redirection transparente de HTTP/HTTPS :
Tous les "clients" HTTP à destination de l'extérieur sont "interceptés". Seules les adresses précisées subissent une redirection du HTTPS, le "set" GO_HTTPS contiendra les adresses sélectionnées.
On ajoute des compteurs pour suivre le travail.
flush table ip PREROUTE; table ip PREROUTE { chain DEFAUT { type nat hook prerouting priority 0; ip daddr != 192.168.2.1/24 tcp dport 80 counter redirect to 3127 ip daddr != 192.168.2.1/24 ip saddr @GO_HTTPS tcp dport https counter redirect to 3129 } set GO_HTTPS { type ipv4_addr; }; } |
Ensuite on ajoute les adresses au set :
nft add element PREROUTE GO_HTTPS { 192.168.2.9 } nft add element PREROUTE GO_HTTPS { 192.168.2.10 , 192.168.2.8} et on vérifie : nft list set PREROUTE GO_HTTPS set GO_HTTPS { type ipv4_addr elements = { 192.168.2.9, 192.168.2.8, 192.168.2.10} } |
Troisième test : envoyer des paquets vers une "queue" en espace utilisateur par exemple pour un IPS :
Ici l'IPS est Suricata, on mettra un compteur sur chaque règle.
On utilise la "famille" inet pour intercepter aussi IPV6 et une priorité de -100 pour passer avant les autres chaines. On ne peut pas utiliser le type "route" qui ne fonctionne pas avec un autre "hook" que "output".
delete table inet IPS; table inet IPS { chain ENT_150 { type filter hook input priority -100; counter queue num 0 } chain SOR_150 { type filter hook output priority -190; counter queue num 0 } chain FOR_150 { type filter hook forward priority -100; counter queue num 1-2; } } |
Après quelques accès internet la commande " nft list table inet IPS " renvoie :
table inet IPS { chain ENT_150 { type filter hook input priority -100; counter packets 1676 bytes 490186 queue num 0 } chain SOR_150 { type filter hook output priority -190; counter packets 1969 bytes 398894 queue num 0 } chain FOR_150 { type filter hook forward priority -100; counter packets 0 bytes 0 queue num 1-2 } } |
Des paquets "locaux" sont bien passés dans la queue 0 et l'un d'entre eux a été "repéré" par Suricata car il contenait un mot de passe de connexion sur un site WEB.
A suivre ....
NFTABLES : sets, maps et dictionnaires
NFTABLES : sets, maps et dictionnaires jppNFTABLES :
Nftables dispose d'un arsenal de modèles permettant de simplifier la réalisation des tests en utilisant des notions analogues à celles des "Ipsets" mais présentant quelques perfectionnements intéressants.
Il existe plusieurs types de tables dans deux saveurs :
- Gadgets "nommés", décrits "à part" et utilisés par une règle.
- Gadgets "inline" non nommés qui peuvent faire partie d'une règle.
Les gadgets nommés permettent de réaliser des ajouts/suppressions "en ligne" gràce aux opérations sur les éléments (de manière analogue à "ipset").
Un certain nombre de "types" de données sont admis pour les éléments :
- ipv4_addr/ipv6_addr Adresses IPV4 ou IPV6, avec option "flags interval" pour notation CIDR ou intervalles, nécessite noyau >= 4.7-rc2 et dernière version nftables (à charger et compiler).
- ether_addr Adresse MAC
- inet_service Ports, sous forme numérique ou texte pour les plus connus (ssh, http, https ...)
- inet_proto Prototype : ICMP, TCP ....
- mark Marque posée par une autre règle.
Ces différents types répondent à la plupart des besoins, il manquerait toutefois la possibilité de désigner un réseau sous forme CIDR en plus des adresses IP. Cette possibilité existe dans les "vmap" non nommées, mais pas pour cellles nommées ? Un oubli sans doute ?
Juin 2016 :
Cet oubli est corrigé dans la dernière version de Nftables et nécessite un kernel >= 4.7-rc2, la syntaxe de la création du set est légèrement différente, il faut ajouter "flags interval" dans la déclaration du set nommé qui devient :
set MONSET ( type ipv4-addr; flags interval)
Une autre nouveauté intéressante de ces "gadgets" est donnée par le deuxième élément possible : l'action à réaliser si l'élément "matche", drop, jump table, goto table, accept ..... Cela ouvre des horizons assez sympatiques.
Par exemple on peut en une seule règle refouler toutes les IP d'un pays à l'aide des listes fournies par différents sites par exemple "ipdeny.com". On peut récupérer la liste des réseaux concernés, mettre en forme la règle (un simple script AWK le fait très bien, générer un fichier au "bon" format et utiliser ce fichier dans un script "Nftables" à l'aide de l'instruction "include" (je vous avais bien dit que la syntaxe Nftables se rapprochait d'un langage !). Dommage d'être obligé d'utiliser un bloc de données non nommé car si on en a besoin plusieurs fois .... par exemple en Entrée et en Forward ou en sortie.
Exemple de syntaxe pour la règle ci dessus :
add rule INPUT ENT_100 ip saddr vmap { 1.0.1.0/24 : drop, 1.0.2.0/23 : drop, 1.0.8.0/21 : drop, ........ 254.254.0.0/24 : drop } |
Qui ajoute une règle à la chaine "ENT_100" de la table "INPUT".
Ce fichier est appelé par l'include suivant dans le script Nftables de création de la table "INPUT" :
#include "le_chemin_de_mon_fichier"
Autre petit exemple, répartir sur plusieurs "chaînes" les entrées en fonction de l'interface d'origine pour les compter, on utilisera une "vmap" :
flush table ip ENTREE; table ip ENTREE { |
Après quelques instants on liste :
nft list table ENTREE table ip ENTREE { chain INP_INT { counter packets 53 bytes 8010 } chain INP_EXT { counter packets 2 bytes 64 } chain INP_LOC { counter packets 12 bytes 774 } chain DEFAUT { type filter hook input priority 0; iifname vmap { "lo" : jump INP_LOC, "br1" : jump INP_INT, "br0" : jump INP_EXT} } } |
C'est bien ce qui était demandé...
Attention en manipulant les sets il existe quelques problèmes lors de la mise à jour d'une table contenant des sets, il est impossible de "flusher" une table tant qu'il y a un set, il faut détruire le set d'abord avant de faire le flush de la table sinon on arrive à un crash système.
Ce problème est résolu pour les noyaux >= 4.0.0-rc4.
NFTABLES : sets nommés en notation CIDR
NFTABLES : sets nommés en notation CIDR jppJuin 2016 : depuis la version 0.5 de Nftables, la dernière que j'ai chargée et testée, on peut créer des sets nommés en utilisant les intervalles et/ou une notation CIDR. Il faut, en plus de cette version récente, disposer d'un kernel >= 4.7-rc2 avec toutes les options "NFT_...." activées en modules.
Exemple de création d'une table avec deux sets nommés, un en IPV4, l'autre en IPV6 :
table ip TEST { set DISPAT { type ipv4_addr; flags interval; }; # bien noter les deux ";" chain BIDON { type filter hook input priority 0; set EXAMPLE { type ipv6_addr; }; |
On peut ensuite ajouter des éléments aux sets, par exemple :
add element TEST DISPAT { 192.168.2.2,192.168.2.4 }; add element TEST DISPAT { 192.168.3.0/24 }; add element TEST DISPAT { 192.168.0.0/24, 192.168.1.0/24 }; add element TEST DISPAT { 192.168.4.10 - 192.168.4.127 }; La commande de liste du set donne le résultat suivant : nft list set TEST DISPAT table ip TEST { set DISPAT { type ipv4_addr flags interval elements = { 192.168.0.0/23, 192.168.2.1, 192.168.2.2, 192.168.2.4, 192.168.3.0/24, 192.168.4.10-192.168.4.127} } } |
On peut remarquer la "fusion" réalisée automagiquement de "192.168.0.0/24, 192.168.1.0/24".
IPV6 n'étant pas activé chez moi (ni chez mon FAI) je n'ai pas fait de tests sur les sets IPV6.