État des lieux

Mes besoins sont les suivants :

  • Lorsqu’une adresse IP échoue de manière répétée (2 ou 3 fois) dans ses tentatives d’authentification, cette adresse IP doit se voir refuser l’accès au serveur pendant longtemps ; en effet, je ne veux pas qu’un attaquant n’ait qu’à attendre quelques minutes ou heures avant de pouvoir recommencer.
  • Le logiciel gère donc nécessairement des compteurs. Or, il ne peut accumuler indéfiniment des compteurs pour toutes les IP qui tentent de se connecter. Il y a donc un moment où un compteur se voit effacé. Je souhaite qu’un compteur ne soit pas effacé avant plusieurs jours, sinon un attaquant n’a qu’à limiter ses attaques à un rythme lent pour passer entre les mailles du filet.
  • Cependant, si une connexion réussit, le compteur de l’adresse IP correspondante doit être effacé. En effet, il n’est pas rare que je me trompe en tapant un mot de passe (clavier d’ordiphone, mot de passe complexe…), et je ne souhaite pas qu’une alternance de réussites et d’échecs au fil des jours aboutisse au blocage de mon adresse IP.
  • En cas de redémarrage du serveur, les restrictions d’accès doivent être restaurées, même après une coupure de courant (arrêt brutal).
  • Optionnellement, j’aimerais récupérer les restrictions actives actuellement avec DenyHosts, en tant que point de départ dans mon utilisation de Fail2ban.

L’action par défaut de Fail2ban en ce qui concerne SSH est de restreindre l’accès pour une courte durée après plusieurs tentatives observées pendant une courte durée ; ces deux durées sont modifiables. Par ailleurs, aucun moyen n’est proposé pour prendre en compte les authentifications réussies. Je dois donc personnaliser Fail2ban.

Les durées

Je commence par le plus facile : augmenter les deux durées sus-mentionnées. Pour cet article, je vais supposer vouloir une recherche des authentifications en échec sur les dernières 48 heures et une durée de bannissement des IP fautives de 5 jours, soit respectivement 172800 et 432000 secondes. Je crée donc une première version de mon fichier de configuration /etc/fail2ban/jail.local, qui prévaut sur les valeurs par défaut présentes dans /etc/fail2ban/jail.conf :

[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.1.0/24
findtime = 172800
bantime = 432000

[ssh]
enabled = true
port = 22,443
maxretry = 3

Je fais un essai avec cette configuration et ça fonctionne bien. Mais je subis suffisamment d’attaques sur mon serveur pour pouvoir constater d’emblée que Fail2ban parcourt les fichiers de log dès son démarrage, sans toutefois accéder aux fichiers archivés par logrotate. Ce premier essai montre donc qu’en cas de redémarrage, je perdrai toutes les restrictions vieilles de plus de 24 heures.

Persistance des restrictions en cas de redémarrage

Pour pouvoir bénéficier de la persistance des restrictions d’accès, il faut nécessairement une base de données persistante, que ne possède pas Fail2ban avant la version 0.9. D’une part, Debian n’intègre pas actuellement de version suffisamment récente, d’autre part, le mécanisme de prise en compte des authentifications réussies que je décris plus loin me parait difficilement compatible avec l’usage de cette base de données. Je vais donc maintenir ma propre base de données, sous forme d’un simple fichier texte. Fail2ban est en effet très souple et me permet de définir mes propres actions.

Le principe est simple : à chaque bannissement d’une IP, le fichier est complété ; quand une IP est à nouveau autorisée, elle est retirée du fichier. Au démarrage de Fail2ban, je déclenche moi-même les bannissements lus dans le fichier. Comme Fail2ban n’a aucune connaissance de cette étape, je veille également à planifier les échéances auxquelles les différentes IP seront à nouveau autorisées à accéder au serveur. Enfin, puisque ce mécanisme spécifique est partiellement en recouvrement avec le mécanisme standard d’initialisation de Fail2ban, des vérifications se chargent d’éviter que les commandes échouent à cause d’un double-bannissement ou d’une double-libération.

Ça, c’était la théorie. En pratique, je dois prendre en compte un comportement de Fail2ban qui devient gênant dans mon contexte : en cas d’arrêt propre (maintenance du serveur par exemple), Fail2ban annule chaque bannissement avant de s’arrêter, l’idée étant que tout sera restauré au redémarrage ; or nous avons vu que ce n’est pas le cas pour les bannissements de longue durée ; il faut donc empêcher cette purge. Mon besoin est qu’un bannissement reste dans le fichier tant qu’il est valable. Puisque rien ne permet de différencier une fin de bannissement pour cause d’arrêt d’une fin de bannissement pour cause d’atteinte du délai, je dois supprimer les lignes du fichier quand le délai est atteint uniquement et non chaque fois que Fail2ban le demande.

Je crée donc un premier brouillon d’une action personnalisée, dans un fichier que je nomme /etc/fail2ban/action.d/blacklist-iptables.local, tenant compte de toutes ces contraintes. Pour faire court, je n’ai pas recopié ci-dessous les commentaires standard, présents dans tous les fichiers d’action ; il est recommandé de prendre modèle sur un tel fichier et d’en respecter la mise en forme. J’ai par contre ajouté des commentaires, commençant par le caractère « # » et en italique, qui visent à expliquer un peu les scripts ; ces commentaires-là ne sont pas à conserver (du moins, je ne sais pas dans quelle mesure les conserver respecte la syntaxe de ces fichiers, pour lesquels je n’ai trouvé aucune documentation !) :

[Definition]

              # Configuration standard du pare-feu.
actionstart = iptables -N f2b-blwl-<name>
              iptables -A f2b-blwl-<name> -j RETURN
              iptables -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-blwl-<name>
              [ -e "/var/lib/fail2ban/iptables-<name>.bl" ] || touch /var/lib/fail2ban/iptables-<name>.bl
# Suppression des lignes périmées. awk -vnow=`date +%%s` -vunban=<unban> '($1+unban+60)>now{print}' \ /var/lib/fail2ban/iptables-<name>.bl > /var/lib/fail2ban/iptables-<name>.bl.new mv -f /var/lib/fail2ban/iptables-<name>.bl.new /var/lib/fail2ban/iptables-<name>.bl # Bannissement des IP issues du fichier et planification de leur libération. while read TS IP; do # — bannissement : iptables -I f2b-blwl-<name> -s $IP -j DROP # — libération planifiée : UNBAN=`expr $TS + <unban>` echo "if iptables -n -L f2b-blwl-<name> | grep -q 'DROP.* $IP '; then iptables -D f2b-blwl-<name> -s $IP -j DROP fi" | at -t `date +%%Y%%m%%d%%H%%M.%%S -d "1970-01-01 00:00:00 UTC +$UNBAN second"` done < /var/lib/fail2ban/iptables-<name>.bl # Suppression des tâches planifiées. actionstop = for atid in `at -l | cut -f1`; do at -c $atid | grep -qF ' f2b-blwl-<name> ' && at -r $atid done # Réinitialisation du pare-feu. iptables -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-blwl-<name> iptables -F f2b-blwl-<name> iptables -X f2b-blwl-<name> actioncheck = iptables -n -L <chain> | grep -q f2b-blwl-<name> # Bannissement d’une IP dans le pare-feu et le fichier, si elle n’est pas déjà dans le fichier. actionban = if ! grep -q ' <ip>$' /var/lib/fail2ban/iptables-<name>.bl; then TS=`echo <time> | sed 's/\..*//'` printf '%%d %%s\n' "$TS" '<ip>' >> /var/lib/fail2ban/iptables-<name>.bl iptables -I f2b-blwl-<name> -s <ip> -j DROP fi # Si l’IP est bannie, je libère cette IP, dans le pare-feu uniquement. actionunban = if iptables -n -L f2b-blwl-<name> | grep -q 'DROP.* <ip> '; then iptables -D f2b-blwl-<name> -s <ip> -j DROP fi # Je supprime les lignes périmées du fichier. awk -vnow=`date +%%s` -vunban=<unban> '($1+unban)>now{print}' \ /var/lib/fail2ban/iptables-<name>.bl > /var/lib/fail2ban/iptables-<name>.bl.new mv -f /var/lib/fail2ban/iptables-<name>.bl.new /var/lib/fail2ban/iptables-<name>.bl [Init] name = default port = ssh protocol = tcp chain = INPUT unban = 300

Le script actionstart configure le pare-feu iptables comme le fait l’action par défaut de Fail2ban ; puis le fichier /var/lib/fail2ban/iptables-….bl est épuré des lignes qui ont périmé entre l’arrêt du serveur et son redémarrage (plus un délai de sécurité de 60 secondes pour éviter que des IP soient libérées, et donc le fichier modifié, alors même que ce dernier est encore en cours de lecture) ; des tâches sont alors planifiées, via le service atd, pour libérer les IP encore présentes dans le fichier, au terme de leurs bannissements respectifs. Notez que j’ai choisi de ne pas modifier le fichier de base de données dans les tâches atd alors que j’aurais pu (ces tâches ne risquent pas de se déclencher prématurément) et peut-être même dû ; je fais ce choix pour limiter le risque de modification simultanée du fichier par deux tâches différentes ; je subis suffisamment d’attaques pour avoir la certitude que le fichier sera rectifié dans l’heure :-p

Le script actionstop supprime les tâches de libération des IP issues du fichier, avant d’effectuer les actions standard de Fail2ban consistant à remettre le pare-feu dans son état initial.

Le script actioncheck vérifie que le pare-feu est bien configuré avant d’autoriser l’exécution du script actionban. Il est dommage que la variable Fail2ban <ip> ne soit pas accessible dans ce script : elle aurait permis d’une part d’éviter des tests if, d’autre part de fiabiliser les logs de Fail2ban (je reviendrai sur ce point).

Le script actionban bannit une IP donnée via le paramètre Fail2ban <ip>. Le script actionunban libère une IP préalablement bannie, y compris dans le fichier mais seulement si l’échéance est effectivement arrivée (ce qui ne sera pas forcément le cas en cas d’arrêt de Fail2ban).

Notification par e-mail

Comme la plupart des actions sont conditionnées par des tests if, et que Fail2ban ne voit pas les actions réalisées au démarrage (bannissement direct depuis le fichier puis libération via des actions atd), les logs de Fail2ban, forcément, ne reflètent plus complètement la réalité. Pour pallier à cela, j’ajoute une action de notification par e-mail, que je réalise de telle sorte que les mails générés ressemblent à ceux de DenyHosts ; ainsi, je continue de bénéficier des règles de classification automatique que j’avais mises en place dans Thunderbird, et je peux analyser ces mails par script de la même manière que je le faisais auparavant.

Malheureusement, si je déclare deux fichiers d’action dans le fichier principal /etc/fail2ban/jail.local, le deuxième fichier se déclenche en cas de bannissement même si l’action du premier fichier retourne un statut de sortie en erreur (j’ai essayé return 1 et exit 1). Par conséquent, je suis obligé d’envoyer les mails depuis le même fichier d’action : /etc/fail2ban/action.d/blacklist-iptables.local ; voici le nouveau contenu de ce fichier (les modifications sont en gras) :

[Definition]

actionstart = iptables -N f2b-blwl-<name>
              iptables -A f2b-blwl-<name> -j RETURN
              iptables -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-blwl-<name>
              [ -e "/var/lib/fail2ban/iptables-<name>.bl" ] || touch /var/lib/fail2ban/iptables-<name>.bl
awk -vnow=`date +%%s` -vunban=<unban> '($1+unban+60)>now{print}' \ /var/lib/fail2ban/iptables-<name>.bl > /var/lib/fail2ban/iptables-<name>.bl.new mv -f /var/lib/fail2ban/iptables-<name>.bl.new /var/lib/fail2ban/iptables-<name>.bl while read TS IP; do iptables -I f2b-blwl-<name> -s $IP -j DROP UNBAN=`expr $TS + <unban>` echo "if iptables -n -L f2b-blwl-<name> | grep -q 'DROP.* $IP '; then iptables -D f2b-blwl-<name> -s $IP -j DROP fi" | at -t `date +%%Y%%m%%d%%H%%M.%%S -d "1970-01-01 00:00:00 UTC +$UNBAN second"` done < /var/lib/fail2ban/iptables-<name>.bl actionstop = for atid in `at -l | cut -f1`; do at -c $atid | grep -qF ' f2b-blwl-<name> ' && at -r $atid done iptables -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-blwl-<name> iptables -F f2b-blwl-<name> iptables -X f2b-blwl-<name> actioncheck = iptables -n -L <chain> | grep -q f2b-blwl-<name> actionban = if ! grep -q ' <ip>$' /var/lib/fail2ban/iptables-<name>.bl; then TS=`echo <time> | sed 's/\..*//'` printf '%%d %%s\n' "$TS" '<ip>' >> /var/lib/fail2ban/iptables-<name>.bl iptables -I f2b-blwl-<name> -s <ip> -j DROP printf %%b "Subject: DenyHosts Report Date: `env LANG=C date +"%%a, %%d %%h %%Y %%T %%z"` From: DenyHosts <<sender>> To: <dest>\n Added the following hosts to /var/lib/fail2ban/iptables-<name>.bl:\n <ip> (unknown)\n ----------------------------------------------------------------------" | /usr/sbin/sendmail -f <sender> <dest> fi actionunban = if iptables -n -L f2b-blwl-<name> | grep -q 'DROP.* <ip> '; then iptables -D f2b-blwl-<name> -s <ip> -j DROP fi awk -vnow=`date +%%s` -vunban=<unban> '($1+unban)>now{print}' \ /var/lib/fail2ban/iptables-<name>.bl > /var/lib/fail2ban/iptables-<name>.bl.new mv -f /var/lib/fail2ban/iptables-<name>.bl.new /var/lib/fail2ban/iptables-<name>.bl [Init] name = default port = ssh protocol = tcp chain = INPUT unban = 300 dest = root@localhost sender = nobody@localhost

Pour finir, je modifie le fichier de configuration principal /etc/fail2ban/jail.local, qui devient :

[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.1.0/24
findtime = 172800
bantime = 432000
action = blacklist-iptables[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s", unban="%(bantime)s"]

[ssh]
enabled = true
port = 22,443
maxretry = 3

Je choisis de mettre l’action sous [DEFAULT] car cette même action servira le jour où je déciderai d’activer Fail2ban également pour la protection du serveur web. Dans la syntaxe de Fail2ban, cette action appelle l’action personnalisée décrite ci-dessus, en fournissant les paramètres entre crochets. Notez que de nombreux paramètres sont définis à partir de variables existantes, déclarées au-dessus ou déclarées par défaut par Debian dans le fichier /etc/fail2ban/jail.conf.

Avec cette configuration, c’est déjà beaucoup mieux : rien n’est perdu lors d’un redémarrage, même brutal, puisque l’état des bannissements est sauvegardé au fur et à mesure, tel un journal, et restitué ensuite. De plus, puisque l’état initial dépend d’un fichier, la transition depuis DenyHosts est très simple ; la commande suivante, lancée avant de faire fonctionner Fail2ban pour la première fois, suffit à initialiser Fail2ban :

grep '^# DenyHosts' /etc/hosts.deny | sed 's/[a-z]:/|/g' | while IFS='|' read x d y i; do printf '%d %s\n' $(date +%s -d "$d") $i; done >/var/lib/fail2ban/iptables-ssh.bl

Cette commande extrait les lignes insérées par DenyHosts dans le fichier /etc/hosts (par exemple : « # DenyHosts: Sun Oct 19 08:19:25 2014 | sshd: 222.186.56.49 ») et remplace les « : » précédés d’une lettre par des « | » afin d’obtenir des lignes de 4 champs, le 1er et le 3ème inintéressants, le 2ème et le 4ème représentant respectivement la date de bannissement d’une adresse IP et l’adresse IP en question ; ce texte formaté est alors lu ligne par ligne et généré au format de mon nouveau fichier « base de données » : le timestamp Unix correspondant à la date, et l’adresse IP, séparés par une espace.

De mes besoins initialement cités, le seul restant à satisfaire est celui de pardonner à une adresse IP ses erreurs passées si elle réussit une authentification sans faute ; autrement dit réinitialiser son compteur.

Le pardon

Là, c’est nettement plus compliqué : Fail2ban n’a aucun mécanisme permettant de réinitialiser le compteur. Je suis donc le conseil donné : gérer une liste d’adresses IP autorisées, de la même manière que j’ai géré une liste d’adresses interdites. Mais combien de temps faut-il accorder ce sauf-conduit ? Après que j’aie effectué une authentification réussie, le compteur de mon adresse IP n’aura pas réellement été réinitialisé puisque Fail2ban ne sait pas faire cela… Puisque la durée de vie du compteur est celle donnée par la variable findtime, c’est là le temps de grâce que je choisis.

Mais imaginons ce scénario : je réussis mon authentification (mon IP devient autorisée pour un délai de findtime secondes) ; pendant le délai, j’échoue parfois à m’authentifier, mais ce n’est pas grave car je suis protégé (le compteur augmente quand même) ; juste avant la fin du délai, je réussis à nouveau à m’authentifier, mais Fail2ban ne réagit pas car mon IP est déjà protégée ; quelques secondes plus tard, juste après l’échéance, j’échoue à m’authentifier et Fail2ban bannit mon IP car j’ai échoué trop souvent et mon adresse IP n’est plus protégée. Conclusion : si la protection effective à mettre en place doit effectivement durer findtime secondes, Fail2ban doit croire que cette protection ne dure que quelques secondes, ce qui permet de « raffraichir » la protection à chaque nouvelle authentification réussie.

Les accès concurrents

Petit interlude dédié aux concurrences d’accès… Lors de mes tests avec la solution qui va suivre, j’ai constaté plusieurs erreurs d’exécution de iptables, dues à des exécutions simultanées ; à l’origine de cela, l’importante quantité de tentatives d’accès à mon serveur, dont certaines très rapprochées [1].

Pour éviter cette erreur, et du même coup éviter tout risque d’erreur lors d’accès concurrents à ma petite base de données, je verrouille iptables à chaque utilisation. Pour que la solution soit effective, le même verrou doit être utilisé à chaque utilisation d’une commande iptables, ce qui dans Debian Wheezy m’a amené à également modifier le script de lancement de iptables, dont je joins une copie avec le patch correspondant, pour référence.

À noter : ce verrouillage autours des utilisation de iptables n’est pas spécifique à ma solution puisqu’il répond à une limitation du noyau Linux. La même erreur peut arriver avec le module iptables standard de Fail2ban et la même solution permet d’y remédier.

Les versions plus récentes de iptables reconnaissent et corrigent ce problème, avec l’introduction du « xtables lock ». Debian Jessie possède une version récente, dont le paramètre -w permet d’attendre que le verrou xtables soit disponible. Les utilisateurs d’une telle version de iptables devraient systématiquement utiliser ce paramètre -w, d’abord parce que cela constitue un filet de sécurité au cas où d’autres outils déclenchent des commandes iptables, ensuite parce qu’il devient ainsi inutile d’altérer des fichiers du système, tel le script d’initialisation de iptables. Néanmoins, je conserve le verrou décrit quelques paragraphes plus haut car il protège également le fichier-base de données.

La solution finale

L’interlude étant terminé, voici maintenant la solution finale. Les fichiers correspondants sont joints à l’article.

La première étape consiste à créer un filtre qui reconnait les authentifications réussies ; une rapide lecture du fichier /var/log/auth.log me permet de les reconnaitre. Voici le contenu du nouveau filtre /etc/fail2ban/filter.d/whitelist-ssh.local, là encore sans les commentaires pour faire plus court :

[INCLUDES]

before = common.conf

[Definition]

_daemon = sshd

failregex = ^%(__prefix_line)sAccepted \S+ for \S+ from <HOST>\s.*$

ignoreregex =

La seconde étape consiste à écrire une nouvelle action, dans un fichier nommé /etc/fail2ban/action.d/whitelist-iptables.local dont le contenu est le suivant (une fois de plus sans les commentaires) :

[Definition]

actionstart =

actionstop =

actioncheck =

actionban = if lockfile-create --lock-name /tmp/f2b-blwl.lock; then
              if grep -q ' <ip>$' /var/lib/fail2ban/iptables-<name>.wl; then
                sed -i.old '/ <ip>$/d' /var/lib/fail2ban/iptables-<name>.wl
              elif iptables -w -n -L f2b-blwl-<name> | grep -q 'DROP.* <ip> '; then
                iptables -w -D f2b-blwl-<name> -s <ip> -j DROP
                sed -i.old '/ <ip>$/d' /var/lib/fail2ban/iptables-<name>.bl
              fi
              TS=`echo <time> | sed 's/\..*//'`
              printf '%%d %%s\n' "$TS" '<ip>' >> /var/lib/fail2ban/iptables-<name>.wl
              lockfile-remove --lock-name /tmp/f2b-blwl.lock
            fi

actionunban =

[Init]
name = default

Dans cette action, j’agis quand Fail2ban me signale une nouvelle IP filtrée (actionban correspond ici à une authentification réussie), mais je ne fais rien quand Fail2ban me signale l’expiration du délai. En effet, je vais donner à Fail2ban un délai fictif, très court. La question à se poser maintenant est : quand faut-il que la base de données des IP autorisées soit à jour ? Et la réponse est : quand on détermine si une IP à bannir va effectivement l’être ou non, c’est à dire dans le script actionban de l’action /etc/fail2ban/action.d/blacklist-iptables.local ; je modifie donc cette action pour prendre en charge le vrai délai de protection, et pour effectuer les vérifications. Les modifications sont en gras :

[Definition]

actionstart = if lockfile-create --lock-name /tmp/f2b-blwl.lock; then
                iptables -w -N f2b-blwl-<name>
                iptables -w -A f2b-blwl-<name> -j RETURN
                iptables -w -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-blwl-<name>
                [ -e "/var/lib/fail2ban/iptables-<name>.bl" ] || touch /var/lib/fail2ban/iptables-<name>.bl
[ -e "/var/lib/fail2ban/iptables-<name>.wl" ] || touch /var/lib/fail2ban/iptables-<name>.wl
awk -vnow=`date +%%s` -vunban=<unban> '($1+unban+60)>now{print}' \ /var/lib/fail2ban/iptables-<name>.bl > /var/lib/fail2ban/iptables-<name>.bl.new mv -f /var/lib/fail2ban/iptables-<name>.bl.new /var/lib/fail2ban/iptables-<name>.bl while read TS IP; do iptables -w -I f2b-blwl-<name> -s $IP -j DROP UNBAN=`expr $TS + <unban>` echo "if lockfile-create --lock-name /tmp/f2b-blwl.lock; then
if iptables -w -n -L f2b-blwl-<name> | grep -q 'DROP.* $IP '; then iptables -w -D f2b-blwl-<name> -s $IP -j DROP fi
lockfile-remove --lock-name /tmp/f2b-blwl.lock
fi" | at -t `date +%%Y%%m%%d%%H%%M.%%S -d "1970-01-01 00:00:00 UTC +$UNBAN second"` done < /var/lib/fail2ban/iptables-<name>.bl lockfile-remove --lock-name /tmp/f2b-blwl.lock fi actionstop = for atid in `at -l | cut -f1`; do at -c $atid | grep -qF ' f2b-blwl-<name> ' && at -r $atid done if lockfile-create --lock-name /tmp/f2b-blwl.lock; then iptables -w -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-blwl-<name> iptables -w -F f2b-blwl-<name> iptables -w -X f2b-blwl-<name> lockfile-remove --lock-name /tmp/f2b-blwl.lock fi actioncheck = if lockfile-create --lock-name /tmp/f2b-blwl.lock; then iptables -w -n -L <chain> | grep -q f2b-blwl-<name> lockfile-remove --lock-name /tmp/f2b-blwl.lock fi actionban = if lockfile-create --lock-name /tmp/f2b-blwl.lock; then awk -vnow=`date +%%s` -vunban=<whitelist> '($1+unban)>now{print}' \ /var/lib/fail2ban/iptables-<name>.wl > /var/lib/fail2ban/iptables-<name>.wl.new mv -f /var/lib/fail2ban/iptables-<name>.wl.new /var/lib/fail2ban/iptables-<name>.wl if ! grep -q ' <ip>$' /var/lib/fail2ban/iptables-<name>.wl /var/lib/fail2ban/iptables-<name>.bl; then TS=`echo <time> | sed 's/\..*//'` printf '%%d %%s\n' "$TS" '<ip>' >> /var/lib/fail2ban/iptables-<name>.bl iptables -w -I f2b-blwl-<name> -s <ip> -j DROP printf %%b "Subject: DenyHosts Report Date: `env LANG=C date +"%%a, %%d %%h %%Y %%T %%z"` From: DenyHosts <<sender>> To: <dest>\n Added the following hosts to /var/lib/fail2ban/iptables-<name>.bl:\n <ip> (unknown)\n ----------------------------------------------------------------------" | /usr/sbin/sendmail -f <sender> <dest> fi lockfile-remove --lock-name /tmp/f2b-blwl.lock fi actionunban = if lockfile-create --lock-name /tmp/f2b-blwl.lock; then if iptables -w -n -L f2b-blwl-<name> | grep -q 'DROP.* <ip> '; then iptables -w -D f2b-blwl-<name> -s <ip> -j DROP fi awk -vnow=`date +%%s` -vunban=<unban> '($1+unban)>now{print}' \ /var/lib/fail2ban/iptables-<name>.bl > /var/lib/fail2ban/iptables-<name>.bl.new mv -f /var/lib/fail2ban/iptables-<name>.bl.new /var/lib/fail2ban/iptables-<name>.bl lockfile-remove --lock-name /tmp/f2b-blwl.lock fi [Init] name = default port = ssh protocol = tcp chain = INPUT unban = 300 dest = root@localhost sender = nobody@localhost whitelist = 300

La touche finale consiste à compléter le fichier de configuration principal /etc/fail2ban/jail.local pour prendre en compte les derniers changements :

[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.1.0/24
findtime = 172800
bantime = 432000
action = blacklist-iptables[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s", unban="%(bantime)s", whitelist="%(findtime)s"]

[ssh]
enabled = true
port = 22,443
maxretry = 3

[ssh-ok]
enabled = true
maxretry = 1
bantime = 1
logpath = /var/log/auth.log
filter = whitelist-ssh action = whitelist-iptables[name="ssh"]

Dans la nouvelle « jail » ci-dessus, nommée « [ssh-ok] », le name donné est celui de la « jail » dont l’action blacklist-iptables est faite pour travailler de pair avec la whitelist-iptables de cette « jail »-ci. De la même manière, si je définissais une « jail » nommée « [html] », je pourrais définir également une « jail » nommée « [html-ok] » dans laquelle le paramètre name vaudrait « html ».

Voilà, cette configuration sort sans doute des sentiers battus, mais c’est celle qui me convient.


Notes :

[1] En réalité, je n’ai maintenant plus aucun accès frauduleux à gérer avec Fail2ban. Mais une fois que le problème est identifié, autant le corriger malgré tout ;-)

Mises à jour :

  • 2015-01-13 — Verrouillage de iptables pour éviter les erreurs d’accès concurrents au pare-feu, ce qui règle du même coup la question des accès concurrents à la « base de données ».
  • 2015-03-30 — Un « ; then » manquait dans l’action actioncheck du fichier /etc/fail2ban/action.d/blacklist-iptables.local; également dans le fichier téléchargeable. Mes excuses à ceux qui ont peut-être été bloqués à cause de cela.
  • 2015-06-03 — Debian Jessie amène une nouvelle version de iptables, intégrant un mécanisme de verrouillage. J'ai également corrigé quelques petites erreurs.