16 juin 2021

Fail2ban et python pour piloter un Firewall central

Précédemment, nous avons vu Fail2ban configuré pour Dovecot et Postfix, mais l’inconvénient de la solution proposée dans une architecture de serveurs derrière un routeur, c’est que pour le coup, chaque serveur aura ses propres règles de blocage.

J’aimerais bien centraliser tout. Si un boulet se présente sur le serveur mail, autant le bloquer sur tout et ce, dès son arrivée sur mon routeur…

Du coup, il faut trouver le moyen de faire le blocage directement dans le firewall de ce dernier. Quid ?

Installer fail2ban sur le routeur ? Pourquoi pas, mais il lui faudrait donc la possibilité de lire les logs des autres VMS…

Des partages NFS ? Non…

Rapatrier les logs sur le routeur pour que fail2ban fasse son taf ? Bof, mon routeur n’a pas pour vocation de centraliser les logs…

Hum… Fail2ban sur le routeur n’est pas la solution.

Le truc serait de garder fail2ban sur chaque machine avec ses logs et sa config et de piloter le firewall du routeur…

En voila une bonne idée !

Pour cela, on va passer par un truc que j’affectionne, un petit système client-serveur en python. C’est rapide à mettre en place et ça répond parfaitement à mon problème.

Sur le routeur, un serveur en écoute des instructions des clients pour modifier le firewall. Et sur chaque serveur, un client  actionné par fail2ban pour balancer l’ordre.

Simple non ?

 

II – Sur le routeur

A – De nouvelles chaines

Sur le firewall, on va déjà préparer le terrain et créant des chaines spécifiques.

Je pars du principe que vous utilisez un fichier pour les règles du firewall. On va dire qu’il s’appelle firewall.sh.

Donc, dans firewall.sh, après :

#!/bin/bash

iptables -t filter -F
iptables -t filter -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

Ajoutez :

# creation des chaines
iptables -N f2b-postfix
iptables -N f2b-dovecot
# on ajoute un retour dans ces chaines (pour revenir à INPUT ou FORWARD, selon le cas)
iptables -A f2b-postfix -j RETURN
iptables -A f2b-dovecot -j RETURN
# Puis on indique dans INPUT et FORWARD de traverser les nouvelles chaines.
iptables -I INPUT -j f2b-postfix
iptables -I INPUT -j f2b-dovevot
iptables -I FORWARD -j f2b-postfix
iptables -I FORWARD -j f2b-dovecot

[...]

On reprend le concept qu’utilise Fail2ban par défaut, à savoir créer une nouvelle chaine, puis faire passer les flux dedans. Si rien ne bloque, ça revient dans les chaines INPUT ou FORWARD (en fonction) et passe les autres règles.

Et j’en profite pour créer une chaine par service, histoire de bien voir par la suite quel service à fait le blocage.

Au passage pensez à autoriser en INPUT sur le port que vous choisirez pour votre serveur.

iptables -t filter -A INPUT -p tcp -s 10.20.1.0/24 --dport 666 -j ACCEPT

Mon réseau interne entre les vms étant en 10.20.1.0/24

Une fois votre fichier firewall.sh ou autre modifié, on exécute :

routeur# ./firewall.sh

 

B – Serveur Python

Maintenant, on va créer le serveur python qui va écouter les ordres des autres VMS, et appliquer les règles.

On installe python :

routeur# apt-get install python

Puis dans un fichier : /srv/pyban.py, vous copiez cela :

#!/usr/bin/env python

import socket
import os
import threading

class ClientThread(threading.Thread):
    def __init__(self, ip, port, clientsocket):
        threading.Thread.__init__(self)
        self.ip = ip
        self.port = port
        self.clientsocket = clientsocket

    def run(self):
        r = self.clientsocket.recv(1024)
        item = r.split(":")
        if item[0] == "ban":
            chain = "iptables -I "+item[2]+" -s "+item[1]+" -j DROP"
            os.system(chain)
        if item[0] == "unban":
            chain = "iptables -D "+item[2]+" -s "+item[1]+" -j DROP"
            os.system(chain)
        else:
            error = 1

tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcpsock.bind(("XXX.XXX.XXX.XXX",666))

while True:
    tcpsock.listen(5)
    (clientsocket, (ip, port)) = tcpsock.accept()
    newthread = ClientThread(ip, port, clientsocket)
    newthread.start()

tcpsock.close()

Tout simple, on a un serveur qui écoute sur le port 666, avec la gestion du multithread, ca ne coute rien.

Il attend comme paramètres ce qu’il faut faire (ban ou unban), l’ip et la chaine dans laquelle on se place.

Pensez bien sur à remplacer XXX.XXX.XXX.XXX par l’ip interne du routeur (par exemple, 10.20.1.1; pas de localhost car sinon, les clients ne pourraient le joindre…)

On peut déjà tester si cela tourne sans erreur :

# python /srv/pyban.py

Ctrl+C pour arrêter.

C – Service

Maintenant, on va en faire un service, histoire qu’il se lance tout seul et soit autonome.

Créez un fichier /etc/systemd/system/pyban.service et mettez y :

Description=Server Python Ban
After=network-online.target

[Service]
Type=idle
ExecStart=/usr/bin/python /srv/pyban.py

[Install]
WantedBy=multi-user.target

Puis on l’active et on le démarre :

# systemctl enable pyban.service
# systemctl start pyban.service

Voila pour le routeur.

II – Sur le serveur

Pour l’exemple, je reprend mon serveur de mail.

A – Client python

Bien évidement, on installe python :

mail# apt-get install python

On va créer ensuite un fichier /srv/pybanclient.py avec dedans :

#!/usr/bin/env python

import socket
import sys

try:
    s = ":";
    seq = (sys.argv[1], sys.argv[2], sys.argv[3]);
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("XXX.XXX.XXX.XXX", 666))
    sock.send(s.join(seq))
    sock.close
except(socket.error):
    sys.exit()

Bien évidement, XXX.XXX.XXX.XXX doit être remplacé par l’ip du routeur.

B – Fail2ban

1 – Action

On va créer un fichier action nommé /etc/fail2ban/action.d/pyban.conf avec ce qui suit dedans :

[Definition]

actionban = python /srv/pybanclient.py ban <ip> <name>
actionunban = python /srv/pybanclient.py unban <ip> <name>
2 – Jail

On va modifier nos deux jails.

Tout d’abord /etc/fail2ban/jail.d/postfix.conf :

[postfix-sasl]
enabled = true
filter = postfix-sasl
action = pyban[name=f2b-postfix]
         mail[name=Postfix SASL]
bantime = 3600
maxretry = 2
logpath = /var/log/mail.log

Puis /etc/fail2ban/jail.d/dovecot.conf :

[dovecot]
enabled = true
filter = dovecot
action = pyban[name=f2b-dovecot]
         mail[name=Dovecot]
bantime = 3600
maxretry = 2
logpath = /var/log/mail.log

Et on termine en rechargeant :

mail# service fail2ban reload

 

III – Test

Rien de plus simple.

Sur le serveur :

serveur# fail2ban-client set postfix-sasl banip XXX.XXX.XXX.XXX

Sur le routeur :

routeur# iptables -L -n

On doit voir l’ip apparaitre dans la bonne chaine avec un DROP devant .

Et si on déban sur le serveur :

serveur# fail2ban-client set postfix-sasl unbanip XXX.XXX.XXX.XXX

Ce doit être nettoyé sur le routeur :

routeur# iptables -L -n

 

iV – Conclusion

Bah voila, on a enfin notre fail2ban qui peut piloter un FW en amont afin de voir les boulets bloqués sur tous nos services. Rien de bien sorcier au final…

Par la suite, on pourra rajouter d’autres services en pilotage en rajoutant leurs règles dans le FW du routeur et avec des clients python sur les serveurs.

 

 

 

 

Laisser un commentaire

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