Pourquoi utiliser keycloak avec fail2ban
Keycloak permet « relativement » simplement de mettre en place une solution SSO (Single Sign On) au sein de son infrastructure tout en permettant des réglages très fins, entre autre, au niveau de la routine de connexion afin de considérablement renforcer la sécurité des accès aux différents services que vous hébergez.
La solution propose une mécanique intégrée de mitigation d’attaque très bien documentée qui montre vite ses limites : lorsque les conditions paramétrées sont atteintes, le serveur bloque… l’utilisateur !
C’est donc une jolie perche tendue aux attaquants pour bloquer très simplement les accès des utilisateurs aux applications. Pour peu que Keycloak soit la seule porte d’entrée (c’est l’intêret de la solution…) n’importe qui peu vous enfermer dehors à condition de connaitre votre identifiant ou votre email
De plus, dans le cadre d’une authentification sans mot de passe, à l’aide d’une clef de sécurité physique par exemple, cette mécanique n’empêche pas un attaquant d’énumérer les utilisateurs du serveur.
La documentation fait état de cette limitation et se contente d’un laconique :
Consider using intrusion prevention software (IPS). Keycloak logs every login failure and client IP address failure. You can point the IPS to the Keycloak server’s log file, and the IPS can modify firewalls to block connections from these IP addresses.
En clair, pour bloquer l’attaquant et non l’utilisateur attaqué, il nous faut un outil capable de lire des logs et de bloquer des adresses IP : fail2ban !
Lors de nos recherches, nous n’avons pas trouvé la recette s’appliquant à la version 19 de keycloak que nous utilisons, c’est pourquoi nous avons décidé de partager notre recette.
Activer les fichiers de log
Lorsque Keycloak est déployé avec un container Docker, les logs sont envoyés à la console du container : pas pratique pour la définition de notre filtre fail2ban.
Un petit tour dans la documentation nous donne les variables d’environnement à renseigner : log et log-file.
Ce qui donne cet ajout dans notre fichier docker-compose :
environment:
TZ: Europe/Paris #pour avoir le bon horodatage dans les logs
KC_LOG: file #avoir un fichier de log
KC_LOG_FILE: /opt/keycloak/data/log/keycloak.log #l'emplacement du fichier (dans le container)
volumes:
- ./log:/opt/keycloak/data/log #on monte un dossier de l'hote pour les logs
Avant de relancer notre serveur nous allons créer le dossier log sur l’hôte et modifier les droits pour éviter des erreurs de permission :
mkdir /srv/keycloak/log && chmod 666 /srv/keycloak/log
On peut maintenant lancer la création du container :
docker-compose up -d
Et on vérifie qu’on a bien un fichier de log :
cat /srv/keycloak/log/keycloak.log
Paramètres fail2ban
S’il n’est pas déjà sur votre machine, on installe fail2ban
apt install fail2ban -y
Par défaut fail2ban ajoute ses règles de blocage dans la chaine INPUT du par-feu de votre machine. Docker et les containers qu’il fait tourner utilisent la chaine FORWARD du par-feu, nous allons donc modifier le comportement par défaut de fail2ban :
nano /etc/fail2ban/action.d/iptables-common.local
Voici le contenu du fichier iptables-common.local :
[Init]
# Option: chain
# Notes specifies the iptables chain to which the Fail2Ban rules should be
# added, Docker uses FORWARD chain, leaving INPUT results in 'ignored' rules
# Values: STRING Default: INPUT
chain = FORWARD
# Option: protocol
# Notes.: internally used by config reader for interpolations.
# Values: [ tcp | udp | icmp | all ] Default: tcp
#
protocol = all
# Option: blocktype
# Note: This is what the action does with rules. This can be any jump target
# as per the iptables man page (section 8). Common values are DROP
# REJECT, REJECT --reject-with icmp-port-unreachable
# Values: STRING
#blocktype = REJECT --reject-with icmp-port-unreachable
blocktype = DROP
[Init?family=inet6]
# Option: blocktype (ipv6)
# Note: This is what the action does with rules. This can be any jump target
# as per the iptables man page (section 8). Common values are DROP
# REJECT, REJECT --reject-with icmp6-port-unreachable
# Values: STRING
#blocktype = REJECT --reject-with icmp6-port-unreachable
blocktype = DROP
Nous allons ensuite créer le fichier de filtre fail2ban spécifique à keycloak :
nano /etc/fail2ban/filter.d/keycloak.conf
Avec ces paramètres :
[INCLUDES]
before = common.conf
[Definition]
failregex =
\s*WARN\s+\[org\.keycloak\.events\]\s+\(executor-thread-\d+\)\s+type=LOGIN_ERROR,\s+realmId=(?:[a-zA-Z0-9-]+|null),\s+clientId=(?:[a-zA-Z0-9-]+|null),\s+userId=(?:[a-zA-Z0-9-]+|null),\s+ipAddress=
ignoreregex =
Et enfin le fichier de définition de la prison fail2ban :
nano /etc/fail2ban/jail.d/keycloak.conf
Avec ces paramètres :
[keycloak]
enabled = true
port = https,http
logpath = /srv/keycloak/log/keycloak.log
maxretry = 3
findtime = 3600 # 1 heure
bantime = 36000 # 10 heures
ignoreip = 127.0.0.1/8 # ajoutez votre ip le temps de tester...
On peut enfin activer fail2ban au démarrage puis le démarrer :
systemctl enable fail2ban.service && systemctl start fail2ban.service
Pour vérifier que ça fonctionne :
tail -f /var/log/fail2ban.log
Puis rendez-vous à l’URL de votre instance keycloak avec votre navigateur et trompez vous volontairement d’identifiant/mot de passe, vous devrez alors voir du mouvement dans votre console.