19 mars 2024

Tuto Nftables, partie III: Firewall et Nat basique

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é.

 

Laisser un commentaire

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