From 86820ad8ecb279f7e5dd62c92029faec97b99534 Mon Sep 17 00:00:00 2001 From: Andy Ning Date: Tue, 5 Apr 2022 16:58:00 -0400 Subject: [PATCH] Add support to keystone to store users in keyring on Debian This update patched keystone to support storing users in keyring under "CGCS" service. Test Plan for Debian: PASS: package build, image build PASS: system bootstrap, unlock PASS: Change keystone "admin" password, observe it changes in keyring too. PASS: Add a new keystone user "test" with password, add the user to keyring by "keyring set CGCS test". Change test's password, observe it changes in keyring too. PASS: Delete the keystone user "test", observe user "test" is deleted from keyring. Story: 2009965 Task: 44970 Signed-off-by: Andy Ning Change-Id: I75ea23f87487b764370a0990ad8aba896d3a0767 --- ...001-Support-storing-users-in-keyring.patch | 151 ++++++++++++++++++ openstack/keystone/debian/patches/series | 1 + 2 files changed, 152 insertions(+) create mode 100644 openstack/keystone/debian/patches/0001-Support-storing-users-in-keyring.patch create mode 100644 openstack/keystone/debian/patches/series diff --git a/openstack/keystone/debian/patches/0001-Support-storing-users-in-keyring.patch b/openstack/keystone/debian/patches/0001-Support-storing-users-in-keyring.patch new file mode 100644 index 00000000..e8eee6db --- /dev/null +++ b/openstack/keystone/debian/patches/0001-Support-storing-users-in-keyring.patch @@ -0,0 +1,151 @@ +From 45b5c5b71b4ad70c5694f06126adfc60a31c51fc Mon Sep 17 00:00:00 2001 +From: Andy Ning +Date: Tue, 5 Apr 2022 10:39:32 -0400 +Subject: [PATCH] Support storing users in keyring + +This patch added support to store keystone users in keyring in +"CGCS" service. + +Signed-off-by: Andy Ning +--- + keystone/exception.py | 6 +++++ + keystone/identity/core.py | 54 +++++++++++++++++++++++++++++++++++++++ + requirements.txt | 1 + + 3 files changed, 61 insertions(+) + +diff --git a/keystone/exception.py b/keystone/exception.py +index c62338b..3cbddfb 100644 +--- a/keystone/exception.py ++++ b/keystone/exception.py +@@ -227,6 +227,12 @@ class CredentialLimitExceeded(ForbiddenNotSecurity): + "of %(limit)d already exceeded for user.") + + ++class WRSForbiddenAction(Error): ++ message_format = _("That action is not permitted") ++ code = 403 ++ title = 'Forbidden' ++ ++ + class SecurityError(Error): + """Security error exception. + +diff --git a/keystone/identity/core.py b/keystone/identity/core.py +index 38ebe2f..31d6cd6 100644 +--- a/keystone/identity/core.py ++++ b/keystone/identity/core.py +@@ -17,6 +17,7 @@ + import copy + import functools + import itertools ++import keyring + import operator + import os + import threading +@@ -54,6 +55,7 @@ MEMOIZE_ID_MAPPING = cache.get_memoization_decorator(group='identity', + + DOMAIN_CONF_FHEAD = 'keystone.' + DOMAIN_CONF_FTAIL = '.conf' ++KEYRING_CGCS_SERVICE = "CGCS" + + # The number of times we will attempt to register a domain to use the SQL + # driver, if we find that another process is in the middle of registering or +@@ -1125,6 +1127,26 @@ class Manager(manager.Manager): + if new_ref['domain_id'] != orig_ref['domain_id']: + raise exception.ValidationError(_('Cannot change Domain ID')) + ++ def _update_keyring_password(self, user, new_password): ++ """Update user password in Keyring backend. ++ This method Looks up user entries in Keyring backend ++ and accordingly update the corresponding user password. ++ :param user : keyring user struct ++ :param new_password : new password to set ++ """ ++ if (new_password is not None) and ('name' in user): ++ try: ++ # only update if an entry exists ++ if (keyring.get_password(KEYRING_CGCS_SERVICE, user['name'])): ++ keyring.set_password(KEYRING_CGCS_SERVICE, ++ user['name'], new_password) ++ except (keyring.errors.PasswordSetError, RuntimeError): ++ msg = ('Failed to Update Keyring Password for the user %s') ++ LOG.warning(msg, user['name']) ++ # only raise an exception if this is the admin user ++ if (user['name'] == 'admin'): ++ raise exception.WRSForbiddenAction(msg % user['name']) ++ + def _update_user_with_federated_objects(self, user, driver, entity_id): + # If the user did not pass a federated object along inside the user + # object then we simply update the user as normal and add the +@@ -1181,6 +1203,17 @@ class Manager(manager.Manager): + + ref = self._update_user_with_federated_objects(user, driver, entity_id) + ++ # Certain local Keystone users are stored in Keystone as opposed ++ # to the default SQL Identity backend, such as the admin user. ++ # When its password is updated, we need to update Keyring as well ++ # as certain services retrieve this user context from Keyring and ++ # will get auth failures ++ # Need update password before send out notification. Otherwise, ++ # any process monitor the notification will still get old password ++ # from Keyring. ++ if ('password' in user) and ('name' in ref): ++ self._update_keyring_password(ref, user['password']) ++ + notifications.Audit.updated(self._USER, user_id, initiator) + + enabled_change = ((user.get('enabled') is False) and +@@ -1210,6 +1243,7 @@ class Manager(manager.Manager): + hints.add_filter('user_id', user_id) + fed_users = PROVIDERS.shadow_users_api.list_federated_users_info(hints) + ++ username = user_old.get('name', "") + driver.delete_user(entity_id) + PROVIDERS.assignment_api.delete_user_assignments(user_id) + self.get_user.invalidate(self, user_id) +@@ -1223,6 +1257,18 @@ class Manager(manager.Manager): + + PROVIDERS.credential_api.delete_credentials_for_user(user_id) + PROVIDERS.id_mapping_api.delete_id_mapping(user_id) ++ ++ # Delete the keyring entry associated with this user (if present) ++ try: ++ keyring.delete_password(KEYRING_CGCS_SERVICE, username) ++ except keyring.errors.PasswordDeleteError: ++ LOG.warning(('delete_user: PasswordDeleteError for %s'), ++ username) ++ pass ++ except exception.UserNotFound: ++ LOG.warning(('delete_user: UserNotFound for %s'), ++ username) ++ pass + notifications.Audit.deleted(self._USER, user_id, initiator) + + # Invalidate user role assignments cache region, as it may be caching +@@ -1475,6 +1521,14 @@ class Manager(manager.Manager): + notifications.Audit.updated(self._USER, user_id, initiator) + self._persist_revocation_event_for_user(user_id) + ++ user = self.get_user(user_id) ++ # Update Keyring password for the 'user' if it ++ # has an entry in Keyring ++ if (original_password) and ('name' in user): ++ # Change the 'user' password in keyring, provided the user ++ # has an entry in Keyring backend ++ self._update_keyring_password(user, new_password) ++ + @MEMOIZE + def _shadow_nonlocal_user(self, user): + try: +diff --git a/requirements.txt b/requirements.txt +index 33a2c42..1119c52 100644 +--- a/requirements.txt ++++ b/requirements.txt +@@ -36,3 +36,4 @@ pycadf!=2.0.0,>=1.1.0 # Apache-2.0 + msgpack>=0.5.0 # Apache-2.0 + osprofiler>=1.4.0 # Apache-2.0 + pytz>=2013.6 # MIT ++keyring>=5.3 +-- +2.25.1 + diff --git a/openstack/keystone/debian/patches/series b/openstack/keystone/debian/patches/series new file mode 100644 index 00000000..8661b867 --- /dev/null +++ b/openstack/keystone/debian/patches/series @@ -0,0 +1 @@ +0001-Support-storing-users-in-keyring.patch