From a49ee620fabf4ed12d6c1ff562de6912f316ef60 Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Fri, 17 Apr 2020 23:08:40 +0530 Subject: [PATCH] New config option 'user_limit' in credentials This patch allows adds new config option 'user_limit' to credentials to set maximum number of credentials a user is permitted to create. Closes-Bug: #1872732 Change-Id: Ic9dc9a4a9ec1ecbf01842c865e19a7a100e5041d --- keystone/conf/credential.py | 12 +++++++++++- keystone/credential/core.py | 10 ++++++++++ keystone/exception.py | 5 +++++ keystone/tests/unit/credential/test_backend_sql.py | 11 +++++++++++ releasenotes/notes/bug-1872732-7261816d0b170008.yaml | 6 ++++++ 5 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1872732-7261816d0b170008.yaml diff --git a/keystone/conf/credential.py b/keystone/conf/credential.py index 2066fbdfbd..9dcb870c71 100644 --- a/keystone/conf/credential.py +++ b/keystone/conf/credential.py @@ -69,6 +69,15 @@ The length of time in minutes for which a signed EC2 or S3 token request is valid from the timestamp contained in the token request. """)) +user_limit = cfg.IntOpt( + 'user_limit', + default=-1, + help=utils.fmt(""" +Maximum number of credentials a user is permitted to create. A value of +-1 means unlimited. If a limit is not set, users are permitted to create +credentials at will, which could lead to bloat in the keystone database +or open keystone to a DoS attack. +""")) GROUP_NAME = __name__.split('.')[-1] ALL_OPTS = [ @@ -77,7 +86,8 @@ ALL_OPTS = [ key_repository, caching, cache_time, - auth_ttl + auth_ttl, + user_limit, ] diff --git a/keystone/credential/core.py b/keystone/credential/core.py index 5a32ef8b29..78551dcd17 100644 --- a/keystone/credential/core.py +++ b/keystone/credential/core.py @@ -86,6 +86,14 @@ class Manager(manager.Manager): credential_copy.pop('blob', None) return credential_copy + def _assert_limit_not_exceeded(self, user_id): + user_limit = CONF.credential.user_limit + if user_limit >= 0: + cred_count = len(self.list_credentials_for_user(user_id)) + if cred_count >= user_limit: + raise exception.CredentialLimitExceeded( + limit=user_limit) + @manager.response_truncated def list_credentials(self, hints=None): credentials = self.driver.list_credentials( @@ -119,6 +127,8 @@ class Manager(manager.Manager): initiator=None): """Create a credential.""" credential_copy = self._encrypt_credential(credential) + user_id = credential_copy['user_id'] + self._assert_limit_not_exceeded(user_id) ref = self.driver.create_credential(credential_id, credential_copy) if MEMOIZE.should_cache(ref): self._get_credential.set(ref, diff --git a/keystone/exception.py b/keystone/exception.py index ff6b32289c..c62338b893 100644 --- a/keystone/exception.py +++ b/keystone/exception.py @@ -222,6 +222,11 @@ class ApplicationCredentialLimitExceeded(ForbiddenNotSecurity): "maximum of %(limit)d already exceeded for user.") +class CredentialLimitExceeded(ForbiddenNotSecurity): + message_format = _("Unable to create additional credentials, maximum " + "of %(limit)d already exceeded for user.") + + class SecurityError(Error): """Security error exception. diff --git a/keystone/tests/unit/credential/test_backend_sql.py b/keystone/tests/unit/credential/test_backend_sql.py index 53c0a44f12..791c10022b 100644 --- a/keystone/tests/unit/credential/test_backend_sql.py +++ b/keystone/tests/unit/credential/test_backend_sql.py @@ -12,6 +12,8 @@ import uuid +from oslo_config import fixture as config_fixture + from keystone.common import provider_api from keystone.credential.providers import fernet as credential_provider from keystone.tests import unit @@ -20,6 +22,7 @@ from keystone.tests.unit import ksfixtures from keystone.tests.unit.ksfixtures import database from keystone.credential.backends import sql as credential_sql +from keystone import exception PROVIDERS = provider_api.ProviderAPIs @@ -102,3 +105,11 @@ class SqlCredential(SqlTests): # Make sure CredentialModel is handing over a text string # to the database. To avoid encoding issues self.assertIsInstance(ref.encrypted_blob, str) + + def test_credential_limits(self): + config_fixture_ = self.user = self.useFixture(config_fixture.Config()) + config_fixture_.config(group='credential', user_limit=4) + self._create_credential_with_user_id(self.user_foo['id']) + self.assertRaises(exception.CredentialLimitExceeded, + self._create_credential_with_user_id, + self.user_foo['id']) diff --git a/releasenotes/notes/bug-1872732-7261816d0b170008.yaml b/releasenotes/notes/bug-1872732-7261816d0b170008.yaml new file mode 100644 index 0000000000..facdf7128f --- /dev/null +++ b/releasenotes/notes/bug-1872732-7261816d0b170008.yaml @@ -0,0 +1,6 @@ +--- +features: + - > + [`bug 1872732 `_] + 'user_limit' is added to config file of credentials that allows user to set + maximum number of credentials a user is permitted to create.