iptables : Utilisation de tables « dynamiques »

Après avoir disséqué quelques firewalls d’origines diverses basés sur iptables, un des problèmes observés couramment est l’application de règles à un grand nombre d’IP.

La solution classique consiste à enregistrer les IP concernées dans un fichier et à écrire une boucle qui génère la même règle pour chaque adresse. Exemple simplifié :

uniq ${FILE_BLACKLISTED_IP} | while read ip ; do
      ${ipt} -A INPUT -s ${ip} -j LOGnDROP
done

avec le fichier « FILE_BLACKLISTED_IP » contenant une IP par ligne.

Parmi les défauts d’une telle méthode, citons :

  • un temps de chargement du firewall pouvant devenir conséquent (certains fichiers dépassent le millier de lignes avec un « echo » pour chacune) ;
  • un listing des règles du firewall lourd et difficile à analyser (résultat de « iptables -L -n ») ;
  • etc.

Cette méthode oblige à ressaisir inlassablement la même séquence iptables pour ajouter ou retirer une IP de la liste des bannies :

  • bannir une IP :
    13:23:35 mafalda ~ # iptables -A INPUT -s 10.0.0.155 -j LOGnDROP
  • ré autoriser les connexions d’une IP :
    13:37:18 mafalda ~ # iptables -D INPUT -s 10.0.0.155 -j LOGnDROP

Je vous laisse envisager les jeux d’écriture induits par la nécessité de recharger uniquement la liste des adresses bannies par exemple…

Utilisation de tables dynamiques

De ces constats est venue l’envie de gérer des tables d’adresses dynamique via iptables. IPsets (http://ipset.netfilter.org/) répond à ce besoin mais n’est pas encore disponible sur la plupart des systèmes en production.

Pour l’heure, le seul module trouvé « partout » et gérant des tables dynamiques avec iptables est « xt_recent » (déjà utilisé pour les règles anti brute force voir Règles iptables anti brute force pour SSH).

Mise en place

La méthode consiste à appliquer une/des règle(s) donnée(s) aux IP d’une table « xt_recent » peuplée manuellement.

Ce qui donne dans le cas de l’exemple précédent :

chmod u+w /sys/module/xt_recent/parameters/ip_list_tot
echo 5000 > /sys/module/xt_recent/parameters/ip_list_tot
...
${ipt} -A INPUT -m recent --rcheck --name BLACKLISTED_IP -j LOGnDROP
 
uniq ${FILE_BLACKLISTED_IP} | while read ip ; do
      echo +${ip} > /proc/net/xt_recent/BLACKLISTED_IP
done
...

Dans le détail :

  • ajout des droits d’écriture sur le fichier « /sys/module/xt_recent/parameters/ip_list_tot » qui défini la taille maximale des tables gérées par « xt_recent » ;
  • définition de la taille des tables à 5000 entrées (adaptez selon vos besoins par défaut ip_list_tot=100) ;
  • règle iptables : en entrée, utiliser le module « recent » (‘-m recent’), si l’adresse source appartient à la liste (‘–rcheck’) nommée « BLACKLISTED_IP » (‘–name BLACKLISTED_IP’) y appliquer LOGnDROP (‘-j LOGnDROP’) ;
  • peuplement de la table via, pour chaque ligne du fichier « FILE_BLACKLISTED_IP », un echo +IP > fichier xt_recent de la liste.

Note : Si nous avions voulu tester l’adresse destination au lieu de l’IP source, nous aurions utilisé « –rcheck –rdest ». Par défaut, « –rcheck » s’applique aux adresses sources.

Note II : La définition de la taille des tables, dans « ip_list_tot« , n’apparaît qu’une fois dans le fichier de définition du firewall.

Analyse du résultat

Premier constat : c’est un peu plus long à taper. Là j’avoue ne pas pouvoir y faire grand chose…

Pour le reste, les tests ont été effectués via deux scripts bannissant un même fichier de 850 IP suivant les deux méthodes décrites précédemment.

Temps de chargement

Ancienne méthode :

16:37:21 mafalda iptables # time ./via_insertion_de_regles.sh
 
real    0m1.226s
user    0m0.068s
sys     0m0.072s

Tables dynamiques :

16:37:40 mafalda iptables # time ./via_table_dynamique.sh 
 
real    0m0.058s
user    0m0.016s
sys     0m0.024s

Lisibilité

Ancienne méthode :

16:39:02 mafalda iptables # iptables -L -n | head -n 10
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
LOGnDROP   all  --  216.147.137.83         0.0.0.0/0           
LOGnDROP   all  --   193.124.133.217         0.0.0.0/0           
... 848 lignes de ce type ...

Note : Essayez sans le « -n » juste pour voir ?!?

Tables dynamique :

16:42:57 mafalda iptables # iptables -L -n | head -n 10
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
LOGnDROP   all  --  0.0.0.0/0            0.0.0.0/0            recent: CHECK name: BLACKLISTED_IP side: source
...

Note : Et là, sans le « -n » ça donne quoi ?!?

Bannir une IP

Ancienne méthode :

16:50:44 mafalda iptables # iptables -A INPUT -s 192.168.1.2 -j LOGnDROP

Table dynamique :

16:50:50 mafalda iptables # echo +192.168.1.2 > /proc/net/xt_recent/BLACKLISTED_IP

Rétablir une IP

Ancienne méthode :

16:50:44 mafalda iptables # iptables -A INPUT -s 192.168.1.2 -j LOGnDROP

Table dynamique :

16:52:46 mafalda iptables # echo -192.168.1.2 > /proc/net/xt_recent/BLACKLISTED_IP

« Flush » de la table des IP bannies

Ancienne méthode : heuuuu….

Table dynamique :

16:53:05 mafalda iptables # echo / > /proc/net/xt_recent/BLACKLISTED_IP

Lister les IP impactées par une règle

Ancienne méthode : Voir « iptables -L -n »

Table dynamique :

16:54:46 mafalda iptables # cat /proc/net/xt_recent/BLACKLISTED_IP

Sauvegarder les règles

Ancienne méthode :

17:03:13 mafalda iptables # iptables-save > myrules

ATTENTION : Sauvegarde les règles une par une ! encore plus long à recharger…

Tables dynamiques :

17:03:13 mafalda iptables # iptables-save > myrules

Et penser à ajouter les IP blacklistées nouvellement dans le fichier black list.

Appliquer les règles à un sous réseau

Ancienne méthode : ajouter le réseau au fichier des IP blacklistées (ie : 192.168.1.0/24).

Tables dynamiques : heuuuu… utiliser la vieille méthode ? (il existe moins de réseaux à bannir que d’IP quand même…)

Exemples

Bannir un lot d’adresses sur une passerelle

Ces quelques lignes :

  • créent une chaîne nommée « LOGnDROP_Blacklisted » enregistrant les connexions et dropant les paquets ;
  • en entrée : recherchent si la source est déclarée dans « BLACKLISTED_IP ». Si oui application de « LOGnDROP_Blacklisted » ;
  • en sortie : recherchent si la destination est déclarée dans « BLACKLISTED_IP ». Si oui application de « LOGnDROP_Blacklisted » ;
  • recherchent si la source ou la destination d’un forward (paquet transféré) est déclarée dans « BLACKLISTED_IP ». Si oui application de « LOGnDROP_Blacklisted » ;
  • définissent la taille maximale d’une table (5000 entrées) ;
  • initialisent la table « BLACKLISTED_IP » avec le contenu du fichier « /etc/firewalld/blacklist.conf ».
...
# Log des drops :
${ipt} -N LOGnDROP_Blacklisted
${ipt} -A LOGnDROP_Blacklisted  -m limit --limit 6/m -j ULOG --ulog-nlgroup 1 --ulog-prefix 'DROP>Blacklisted : '
${ipt} -A LOGnDROP_Blacklisted  -j DROP
...
### BLACKLIST FROM IP to ALL ###
${ipt} -A INPUT   -m recent --rcheck --name BLACKLISTED_IP -j LOGnDROP_Blacklisted
${ipt} -A OUTPUT  -m recent --rcheck --rdest --name BLACKLISTED_IP -j LOGnDROP_Blacklisted
${ipt} -A FORWARD -m recent --rcheck --name BLACKLISTED_IP -j LOGnDROP_Blacklisted
${ipt} -A FORWARD -m recent --rcheck --rdest --name BLACKLISTED_IP -j LOGnDROP_Blacklisted
 
# N'apparaît qu'une fois :
echo 5000 > /sys/module/xt_recent/parameters/ip_list_tot
 
uniq /etc/firewalld/blacklist.conf | while read ip ; do
        echo +${ip} > /proc/net/xt_recent/BLACKLISTED_IP
done
...

Note : l’exemple utilise les logs via ulogd. Adaptez la chaîne nommée « LOGnDROP_Blacklisted » en cas de besoins.

Autoriser les connexions http à un lot d’IP

${ipt} -N LOGnACCEPT
${ipt} -A LOGnACCEPT -m limit --limit 6/m -j LOG --log-prefix '[fw accept] : '
${ipt} -A LOGnACCEPT -j ACCEPT
...
${ipt} -A INPUT -d ${ip_locale} -p tcp --dport http -m recent --rcheck --name AUTHORIZED_HTTP -j LOGnACCEPT
${ipt} -A OUTPUT -s ${ip_locale} -p tcp --sport http -m state --state ESTABLISHED,RELATED -m recent --rcheck --rdest --name AUTHORIZED_HTTP -j LOGnACCEPT
 
echo +10.0.10.134 > /proc/net/xt_recent/AUTHORIZED_HTTP

Cette fois la chaîne nommée « LOGnACCEPT » utilise le gestionnaire de log standard pour enregistrer les connexions en http à la machine locale.

Dans cet exemple, seul l’IP « 10.0.10.134 » sera acceptée par ce jeu de règles.

Conclusion

La technique n’est pas parfaite et manque cruellement de la possibilité de définir des plages IP complètes, mais elle est efficace en offre une meilleur lisibilité du firewall tout en en simplifiant l’usage (black list via un simple « echo »…).

En attendant les solutions adaptées telles qu’IPSets, cette implémentation des tables dynamiques avec iptables offre quelques perspectives de souplesse et d’efficacité appréciables.

Une réflexion au sujet de « iptables : Utilisation de tables « dynamiques » »

Laisser un commentaire

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