diff --git a/doc/source/security_compliance.rst b/doc/source/security_compliance.rst index 6a9b92d16..1b4510e72 100644 --- a/doc/source/security_compliance.rst +++ b/doc/source/security_compliance.rst @@ -55,6 +55,14 @@ the ``lockout_duration`` in seconds: If the ``lockout_duration`` is not set, then users may be locked out indefinitely until the user is explicitly enabled via the API. +Finally, you can set it so that some users, such as service users, are never +locked out by adding their user ID to the ``lockout_ignored_user_ids`` list: + +.. code-block:: ini + + [security_compliance] + lockout_ignored_user_ids = 3a54353c9dcc44f690975ea768512f6a,14b78ed1421a47d0b741ba218e1a49a1 + Disabling Inactive Users ------------------------ diff --git a/keystone/conf/security_compliance.py b/keystone/conf/security_compliance.py index 3ba31509f..ee692217d 100644 --- a/keystone/conf/security_compliance.py +++ b/keystone/conf/security_compliance.py @@ -53,6 +53,17 @@ non-zero value. This feature depends on the `sql` backend for the `[identity] driver`. """)) +lockout_ignored_user_ids = cfg.ListOpt( + 'lockout_ignored_user_ids', + default=[], + help=utils.fmt(""" +Comma separated list of user IDs to be ignored when checking if a user should +be locked out based on failed authentication attempts. Thus, users in this list +can fail to authenticate for an unlimited amount of times and will never be +locked out. This feature will only be enabled if `[security_compliance] +lockout_failure_attempts` is set. +""")) + password_expires_days = cfg.IntOpt( 'password_expires_days', min=1, @@ -125,6 +136,7 @@ ALL_OPTS = [ disable_user_account_days_inactive, lockout_failure_attempts, lockout_duration, + lockout_ignored_user_ids, password_expires_days, password_expires_ignore_user_ids, unique_last_password_count, diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py index 76f0c4b65..32e793813 100644 --- a/keystone/identity/backends/sql.py +++ b/keystone/identity/backends/sql.py @@ -83,6 +83,8 @@ class Identity(base.IdentityDriverBase): :returns Boolean: True if the account is locked; False otherwise """ + if user_id in CONF.security_compliance.lockout_ignored_user_ids: + return False attempts = user_ref.local_user.failed_auth_count or 0 max_attempts = CONF.security_compliance.lockout_failure_attempts lockout_duration = CONF.security_compliance.lockout_duration diff --git a/keystone/tests/unit/identity/test_backend_sql.py b/keystone/tests/unit/identity/test_backend_sql.py index 11a497c25..e35b67ea7 100644 --- a/keystone/tests/unit/identity/test_backend_sql.py +++ b/keystone/tests/unit/identity/test_backend_sql.py @@ -356,6 +356,24 @@ class LockingOutUserTests(test_backend_sql.SqlTests): user_id=self.user['id'], password=uuid.uuid4().hex) + def test_lock_out_for_ignored_user(self): + # add the user id to the ignore list + self.config_fixture.config( + group='security_compliance', + lockout_ignored_user_ids=[self.user['id']]) + # fail authentication repeatedly the max number of times + self._fail_auth_repeatedly(self.user['id']) + # authenticate with wrong password, account should not be locked + self.assertRaises(AssertionError, + self.identity_api.authenticate, + self.make_request(), + user_id=self.user['id'], + password=uuid.uuid4().hex) + # authenticate with correct password, account should not be locked + self.identity_api.authenticate(self.make_request(), + user_id=self.user['id'], + password=self.password) + def test_set_enabled_unlocks_user(self): # lockout user self._fail_auth_repeatedly(self.user['id']) diff --git a/releasenotes/notes/pci_lockout_ignore_list-83d4c86ad3984d75.yaml b/releasenotes/notes/pci_lockout_ignore_list-83d4c86ad3984d75.yaml new file mode 100644 index 000000000..db98ecf7c --- /dev/null +++ b/releasenotes/notes/pci_lockout_ignore_list-83d4c86ad3984d75.yaml @@ -0,0 +1,7 @@ +--- +features: + - > + [`bug 1642348 `_] + Added a way to ignore the lockout validation for specific users, such as + service users, by setting the `lockout_ignored_user_ids` option in the + `[security_compliance]` section of `keystone.conf`.