Tutoriel révisé en décembre 2022 / Debian 11
Dans cette première partie de la série sur Nftables, nous allons faire un peu de théorie avant d’attaquer la mise en pratique.
I – Schéma des flux
Un petit schéma simplifié de netfilter :
Local process ^ | | | |Routing |--> input --/ \--> output --\ input interface-> prerouting ->|Decision| |Routing|-> postrouting -> output interface | |------------> forward ---------/ Decision
A gauche, interface d’entrée, à droite, interface de sortie.
Je considère que nous sommes sur une machine qui fait office de routeur, qui a donc le forward activé.
Dans ces flux, netfilter propose des hooks. Ce sont les endroits ou l’on peut intervenir.
Il y a la une première différence entre Iptables et Nftables. En effet, si Iptables nous imposait chaines (Mangle, Input, Output, etc….) et tables de base (nat, filter), maintenant, avec Nftables, on commence avec quelque chose de vide. Nous somme libres de faire ce que l’on veut. Pas de chaines imposées, on vient se brancher où l’on a besoin.
On voit tout ça dans les parties pratiques qui suivront.
Un paquet arrivant à notre hôte suit donc ce chemin :
- Il arrive sur l’interface et est passé à netfilter.
- Premier hook prerouting : on peut faire du DNAT. Utile pour « exposer » un service sur une machine derrière ce routeur, par exemple, un serveur web.
- Ensuite intervient la décision de routage, en fonction donc de la destination :
- Si pour process local :
- filtrage possible avec le hook input.
- Si la paquet n’est pas jeté, il est délivré au process.
- Si pour autre hôte :
- filtrage possible avec le hook forward.
- Si pour process local :
- On va tout de suite parler d’un paquet émis par le système, depuis un process local :
- Il passe une première décision de routage (non représentée)
- un hook output ou l’on pourra filtrer (et éventuellement faire du DNAT, mais c’est rare).
- Ensuite, dernière décision de routage, qui peut voir arriver soit des paquets forwardés soit des paquet en output d’un process local.
- Le dernier hook possible postrouting : on peut faire du SNAT. Utile pour que des machines derrière le routeur puissent communiquer avec le réseau extérieur.
II – Default Policy
Les polices par défauts, qu’est ce ?
Nos hooks prerouting, input, output, forward et postrouting ont un « réglage » qui permet de définir le comportement si aucune règle ne matche.
Sans configurations particulières, par défaut, les polices sont sur accept, c’est à dire que tout est autorisé.
Alors autant sur prerouting et postrouting c’est normal et si on mettait sur drop, on aurait des ennuis (vous verrez pourquoi dans les exemples plus bas), par contre, sur les hooks input, output et forward, il faudra mettre les polices par défaut sur drop. Ainsi, on bloque tout, et on autorisera spécifiquement les flux qui nous intéressent.
III – Statefull, Stateless
Un autre réglage qu’il faudra effectuer : passer le routeur en statefull, afin d’avoir le suivi des connexions (module conntrack du kernel).
Comme on est dans la partie théorique, j’explique maintenant :
Une transaction TCP/IP, c’est le fameux 3way handshake
Le client fait SYN, le serveur répond SYN/ACK, et le client valide avec ACK et ensuite, ça cause….
Imaginons maintenant que nous filtrons tout en input et output (policy drop) et que voulions accepter le ssh.
Notre client est 12.34.56.78, notre serveur 66.66.66.66
Le client émet un premier paquet SYN :
src:12.34.56.78:55555 / dst:66.66.66.66:22
Le 55555 est le port aléatoire choisi par le client ssh.
Notre hôte reçoit ce paquet. Il check dans la table ip_conntrack et voit que ce paquet ne fait pas partie d’une connexion établie, il lui colle l’état NEW.
La destination étant lui-même, on passe la première décision de routage en arrivant dans le hook input et la on filtre :
ce qui a comme port destination 22 : c'est OK
Ça arrive au démon SSH, qui va donc renvoyer un SYN/ACK avec un paquet :
src:66.66.66.66:22 / dst:12.34.56.78:55555
Ce paquet arrive dans le hook output où par défaut, il va être droppé par la policy.
Pour info, ce paquet est également marqué par conntrack comme ESTABLISHED.
En effet conntrack a gardé une trace de la requête initiale (12.34.56.78:55555/dst:66.66.66.66:22) et la, il voit un paquet « inverse » (src:66.66.66.66:22/dst:12.34.56.78:55555), donc, c’est un retour: c’est tagué ESTABLISHED.
Alors on est d’accord, au sens TCP, la connexion n’est pas encore vraiment établie vue que le handshake n’est pas terminé, mais il faut que ça fonctionne ainsi, sinon, on serait emmerdé 😉
Plus d’infos ici : https://www.linuxtopia.org/Linux_Firewall_iptables/x1414.html
Bon, pour le moment, passons sur ce fameux état et on va faire comme si on ne savait pas.
Il faudrait donc dire, pour passer le filtre output :
ce qui a comme port source 22 : c'est OK
Donc au final, deux règles.
Et dans le cas contraire, par exemple une ressource web qu’on demande, il faudrait une règle en ouput :
ce qui va vers le port destination 80 : c'est OK
et pour le retour, du coup, en input :
ce qui a comme port source 80 : c'est OK
La encore, deux règles.
On parle alors de stateless, le routeur n’utilise pas les états des connexions. Mais c’est pénible car pour chaque flux, il nous faut deux règles.
Heureusement conntrack est la pour nous aider avec les fameux etat NEW, ESTABLISHED, etc…
Avec juste trois règles (en input, output et forward), on autorisera les paquets marqués ESTABLISHED.
Note :
On trouve souvent sur le grand internet des configs qui font mention de l’état RELATED.
Exemple qu’on croise dans beaucoup de docs, avec iptables :
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
L’état RELATED est utile pour certains protocoles (FTP, IRC, etc…) où le bon fonctionnement nécessite l’ouverture d’autres ports. Mais si on laisse ainsi, ça peut facilement être abusé, donc, on ne met pas.
Et si l’on doit utiliser de tels protocoles, on le réglera précisément afin d’éviter une possible faille sur le firewall.
Plus d’infos : https://home.regit.org/netfilter-en/secure-use-of-helpers/
IV – Routeur et Nat
Nat, c’est « Network Address Translation », ou en français : changement d’adresse réseau. Le Nat implique de se faire au niveau d’un routeur, mais un routeur ne fait pas forcément du Nat… Je m’explique :
Exemple sans NAT et avec deux routeurs, histoire de bien comprendre :
10.10.1.0/24 -- eth0 routeur1 eth1 -- 10.1.1.0/24 -- eth1 routeur2 eth0 -- 10.20.1.0/24
On a trois réseaux :
- 10.10.1.0/24 et 10.20.1.0/24 qui desservent des bécanes. Sur 10.10.1.0/24, le routeur 1 a comme IP 10.10.1.254 et sur 10.20.1.0/24, le routeur 2 a comme IP 10.20.1.254
- 10.1.1.0/24 qui relie les deux routeurs. Routeur 1 a comme IP 10.1.1.1 et le routeur 2, 10.1.1.2.
Au niveau des routes, sur routeur 1:
- 10.10.1.0/24 -> eth0
- 10.1.1.0/24 -> eth1
- 10.20.1.0/24 -> eth1 via 10.1.1.2
Et sur le routeur 2 :
- 10.20.1.0/24 -> eth0
- 10.1.1.0/24 -> eth1
- 10.10.1.0/24 -> eth1 via 10.1.1.1
Pour une machine du réseau 1, gateway : 10.10.1.254 et pour une machine du réseau 2, gateway : 10.20.1.254.
Ainsi, un paquet d’une machine du réseau 1 vers le réseau 2 :
- Le paquet arrive sur le routeur 1, il connait la route vers 10.20.1.0/24, il l’envoi au routeur 2 (10.1.1.2).
- Le routeur 2 reçoit le paquet, il connait la destination, il envoi à la machine.
- La machine répond à 10.20.1.69 et ça revient sans encombres, routeur 2 connaissant la route pour 10.10.1.0/24, etc…
Bon, rien de sorcier.
Par contre, dans le cas ou les deux réseaux ne « se connaissent » pas, il va falloir ruser.
Exemple typique, deux réseau privés connectés sur Internet :
192.168.1.0/24 -- eth0 mabox eth1 -- 66.66.66.0/24 -- eth1 routeurFAI eth0 -- 10.10.1.0/24
On est d’accord, le schéma n’a rien de réel, au cul du routeur FAI, on ne va pas avoir directement un serveur WEB. On va trouver avant, un certains nombres de routeurs et d’autres équipements mais l’idée générale est la.
La box chez nous a comme IP : 192.168.1.254 et coté publique 66.66.66.1 et le site qu’on veut atteindre est sur 66.66.66.66, mais sur une bécane derrière en 10.10.1.69.
Notre client a comme IP 192.168.1.69.
Maintenant voyons les types de NAT que l’on va devoir faire.
A – SNAT
Ou Source Network Address Translation, changement d’adresse source.
Notre client 192.168.1.69 demande un site web sur 66.66.66.69.
src:192.168.1.69:55555 / dst:66.66.66.69:80
Le paquet arrive sur notre box (routeur) et si rien n’est fait, le paquet va partir ainsi sur le net avec une source en 192.168.1.69.
Mais on le sait, ce réseau fait parti de la liste des sous réseaux privés et donc, non routable.
Et pourquoi ma bonne dame ?
Et bien, le serveur recevrait alors un paquet avec une source en 192.168.1.69, qui sera donc utilisé comme destination de la réponse. Et ça, le routeur FAI, bah il ne connait pas.
Tout comme si une machine du réseau 10.10.1.0/69 sortait sur Internet, son réseau 10.10.1.0/24 est inconnu des autres routeur.
Bref, faut changer le paquet avant qu’il ne quitte le réseau.
- Donc notre paquet arrive sur notre box. Conntrack le marque comme NEW.
- Première décision de routage, c’est pour ailleurs, donc forward, on l’on autorise vers le port 80
- Dernière décision de routage et c’est don en postrouting qu’on va faire la modification :
pour chaque paquet à destination d'ailleurs : change la source en 66.66.66.1
Au passage, le routeur garde une trace des modifications qu’il effectue dans la table de NAT.
Le paquet part donc :
src:66.66.66.1:55555 / dst:66.66.66.69:80
Pour le coté serveur, on voit ça après. Mais dans tous les cas, le serveur nous répondra avec un paquet de la forme :
src: 66.66.66.69:80/66.66.66.1:55555
- Ce paquet arrive sur notre box, il est marqué en ESTABLISHED.
- Ensuite un de-nat automatique se fait en prerouting grâce à la trace de la table de NAT.
src:66.66.66.69:80 / dst:192.168.1.69
Et c’est la qu’on comprend que la police en prerouting, si on la positionne sur drop, ça va nous obliger à réécrire des règles implicites.
Comme ce paquet n’est pas pour le routeur, il forward. Forward qui accepte automatiquement les ESTABLISHED et le paquet suit son chemin, vous connaissez l’histoire.
B – DNAT
Ou Destination Network Address Translation, changement d’adresse de destination.
Bon, notre client demande un site web :
src:66.66.66.1:55555 / dst:66.66.66.69:80
Ce paquet arrive sur le routeur FAI, et est marqué NEW, mais le service web étant ailleurs, il faut changer la destination, et avant la première decision de routage s’il vous plait.
Cela se fait donc en prerouting.
- Le paquet arrive sur le routeur
- Dans le prerouting,une règle indique:
si destiné au port 80 : change la destination en 10.10.1.19
- Au passage, inscription dans la table de Nat du changement effectué.
- Lors de la première décision de routage, le paquet sera considéré comme devant être forwardé.
- Il quitte le routeur, direction le serveur.
Le paquet arrive sur notre serveur 10.10.1.69 :
src: 66.66.66.1:55555 / dst:10.10.1.69:80
Il forge la réponse en inversant destination et source. On obtient:
src:10.10.1.69:80 / dst:66.66.66.1:55555
- Le paquet arrive sur le routeur, et est marqué established.
- Décision de routage puis forward (ou l’on accepte les established) et dernier routage.
- Dans le postrouting, de-nat automatique grâce à la trace de la table de NAT :
src:66.66.66.69:80 / dst:66.66.66.1:55555
- Et le paquet part.
La encore, on voit que le drop en postrouting, comme police par défaut, ce serait une ânerie.
V – Conclusion
On a revu les bases de netfilter, et les concepts importants à connaitre.
Je conçois que ce peut être indigeste et si un truc n’est pas clair, n’hésitez pas à m’en faire part.
Maintenant, On va pouvoir passer à la mise en pratique !