Tutoriel révisé en décembre 2022 / Debian 11
Dans ce troisième article de la série sur Nftables, nous allons scripter notre premier firewall sur un routeur.
Admettons que je sois sur un routeur entre internet et un lan. Ip externe 66.66.66.66 et IP interne 10.10.10.254. Sur ce réseau 10.10.10.0/24, un serveur Web 10.10.10.69, qu’on veut rendre accessible de l’extérieur.
On va créer un fichier qui va nous servir à scripter notre firewall :
# touch /srv/firewall_basic # chmod +x /srv/firewall_basic
Ensuite, on édite ce fichier qu’on va remplir petit à petit.
I – Préparation
On va commencer notre script par :
#!/usr/sbin/nft -f flush ruleset add table filter add table nat
Avant tout, on flush, puis on ajoute deux tables: filter et nat
Puis on ajoute nos chaines de bases avec les hooks et en police par défaut, drop :
add chain filter input { type filter hook input priority 0; policy drop ;} add chain filter output { type filter hook output priority 0; policy drop ;} add chain filter forward { type filter hook forward priority 0; policy drop ;} add chain nat prerout { type nat hook prerouting priority 0; } add chain nat postrout { type nat hook postrouting priority 0; }
Par défaut donc, on bloque tout.
II – Statefull
On va ajouter les règles qui transforme notre routeur stateless en statefull, afin d’autoriser les retours :
add rule filter input ct state established accept add rule filter output ct state established accept add rule filter forward ct state established accept
On positionne une règle pour les paquets established dans chaque chaine. Ces règles utilisent conntrack (ct) pour vérifier l’état (state). Si established, on accepte.
III – Flux du routeur
Maintenant, passons au flux en entrée et sortie, pour le routeur lui même.
A – Entrée
En entrée, on veut accepter le loopback :
add rule filter input iifname "lo" accept
Le ping :
add rule filter input ip protocol icmp accept
Et le ssh depuis une IP spécifique (interne à mon réseau par exemple) :
add rule filter input ip saddr 10.10.10.250 tcp dport 22 counter accept
B – Sortie
En sortie, le loopback et le ping :
add rule filter output oifname "lo" accept add rule filter output ip protocol icmp accept
Puis, histoire de pouvoir aller sur le net, le http/https et le dns :
add rule filter output ip protocol tcp tcp dport { 80,443} accept add rule filter output ip protocol udp udp dport 53 accept
Pour le 80,443, on utilise un set, on reviendra sur cette notion plus tard.
IV – Flux en forward
Maintenant que nous avons vu le flux pour le routeur, voyons les flux en transit.
A – DNAT
La, on parle des paquets arrivant depuis le net, destinés au serveur web sur 10.10.10.69.
Tout d’abord on change la destination :
add rule nat prerout daddr 66.66.66.66 tcp dport { 80,443} counter dnat 10.10.10.69
Pourquoi ajouter l’ip de destination ? Tous nos paquets demandant notre serveur web arrivent bien avec cette destination !?
Oui, mais pas que…
Le prerouting est la premier chose que traversent les paquets qui font net -> serveur Web… mais il en va de meme pour les paquets faisant réseau interne -> internet.
Du coup, si on ne précise pas, nos machines du réseau interne, dès lors qu’elles demanderaient n’importe quelle ip sur le port 80 ou 443, ce serait redirigé vers le serveur Web…. Donc, on précise …
Ensuite, on autorise le forward :
add rule filter forward ip protocol tcp ip daddr 10.10.10.69 tcp dport { 80,443} accept
Et roule pedro….
B – SNAT
On va vouloir que notre serveur web puisse lui aussi aller sur interne, pour faire des MAJ par exemple.
On autorise le forward ou l’on utilise la source pour filtrer :
add rule filter forward ip protocol udp ip saddr 10.10.10.69 udp dport 53 counter accept add rule filter forward ip protocol tcp ip saddr 10.10.10.69 tcp dport { 80,443} counter accept
Puis le SNAT :
add rule nat postrout ip saddr 10.10.10.69 snat 66.66.66.66
Le curieux pourrait se dire, tiens, pourquoi on reprécise sur l’ip source ?
Et bien, si on ne précise rien, tous les paquets traversant postrout seront snaté, y compris ceux du routeur lui même pour le réseau 10.10.10.0/24… et ça, on ne veut pas…
Précision concernant les hooks prérout et postrout :
Si vous en utilisez qu’un seul, il faut quand même créer le hook de l’autre : si vous avez un prérout, faut avoir un postrout meme vide, et vice-versa.
En effet, Netfilter en aura besoin pour faire les manips inverses automatiquement.
V – Conclusion
On peut sauvegarder notre script.
Y’a plus qu’à exécuter :
# ./srv/firewall_basic
And voila !
# nft list ruleset table ip filter { chain input { type filter hook input priority 0; policy drop; ct state established accept iifname "lo" accept ip protocol icmp accept ip saddr 10.10.10.250 tcp dport ssh counter packets 0 bytes 0 accept } chain output { type filter hook output priority 0; policy drop; ct state established accept oifname "lo" accept ip protocol icmp accept tcp dport { http, https } accept udp dport domain accept } chain forward { type filter hook forward priority 0; policy drop; ct state established accept 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 ip nat { chain prerout { type nat hook prerouting priority 0; policy accept; tcp dport { http, https } dnat to 10.10.10.69 } chain postrout { type nat hook postrouting priority 0; policy accept; ip saddr 10.10.10.69 snat to 66.66.66.66 } }
Ce qui est bien et qu’on va exploiter à partir de maintenant, c’est que ce genre de sortie, on peut tout à fait s’en servir dans un fichier de configuration: cela est bien plus simple à administrer.
On voit ça dans la partie suivante avec la mise en place d’un firewall plus évolué.