19 mars 2024

Tuto Nftables, partie I: un peu de théorie

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.
  • 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 !

 

 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *