Là où fail2ban cesse de suffire

fail2ban est un bon outil et la bonne première réponse. Voici précisément où il s'arrête, comment le pousser plus loin, et ce qu'il ne peut faire dans aucune configuration.

8 min de lecture

Commençons par lui rendre justice

fail2ban est dans les dépôts de toutes les distributions, s'installe en une commande et bloque de vrais attaquants le soir même. Si vous avez un serveur exposé que rien ne surveille, installer fail2ban est une position strictement meilleure que la vôtre, et rien de ce qui suit ne dit le contraire.

Ce guide porte sur l'écart entre « bloque de vrais attaquants ce soir » et « est encore correct dans un an sur tout un parc », car c'est dans cet écart que vivent les surprises, et il est rarement écrit quelque part.

Aucune des limites ci-dessous n'est un bug. Ce sont des conséquences de ce qu'est fail2ban : un démon d'analyse de journaux conçu à une époque de serveurs uniques et d'attaquants uniques.

Limite 1 : il bannit des adresses, pas des plages

C'est la grosse. Le trafic d'attaque vient d'hébergeurs où un opérateur détient un bloc entier. Bannissez 203.0.113.47 et le botnet passe à .48 dans l'heure, et vous absorbez un nouveau tour complet de tentatives avant que celle-là soit bannie aussi. Sur un /24, cela fait 256 tours du seuil que vous avez configuré.

Vous pouvez approcher une solution avec une action personnalisée qui élargit chaque bannissement à son /24, mais vous maintenez alors de l'arithmétique CIDR en shell, et vous tromper signifie bannir ce dont vous aviez besoin. L'outil n'a aucune notion de plage comme objet de premier ordre.

L'effet mesurable : sur un serveur sous attaque continue, une défense qui bannit des plages voit le nombre de tentatives s'effondrer, tandis qu'une défense par adresse plafonne au rythme de rotation.

Limite 2 : les bannissements expirent, et l'état ne survit pas

Le bantime par défaut se compte en minutes. C'est raisonnable sur un hébergement mutualisé où un vrai utilisateur peut se faire prendre, et c'est faux pour un serveur exposé à internet : le même botnet revient ce soir et recommence.

Vous pouvez mettre bantime à -1 pour des bannissements permanents, et vous devriez — mais le deuxième problème apparaît alors. La liste vit dans la base de fail2ban et dans le pare-feu actif ; reconstruisez la machine, restaurez une image ou migrez vers un nouvel hôte et tout ce que le serveur avait appris disparaît.

Il y a aussi une version de troisième ordre : un serveur qui tourne depuis deux ans accumule un ensemble de pare-feu avec des dizaines de milliers d'entrées, et personne n'a jamais vérifié si les plus anciennes valent encore la comparaison.

Limite 3 : chaque serveur apprend seul

Celle-ci est structurelle et aucune configuration ne la corrige. Chacun de vos serveurs découvre chaque attaquant indépendamment, et le coût de cette découverte est le seuil que vous avez fixé — cinq tentatives, dix, peu importe.

Dix serveurs, dix fois le même attaquant, dix fois le seuil absorbé. Et chacune de ces tentatives est un vrai fork de sshd, une vraie ligne de journal et une vraie chance que ce soit le passage où un mot de passe réutilisé finit par marcher.

fail2ban n'a aucun mécanisme pour que la connaissance d'un serveur atteigne un autre. Il existe des listes communautaires à souscrire, qui aident, mais elles sont génériques et accusent des heures voire des jours de retard ; rien ne vous dit que l'adresse qui frappe le serveur trois en ce moment est celle qui frappait le serveur un ce matin.

Limite 4 : il échoue silencieusement

C'est la limite qui coûte réellement des serveurs, et c'est celle dont on parle le moins.

fail2ban repose sur la correspondance de lignes de journal avec des expressions régulières. Une montée de version change un format de message sshd ; le filtre ne correspond plus ; la jail « tourne » toujours et rapporte zéro bannissement, ce qui ressemble exactement à une semaine calme. Des mises à jour de paquets ont déjà réinitialisé jail.local. Un changement de backend systemd laisse le journal non lu. Le service ne revient pas après un redémarrage.

Dans chacun de ces cas, la supervision que vous avez probablement — le service tourne-t-il ? — répond oui. La question qui l'attraperait est « a-t-il banni quelqu'un récemment, et ce nombre ressemble-t-il au volume d'attaques que je vois dans le journal ? », et presque personne ne la pose.

Pousser fail2ban plus loin

Si vous voulez rester dessus — choix légitime pour un petit parc — voici les changements qui comptent le plus, à peu près dans l'ordre :

  • Mettre bantime = -1 et augmenter findtime, pour attraper aussi un attaquant lent. Ajouter la jail recidive par-dessus, pour escalader les récidivistes.
  • Ajouter une action personnalisée qui élargit un bannissement à son /24, en testant d'abord soigneusement l'arithmétique contre vos propres plages.
  • Faire survivre les bannissements aux redémarrages via un ensemble nftables marqué persistant, ou les restaurer depuis la base au démarrage.
  • Superviser le nombre de bannissements, pas l'état du service. Alerter quand il est nul alors que le journal d'authentification ne l'est pas.
  • Mettre vos adresses d'administration dans ignoreip avant toute chose, et vérifier depuis une seconde connexion que c'est bien fait.

Ce qu'apporte un agent managé

Deux choses, et la seconde n'est constructible seul dans aucune configuration.

La première est le socle opérationnel : le bannissement de sous-réseaux comme notion de premier ordre, des bannissements permanents et restaurés au démarrage, le vrai port SSH lu depuis la configuration de sshd plutôt que supposé, votre adresse en liste blanche dès l'installation, et une console qui répond « est-ce que ça marche encore » — le mode de défaillance qui mord réellement.

La seconde est la réputation partagée. Chaque serveur protégé alimente une base commune, si bien qu'une adresse ayant attaqué quelqu'un d'autre hier est déjà bloquée quand elle vous atteint, et vous n'absorbez jamais ses N premières tentatives. Un analyseur de journaux sur un hôte ne peut apprendre que des attaques visant cet hôte.

SSH Protector décide localement sur l'agent, la protection ne dépend donc pas de la joignabilité du cloud : si la connexion tombe, la politique locale continue. Un serveur est gratuit à vie, ce qui suffit à le faire tourner une semaine à côté de fail2ban et comparer ce que chacun a attrapé.

FAQ

fail2ban vaut-il encore la peine ?
Oui, comme socle. Il est gratuit, dans tous les dépôts, et sur un serveur que rien d'autre ne surveille il bloquera de vrais attaquants dès le jour de l'installation. Les réserves honnêtes : bannissement d'adresses uniques contre des botnets qui tournent dans des plages, bannissements qui expirent par défaut, aucune connaissance partagée entre hôtes, et un mode de défaillance silencieux.
Comment rendre les bannissements fail2ban permanents ?
Mettre bantime = -1 dans la jail concernée. Sachez que la liste vit alors uniquement dans la base de fail2ban et le pare-feu actif : une reconstruction ou une restauration d'image perd tout ce que le serveur avait appris, et l'ensemble grossit sans que personne le révise. Faites aussi survivre l'ensemble du pare-feu aux redémarrages, sinon les bannissements disparaissent à chaque redémarrage.
fail2ban peut-il bannir un sous-réseau entier ?
Pas nativement. Il bannit l'adresse apparue dans la ligne de journal. Vous pouvez écrire une action personnalisée qui élargit chaque bannissement au /24 contenant, mais vous maintenez alors l'arithmétique CIDR vous-même, et une erreur y bloque une plage dont vous aviez besoin. La notion de plage n'existe pas comme telle dans l'outil.
Comment vérifier que fail2ban fonctionne vraiment ?
Lancez fail2ban-client status sshd et regardez le nombre de bannis, pas seulement si le service est actif. Une jail active qui n'a banni personne alors que le journal montre des échecs signifie que le filtre ne correspond plus — généralement après une montée de version ayant changé un format de message sshd. Supervisez le nombre de bannissements, pas le processus.

Faites-le tourner à côté une semaine

Bannissements de sous-réseaux, permanents par défaut, réputation partagée et une vue indiquant si c'est vivant. Un serveur gratuit à vie, sans carte.