From c4005d29f07d10eeda580d804a48fdfc9441f074 Mon Sep 17 00:00:00 2001 From: Gilvan Filho Date: Tue, 6 Aug 2024 09:50:34 -0300 Subject: [PATCH] add linear strategy to brute force closes #25917 Signed-off-by: Gilvan Filho --- .../idm/RealmRepresentation.java | 13 +++++ .../topics/threat/brute-force.adoc | 37 ++++++++++--- .../admin/messages/messages_en.properties | 4 ++ .../security-defences/BruteForceDetection.tsx | 13 +++++ .../src/defs/realmRepresentation.ts | 1 + .../models/cache/infinispan/RealmAdapter.java | 13 +++++ .../infinispan/entities/CachedRealm.java | 7 +++ .../org/keycloak/models/jpa/RealmAdapter.java | 16 ++++++ .../datastore/DefaultExportImportManager.java | 2 + .../models/utils/ModelToRepresentation.java | 2 + .../models/utils/RealmModelDelegate.java | 5 ++ .../util/IdentityBrokerStateTestHelpers.java | 11 ++++ .../java/org/keycloak/models/RealmModel.java | 3 ++ .../managers/DefaultBruteForceProtector.java | 10 +++- .../services/managers/RealmManager.java | 1 + .../testsuite/forms/BruteForceTest.java | 52 +++++++++++++++++++ 16 files changed, 182 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 3e347833b2..064544fa9b 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -92,6 +92,7 @@ public class RealmRepresentation { protected Boolean bruteForceProtected; protected Boolean permanentLockout; protected Integer maxTemporaryLockouts; + protected BruteForceStrategy bruteForceStrategy; protected Integer maxFailureWaitSeconds; protected Integer minimumQuickLoginWaitSeconds; protected Integer waitIncrementSeconds; @@ -777,6 +778,14 @@ public class RealmRepresentation { this.maxTemporaryLockouts = maxTemporaryLockouts; } + public BruteForceStrategy getBruteForceStrategy() { + return this.bruteForceStrategy; + } + + public void setBruteForceStrategy(BruteForceStrategy bruteForceStrategy) { + this.bruteForceStrategy = bruteForceStrategy; + } + public Integer getMaxFailureWaitSeconds() { return maxFailureWaitSeconds; } @@ -1450,4 +1459,8 @@ public class RealmRepresentation { } organizations.add(org); } + + public enum BruteForceStrategy { + LINEAR, MULTIPLE; + } } diff --git a/docs/documentation/server_admin/topics/threat/brute-force.adoc b/docs/documentation/server_admin/topics/threat/brute-force.adoc index 44d641001f..033568b487 100644 --- a/docs/documentation/server_admin/topics/threat/brute-force.adoc +++ b/docs/documentation/server_admin/topics/threat/brute-force.adoc @@ -75,15 +75,19 @@ wait time will never reach the value you have set to `Max wait`. .. If the time between this failure and the last failure is greater than _Failure Reset Time_ ... Reset `count` .. Increment `count` -.. Calculate `wait` using _Wait Increment_ * (`count` / _Max Login Failures_). The division is an integer division rounded down to a whole number -.. If `wait` equals 0 and the time between this failure and the last failure is less than _Quick Login Check Milliseconds_, set `wait` to _Minimum Quick Login Wait_. +.. Calculate `wait` according brute force strategy defined (see below Strategies to set Wait Time). +.. If `wait` equals to or less than 0 and the time between this failure and the last failure is less than _Quick Login Check Milliseconds_, set `wait` to _Minimum Quick Login Wait_. ... Temporarily disable the user for the smallest of `wait` and _Max Wait_ seconds ... Increment the temporary lockout counter `count` does not increment when a temporarily disabled account commits a login failure. ==== -For instance, if you have set `Max Login Failures` to `5` and a `Wait Increment` of `30` seconds, the effective time an account will be disabled after several failed authentication attempts will be: +*Strategies to set Wait Time* + +Keycloak provides two strategies to calculate wait time: By multiples or Linear. By multiples is the first strategy introduced by keycloak, so that is the default one. + +With by multiples strategy wait time will be incremented when number (or count) of failures are multiple of `Max Login Failure`. For instance, if you have set `Max Login Failures` to `5` and a `Wait Increment` of `30` seconds, the effective time an account will be disabled after several failed authentication attempts will be: [cols="1,1,1,1"] |=== @@ -100,9 +104,30 @@ For instance, if you have set `Max Login Failures` to `5` and a `Wait Increment` |**10** |**30** | 5 | **60** |=== -Note that the `Effective Wait Time` at the 5th failed attempt will disable the account for `30` seconds. Only after reaching -the next multiple of `Max Login Failures`, in this case `10`, will the time increase from `30` to `60`. The time the account will be disabled -is only increased when reaching multiples of `Max Login Failures`. +Note that the `Effective Wait Time` at the 5th failed attempt will disable the account for `30` seconds. Only after reaching the next multiple of `Max Login Failures`, in this case `10`, will the time increase from `30` to `60`. The time the account will be disabled is only increased when reaching multiples of `Max Login Failures`. + +The by multiple strategy uses the following formula to calculate wait time: _Wait Increment_ * (`count` / _Max Login Failures_). The division is an integer division rounded down to a whole number. + +With linear strategy wait time will be incremented when number (or count) of failures are equal to or greater than `Max Login Failure`. For instance, if you have set `Max Login Failures` to `5` and a `Wait Increment` of `30` seconds, the effective time an account will be disabled after several failed authentication attempts will be: + +[cols="1,1,1,1"] +|=== +|`Number of Failures` | `Wait Increment` | `Max Login Failures` | `Effective Wait Time` +|1 |30 | 5 | 0 +|2 |30 | 5 | 0 +|3 |30 | 5 | 0 +|4 |30 | 5 | 0 +|**5** |**30** | 5 | **30** +|**6** |**30** | 5 | **60** +|**7** |**30** | 5 | **90** +|**8** |**30** | 5 | **120** +|**9** |**30** | 5 | **150** +|**10** |**30** | 5 | **180** +|=== + +Note that the `Effective Wait Time` at the 5th failed attempt will disable the account for `30` seconds. Each new failed attempt will increase wait time. + +The linear strategy uses the following formula to calculate wait time: _Wait Increment_ * (1 + `count` - _Max Login Failures_). *Permanent Lockout Parameters* diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index 702164fab8..1f97634391 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -729,6 +729,10 @@ rowSaveBtnAriaLabel=Save edits for {{messageBundle}} permanentLockout=Permanent lockout maxTemporaryLockouts=Maximum temporary lockouts maxTemporaryLockoutsHelp=The number of temporary lockouts permitted before the user is permanently locked out. +bruteForceStrategy=Strategy to increase wait time +bruteForceStrategyHelp=Multiple means wait time will be increased only when number of failures are multiples of '{{failureFactor}}'. Linear means each new failure starting at '{{failureFactor}}' will increase wait time. +bruteForceStrategy.LINEAR=Linear +bruteForceStrategy.MULTIPLE=Multiple debug=Debug webAuthnPolicyRequireResidentKey=Require discoverable credential unlockUsersConfirm=All the users that are temporarily locked will be unlocked. diff --git a/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx b/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx index 8e8940a778..4737437a1d 100644 --- a/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx +++ b/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx @@ -4,6 +4,7 @@ import { KeycloakSelect, NumberControl, SelectVariant, + SelectControl, } from "@keycloak/keycloak-ui-shared"; import { ActionGroup, @@ -52,6 +53,8 @@ export const BruteForceDetection = ({ BruteForceMode.PermanentAfterTemporaryLockout, ]; + const bruteForceStrategyTypes = ["MULTIPLE", "LINEAR"]; + const setupForm = () => { convertToFormValues(realm, setValue); setIsBruteForceModeUpdated(false); @@ -155,6 +158,16 @@ export const BruteForceDetection = ({ bruteForceMode === BruteForceMode.PermanentAfterTemporaryLockout) && ( <> + ({ + key, + value: t(`bruteForceStrategy.${key}`), + }))} + />