Pare-feu : iptables ou netfilter ?

Après avoir lu sur un forum qu’il devenait assez compliqué d’avoir à la fois iptables et nftables d’installés sur le système pour la gestion du pare-feu, je me suis penché sur le sujet (auquel je ne connaissais rien) et décidé d’utiliser netfilter en lieu et place de ce bon vieux iptables, puisque c’est le futur (et même le présent !).

Ça n’a pas été aussi simple que prévu, puisque après avoir enlevé iptables, je me suis retrouvé avec un problème avec mes machines virtuelles, leur interface réseau virtuelle ne démarrant plus (dépendance de libvirt sur iptables).

Mais par contre j’ai appris plein de choses sur le pare-feu, et c’était aussi le but recherché, à savoir de comprendre un peu mieux comment tout ça fonctionne sur Debian. Mes règles sont désormais directement créées dans nftables, excepté celles de libvirt. Et je me suis débarrassé de ufw.

Voyons voir un peu tout ça dans le détail, en commençant par expliquer de quoi il retourne, comment je suis passé de iptables à nftables, puis le problème avec libvirt que ça a déclenché, et enfin la solution, qui je le dis tout de suite consiste à conserver iptables, puisque libvirt en a besoin (et ça risque de durer !). Ceci n’empêchant pas d’utiliser nftables pour le reste, c’est déjà ça.

Les bases

Je ne suis pas pour autant devenu un spécialiste des pare-feu, que les choses soient claires. Mais je vais vous expliquer ce que j’ai compris sur le sujet. Si cela paraît un peu confus au départ, tout s’éclaire en mettant en pratique et en créant nos règles. Mais il faut assimiler deux ou trois choses avant de commencer.

Pour une explication plus complète, vous pouvez suivre ce cours en ligne qui explique bien (et en français) tout le fonctionnement et la mise en place. Ce ne sont pas les tutos en anglais qui manquent, comme celui-ci, ou encore celui-là, toutefois aucun n’indique comment sauvegarder les règles créées et les retrouver après un redémarrage, ce qui est assez surprenant… 😯 Peut-être ai-je loupé quelque chose ? Toujours est-il que j’indique ici comment j’ai fait, au moins ça marche !

NetFilter

Le pare-feu dans Linux, c’est NetFilter. Il est intégré au noyau, et comporte des hooks (crochets) qui permettent d’intercepter tout ce qui entre et qui sort du système (je laisse passer, je bloque, etc…). Il existe 5 types de hooks : input, output, forward, prerouting, postrouting (les deux derniers n’existant que sur les routeurs).

Que ce soit iptables ou nftables, les deux permettent de communiquer avec NetFilter. Mais aujourd’hui, iptables est délaissé pour des raisons de performance, de sécurité, etc… Une réécriture du code s’imposait. Ainsi, Debian est fourni avec nftables depuis Debian 10 (aka « Buster »), sorti en 2019. Mais le paquet iptables est toujours installé par défaut ! 🙄

ufw & iptables

Auparavant, j’utilisais ufw (Uncomplicated FireWall), qui servait d’interface pour iptables. Quand on installe ufw, iptables est automatiquement installé (si ce n’est pas déjà le cas sur le système). En d’autres termes, ufw dépend de iptables :

$ sudo apt show ufw
Package: ufw
Version: 0.36.1-4
Priority: optional
Section: admin
Maintainer: Jamie Strandboge 
Installed-Size: 861 kB
Depends: iptables, lsb-base (>= 3.0-6), ucf, python3:any, debconf (>= 0.5) | debconf-2.0
Suggests: rsyslog

Et voilà ce que je faisais précédemment sur mon PC pour déclarer les quelques règles dont j’avais besoin :

$ sudo apt install ufw
$ sudo ufw enable
$ sudo ufw status verbose
$ sudo ufw allow 873 comment 'rsync'
$ sudo ufw allow 22 comment 'ssh'
$ sudo ufw allow 137,138/udp comment 'samba'
$ sudo ufw allow 139,445/tcp comment 'samba'
$ sudo ufw allow 1714:1764/udp comment 'KDE Connect'
$ sudo ufw allow 1714:1764/tcp comment 'KDE Connect'

On pourrait donc croire que l’on crée des règles iptables à l’aide d’ufw. Mais /usr/sbin/iptables est désormais un lien symbolique qui pointe sur iptables-nft qui utilise l’API de nftables pour insérer toutes les règles dans des tables nft spéciales appelée « filter » et « nat ».

Cela permet de voir les règles iptables comme des règles nftables sur une commande comme sudo nft list ruleset.

Et donc les commandes ci-dessus que je rentrais étaient en fait « traduites » pour être vues comme des règles nft ! On peut le vérifier facilement et tapant la commande sudo nft list table ip filter |grep ufw- : on voit alors tout un tas de chaînes intitulées « ufw-before… », « ufw-after… » , etc…

D’où l’idée de tout simplifier en utilisant directement nftables !

nftables

nftables est donc le nouvel intermédiaire qui permet de communiquer avec NetFilter. Il a été conçu avec l’idée de corriger les problèmes d’iptables.

On crée d’abord une table, qui n’est au final qu’un conteneur pour s’organiser. Il n’y a pas de table prédéfinie, mais il existe des familles de tables : IP, ARP, IP6, INET (qui regroupe ipv4 et ipv6), et enfin BRIDGE.

Chaque table va contenir des chaînes qui elles vont stocker les règles proprement dites. Il n’existe pas de chaînes prédéfinies, mais elles doivent se rattacher aux hooks (crochets).

Les règles définissent les actions à appliquer. Elles se composent d’un filtre suivi d’une action. C’est assez simple à comprendre.

Mise en place

Je vais maintenant créer les règles dont j’ai besoin. Elles sont très basiques et consistent à ouvrir quelques ports pour mes services, à savoir ssh, rsync, samba et KDE Connect (ce dernier est pour mon smartphone).

Je commence donc par créer ma propre table appelée « pc-filtre » (désolé, je n’ai pas trouvé mieux ! 😉 ). Je choisis d’utiliser la famille inet, qui a l’avantage de regrouper ipv4 et ipv6 :

$ sudo nft add table inet pc-filtre

Puis je crée deux chaînes, pour séparer l’entrée et la sortie. On précise bien à quel hook on veut se rattacher, notre chaîne « ipnut » va donc se rattacher au hook « INPUT ».

La notion de priorité permet de gérer les cas où l’on a plusieurs chaînes, la priorité la plus basse prend alors le pas sur les autres ; mais dans mon cas, je ne vais utiliser cette possibilité de réglage plus fin, je crée juste deux chaînes basiques :

$ sudo nft add chain inet pc-filtre input { type filter hook input priority 0 \;}
$ sudo nft add chain inet pc-filtre output { type filter hook output priority 0 \;}

Et enfin je crée mes règles dans la chaîne « input » :

$ sudo nft add rule inet pc-filtre input tcp dport 22 accept
$ sudo nft add rule inet pc-filtre input tcp dport 139 accept
$ sudo nft add rule inet pc-filtre input tcp dport 445 accept
$ sudo nft add rule inet pc-filtre input udp dport 137 accept
$ sudo nft add rule inet pc-filtre input udp dport 138 accept
$ sudo nft add rule inet pc-filtre input tcp dport 873 accept
$ sudo nft add rule inet pc-filtre input udp dport 873 accept
$ sudo nft add rule inet pc-filtre input tcp dport 1714-1764 accept
$ sudo nft add rule inet pc-filtre input udp dport 1714-1764 accept

Et je vérifie :

$ sudo nft list table inet pc-filtre
table inet pc-filtre {
	chain input {
		type filter hook input priority filter; policy accept;
		tcp dport 22 accept
		tcp dport 139 accept
		tcp dport 445 accept
		udp dport 137 accept
		udp dport 138 accept
		tcp dport 873 accept
		udp dport 873 accept
		tcp dport 1714-1764 accept
		udp dport 1714-1764 accept
	}

	chain output {
		type filter hook input priority filter; policy accept;
	}
}

Terminé ? pas vraiment, il va falloir faire en sorte que cette table soit chargée au démarrage, parce que à ce stade, si vous redémarrez le PC, tout est perdu ! Le plus bizarre c’est que je n’ai rien trouvé à ce sujet dans les différents tutoriaux, comme si les commandes tapées étaient automatiquement conservées… Sauf qu’après un redémarrage, j’avais tout perdu. Donc, bon, voilà au cas où vous observez le même comportement :

Le plus simple est de créer un fichier contenant vos règles :

$ sudo nft list ruleset > ~/nftables-pc-filter.conf

Ensuite éditer ce fichier pour y ajouter une première ligne indiquant qu’il s’agit d’un fichier pour nft, ce qui me donne au final :

$ cat ~/nftables-pc-filter.conf
#!/usr/sbin/nft -f

table inet pc-filtre {
	chain input {
		type filter hook input priority filter; policy accept;
		tcp dport 22 accept
		tcp dport 139 accept
		tcp dport 445 accept
		udp dport 137 accept
		udp dport 138 accept
		tcp dport 873 accept
		tcp dport 1714-1764 accept
		udp dport 1714-1764 accept
		udp dport 873 accept
	}

	chain output {
		type filter hook input priority filter; policy accept;
	}
}

Puis je copie ce fichier dans /etc, et j’édite le fichier de configuration par défaut de nftables, soit /etc/nftables.conf, et d’y ajouter un « include » de mon fichier :

$ cat /etc/nftables.conf 
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
	chain input {
		type filter hook input priority filter;
	}
	chain forward {
		type filter hook forward priority filter;
	}
	chain output {
		type filter hook output priority filter;
	}
}

include "/etc/nftables-pc-filter.conf"

Et voilà, cette fois,je peux redémarrer, toutes mes règles seront chargées à chaque démarrage.

Importer les règles de iptables

Si vous avez beaucoup de règles existantes, il est possible de faire une migration :

$ iptables-save > save.txt
$ iptables-restore-translate -f save.txt > ruleset.nft

Il ne reste plus qu’à inclure votre ruleset.nft au démarrage de nftables. Voir cette page pour plus d’infos.

Problème rencontré

Une fois ceci fait, j’ai supprimé les deux paquets ufw et iptables, puis j’ai passé le service nftables à « enable » :

$ sudo apt remove ufw iptables
$ sudo systemctl status nftables
● nftables.service - nftables
     Loaded: loaded (/lib/systemd/system/nftables.service; enabled; preset: enabled)
     Active: active (exited) since Fri 2022-08-12 18:10:54 CEST; 16h ago
       Docs: man:nft(8)
             http://wiki.nftables.org
   Main PID: 716 (code=exited, status=0/SUCCESS)
        CPU: 6ms

J’avais une VM de démarrée à ce moment-là, la fenêtre a brutalement disparue ! Et dans la fenêtre du gestionnaire de machines virtuelles (virt-manager), ma liste de VMs avait disparue aussi ! 😯

J’ai désinstallé/réinstallé l’ensemble des paquets nécessaires pour KVM/QEMU, et j’ai tout de suite retrouvé la liste de mes VMs. Mais au lancement, message d’erreur :

Erreur libvirt : pas de réseau actif

J’avais déjà vu ce message, il concerne le réseau virtuel créé par libvirt pour gérer l’interface réseau (en NAT) des VMS. Libvirt est l’API de virtualisation utilisée par KVM.

Hélas, impossible de démarrer ce fameux réseau, et le message est assez clair : il ne trouve pas iptables, et pour cause, puisque je viens de le désinstaller !

$ sudo virsh net-list --all
 Nom       État      Démarrage automatique   Persistent
---------------------------------------------------------
 default   inactif   Oui                     Oui

$ sudo virsh net-autostart default
Réseau default marqué en démarrage automatique

$ sudo virsh net-start default
erreur :Impossible de démarrer le réseau default
erreur :internal error: Failed to apply firewall rules /usr/sbin/iptables -w --table filter --list-rules: libvirt:  erreur : cannot execute binary /usr/sbin/iptables: Aucun fichier ou dossier de ce type

À ce stade, il apparaît que libvirt a une dépendance sur iptables. Sur Debian, elle se trouve dans le paquet libvirt-daemon-system :

$ sudo apt show libvirt-daemon-system
Package: libvirt-daemon-system
Version: 8.5.0-1
Priority: optional
Section: admin
Source: libvirt
Maintainer: Debian Libvirt Maintainers 
Installed-Size: 460 kB
Depends: adduser, gettext-base, iptables | firewalld, libvirt-clients (= 8.5.0-1), libvirt-daemon (= 8.5.0-1), libvirt-daemon-config-network (= 8.5.0-1), libvirt-daemon-config-nwfilter (= 8.5.0-1), libvirt-daemon-system-systemd (= 8.5.0-1) | libvirt-daemon-system-sysv (= 8.5.0-1), logrotate, policykit-1, debconf (>= 0.5) | debconf-2.0

Pour être plus précis, la lib dépend soit de iptables, soit de firewalld :

iptables | firewalld

OK, firewalld est un « wrapper » censé faciliter la gestion du pare-feu et l’écriture des règles nftables. De ce que j’ai pu lire, il est assez compliqué et peu adapté à la gestion de quelques règles simples comme c’est le cas sur un poste de travail.

Mais, bon, je fais le test par acquis de conscience : firewalld était bien installé sur mon système, mais le service non démarré. Je l’autorise (sudo systemctl enable firewalld.service), puis redémarre le système. Mais le même message d’erreur est toujours là au démarrage du réseau virtuel : libvirt réclame iptables.

Conclusion

Il y a bien une dépendance de libvirt à iptables, et ce n’est pas normal que je puisse désinstaller le paquet iptables sans être averti de celle-ci. J’ai donc fini par le réinstaller, tout en gardant mon nouveau fichier de règles nftables.

On peut d’ailleurs très bien continuer à gérer son pare-feu avec iptables, puisque les règles sont traduites automatiquement. Peut-être que sur des serveurs aux règles complexes, c’est effectivement plus sain de migrer vers une solution purement nftables.

J’ai d’abord posté un sujet sur le forum debian-fr.org, où j’ai reçu de l’aide même c’était parfois un peu tendu (!). Mais cela m’a pas mal éclairé et amené dans la bonne direction. En l’absence de solutions, j’ai fini par envoyer un mail au support utilisateur de libvirt (chez red-hat), et le lendemain j’avais une longue réponse très détaillée sur le sujet (génial !). D’après cette personne, si Debian te laisse désinstaller iptables, il y a un problème car libvirt a bien une dépendance, et ne peut fonctionner directement avec nftables.

J’ai alors posté sur le forum Debian Users pour signaler le problème : il y a eu des commentaires, mais pas sûr que le problème soit réglé pour l’instant. Apparemment, certains attendent que libvirt soit corrigé (cela devrait fonctionner avec iptables-nft, voir plus haut), quand d’autres émettent l’hypothèse que le patch devrait être fait côté Debian… La situation est plus proche d’un status-quo qu’autre chose semble-t-il.

Voilà, tout cela a été très formateur, j’ai appris plein de choses, comme toujours, et même si iptables est toujours installé sur mon système, les règles sont désormais directement créées à la mode nftables ! 😎

Laisser un commentaire

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