Skip to content

Instantly share code, notes, and snippets.

@drmalex07
Last active July 29, 2024 21:25
Show Gist options
  • Save drmalex07/3eba8b98d0ac4a1e821e8e721b3e1816 to your computer and use it in GitHub Desktop.
Save drmalex07/3eba8b98d0ac4a1e821e8e721b3e1816 to your computer and use it in GitHub Desktop.
Use fail2ban to block brute-force attacks to keycloak server. #keycloak #fail2ban #brute-force-attack

Add regular-expression filter under /etc/fail2ban/filter.d/keycloak.conf:

[INCLUDES]

before = common.conf

[Definition]

_threadName = [a-z][-_0-9a-z]*(\s[a-z][-_0-9a-z]*)*
_userId = (null|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
_realmName = ([a-zA-Z][-_a-zA-Z0-9]*)

failregex = 
    ^\s*WARN\s+\[org\.keycloak\.events\]\s+\(%(_threadName)s\) type=LOGIN_ERROR, realmId=%(_realmName)s, clientId=account, userId=%(_userId)s, ipAddress=<HOST>

ignoreregex = 

Assuming server logs are stored under /usr/local/keycloak/standalone/log/server.log, add jail configuration under /etc/fail2ban/jail.d/keycloak.conf:

[keycloak]
enabled = true
port = https,8443
logpath = /usr/local/keycloak/standalone/log/server.log
maxretry = 6
findtime = 600
bantime = 600

Simulate some failed logins and test your regular expressions:

sudo fail2ban-regex -v /usr/local/keycloak/standalone/log/server.log /etc/fail2ban/filter.d/keycloak.conf

Restart fail2ban for jail to be enabled:

sudo systemctl restart fail2ban.service

During normal operation of fail2ban, we can check the status of a particular jail:

sudo fail2ban-client status keycloak
@jon-nfc
Copy link

jon-nfc commented Jul 8, 2023

@drmalex07, thanks for the filter, however needed changes to work. issues due to clientId=account and _realmName = ([a-zA-Z][-_a-zA-Z0-9]*), below is what i'm running and works for any failed login

[Definition]

_threadName = [a-z][-_0-9a-z]*(\s[a-z][-_0-9a-z]*)*
_userId = (null|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
-_realmName = ([a-zA-Z][-_a-zA-Z0-9]*)
+_realmName = (\S+)
+_clientId = (\S+)
+# preference 'user_not_found' as brute force is enabled for users with to may thumbs. 
+  # 'user_not_found' user doesnt exist
+  # 'invalid_user_credentials' incorrect password
+_error = (user_not_found) 

failregex = 
-     ^\s*WARN\s+\[org\.keycloak\.events\]\s+\(%(_threadName)s\) type=LOGIN_ERROR, realmId=%(_realmName)s, clientId=account, userId=%(_userId)s, ipAddress=<HOST>
+    ^\s*WARN\s+\[org\.keycloak\.events\]\s+\(%(_threadName)s\)\s+type=LOGIN_ERROR,\srealmId=(.+),\s+clientId=%(_clientId)s,\s+userId=%(_userId)s,\s+ipAddress=<HOST>,\s+error=%(_error)s,
ignoreregex = 

@systemofapwne
Copy link

@drmalex07, thanks for the filter, however needed changes to work. issues due to clientId=account and _realmName = ([a-zA-Z][-_a-zA-Z0-9]*), below is what i'm running and works for any failed login

[Definition]

_threadName = [a-z][-_0-9a-z]*(\s[a-z][-_0-9a-z]*)*
_userId = (null|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
-_realmName = ([a-zA-Z][-_a-zA-Z0-9]*)
+_realmName = (\S+)
+_clientId = (\S+)
+# preference 'user_not_found' as brute force is enabled for users with to may thumbs. 
+  # 'user_not_found' user doesnt exist
+  # 'invalid_user_credentials' incorrect password
+_error = (user_not_found) 

failregex = 
-     ^\s*WARN\s+\[org\.keycloak\.events\]\s+\(%(_threadName)s\) type=LOGIN_ERROR, realmId=%(_realmName)s, clientId=account, userId=%(_userId)s, ipAddress=<HOST>
+    ^\s*WARN\s+\[org\.keycloak\.events\]\s+\(%(_threadName)s\)\s+type=LOGIN_ERROR,\srealmId=(.+),\s+clientId=%(_clientId)s,\s+userId=%(_userId)s,\s+ipAddress=<HOST>,\s+error=%(_error)s,
ignoreregex = 

I am on keycloak 25 and your regex was not working on when KC_LOG=file was in use (missing " here and there and timestamp not matched)

Here is my working keycloakf.conf filter, based on yours:

[INCLUDES]

before = common.conf

[Definition]

_threadName = [a-z][-_0-9a-z]*(\s[a-z][-_0-9a-z]*)*
_userId = (null|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
_realmName = (\S+)
_clientId = (\S+)
# preference 'user_not_found' as brute force is enabled for users with to may thumbs. 
  # 'user_not_found' user doesnt exist
  # 'invalid_user_credentials' incorrect password
_error = (user_not_found|invalid_user_credentials) 

failregex = ^.*WARN\s+\[org\.keycloak\.events\]\s+\(%(_threadName)s\)\s+type="LOGIN_ERROR",\srealmId="(.+)",\s+clientId="%(_clientId)s",\s+userId="%(_userId)s",\s+ipAddress="<HOST>",\s+error="%(_error)s"

ignoreregex = 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment