Listen and update keystone service users' passwords
This commit adds listeners to monitor the change of keystone service users' passwords, apply puppet runtime manifest to update the service configuration and restart the related services. Tests passed: 1 Update keyring users' password 2 Change keystone users' passwords with OpenStack CLI 3 Verified the configuration updated 4 Verified the service w/o auth failure 5 No host swact during the apply, no FM alarm created at the end of the process Note: 1 the password synchronization between keyring and keystone is not included in this review. 2. the update of the secure static hieradata is not included in this change due to upgrade concerns, users need to update the hieradata manually. E.g. the subcloud_rehome playbook will add a task to migrate the passwords in the hieradata during subcloud rehoming. 3. the unit tests will be delivered by another task in this story. Depends-On: https://review.opendev.org/c/starlingx/stx-puppet/+/853708 Story: 2010230 Task: 46074 Signed-off-by: Yuxing Jiang <Yuxing.Jiang@windriver.com> Change-Id: I1a2dbc8b1e0bd03c2086895818729b2283b0fb96
This commit is contained in:
parent
7782f877a0
commit
5b5fe7ec7c
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2019 Intel Corporation
|
||||
# Copyright (c) 2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2021-2022 Wind River Systems, Inc.
|
||||
#
|
||||
"""
|
||||
Sysinv Keystone notification listener.
|
||||
|
@ -36,21 +36,23 @@ class NotificationEndpoint(object):
|
|||
filter_rule = oslo_messaging.NotificationFilter(
|
||||
event_type='identity.user.updated')
|
||||
|
||||
def __init__(self, callback, context):
|
||||
def __init__(self, callback, context, user):
|
||||
self.callback_func = callback
|
||||
self.context = context
|
||||
self.username = user
|
||||
self.dbapi = dbapi.get_instance()
|
||||
self._openstack = openstack.OpenStackOperator(self.dbapi)
|
||||
|
||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||
"""Receives notification at info level."""
|
||||
user = self._openstack.get_platform_keystone_user(self.context.user)
|
||||
|
||||
user = self._openstack.get_platform_keystone_user(self.username)
|
||||
|
||||
if payload['eventType'] == 'activity' and \
|
||||
payload['action'] == 'updated.user' and \
|
||||
payload['outcome'] == 'success' and \
|
||||
payload['resource_info'] == user.id:
|
||||
self.callback_func(self.context)
|
||||
self.callback_func(self.context, self.username)
|
||||
|
||||
return oslo_messaging.NotificationResult.HANDLED
|
||||
|
||||
|
@ -92,8 +94,9 @@ def start_keystone_listener(callback_endpoints):
|
|||
|
||||
endpoints = []
|
||||
for callback_endpoint in callback_endpoints:
|
||||
endpoint = NotificationEndpoint(callback_endpoint.get('func'),
|
||||
callback_endpoint.get('ctx'))
|
||||
endpoint = NotificationEndpoint(callback_endpoint.get('function'),
|
||||
callback_endpoint.get('context'),
|
||||
callback_endpoint.get('user'))
|
||||
endpoints.append(endpoint)
|
||||
|
||||
pool = "sysinv-keystone-listener-workers"
|
||||
|
|
|
@ -1132,7 +1132,7 @@ class AppOperator(object):
|
|||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
|
||||
def audit_local_registry_secrets(self, context):
|
||||
def audit_local_registry_secrets(self, context, username=None):
|
||||
"""
|
||||
local registry uses admin's username&password for authentication.
|
||||
K8s stores the authentication info in secrets in order to access
|
||||
|
|
|
@ -194,6 +194,21 @@ LOCK_NAME_UPDATE_CONFIG = 'update_config_'
|
|||
LOCK_APP_AUTO_MANAGE = 'AppAutoManageLock'
|
||||
LOCK_RUNTIME_CONFIG_CHECK = 'runtime_config_check'
|
||||
|
||||
# Keystone users whose passwords change are monitored by keystone listener, and
|
||||
# the puppet classes to update the service after the passwords change.
|
||||
# TODO(yuxing): there are still several keystone users are not covered by this
|
||||
# dictionary, e.g. dcorch,dcdbsync, smapi and sysinv etc. Need to consider to
|
||||
# create puppet class to reload the related service in case their passwords
|
||||
# are changed in keystone and keyring.
|
||||
KEYSTONE_USER_PASSWORD_UPDATE = {
|
||||
"admin": "openstack::keystone::password::runtime",
|
||||
"barbican": "openstack::keystone::barbican::password::runtime",
|
||||
"dcmanager": "openstack::keystone::dcmanager::password::runtime",
|
||||
"fm": "openstack::keystone::fm::password::runtime",
|
||||
"mtce": "platform::mtce::runtime",
|
||||
"patching": "openstack::keystone::patching::password::runtime",
|
||||
"vim": "openstack::keystone::nfv::password::runtime"
|
||||
}
|
||||
|
||||
AppTarBall = namedtuple(
|
||||
'AppTarBall',
|
||||
|
@ -280,16 +295,10 @@ class ConductorManager(service.PeriodicService):
|
|||
# monitor keystone user update event to check whether admin password is
|
||||
# changed or not. If changed, then sync it to kubernetes's secret info,
|
||||
# and restart impacted services.
|
||||
admin_context = ctx.RequestContext(user='admin', tenant='admin',
|
||||
is_admin=True)
|
||||
callback_endpoints = \
|
||||
[{'func': self._app.audit_local_registry_secrets,
|
||||
'ctx': admin_context},
|
||||
{'func': self.update_keystone_password,
|
||||
'ctx': admin_context}]
|
||||
# The thread to monitor keystone admin password change
|
||||
callback_endpoints = self._get_keystone_callback_endpoints()
|
||||
greenthread.spawn(keystone_listener.start_keystone_listener,
|
||||
callback_endpoints)
|
||||
|
||||
# Monitor ceph to become responsive
|
||||
if StorageBackendConfig.has_backend_configured(
|
||||
self.dbapi,
|
||||
|
@ -352,6 +361,23 @@ class ConductorManager(service.PeriodicService):
|
|||
else:
|
||||
return None
|
||||
|
||||
def _get_keystone_callback_endpoints(self):
|
||||
""" Get call back endpoints for keystone listener"""
|
||||
|
||||
callback_endpoints = []
|
||||
context = ctx.RequestContext(user='admin', tenant='admin',
|
||||
is_admin=True)
|
||||
for username in KEYSTONE_USER_PASSWORD_UPDATE.keys():
|
||||
if username == 'admin':
|
||||
callback_endpoints.append(
|
||||
{'function': self._app.audit_local_registry_secrets,
|
||||
'context': context,
|
||||
'user': username})
|
||||
callback_endpoints.append({'function': self._update_keystone_password,
|
||||
'context': context,
|
||||
'user': username})
|
||||
return callback_endpoints
|
||||
|
||||
def _initialize_active_controller_reboot_config(self):
|
||||
# initialize host_reboot_config for active controller in case
|
||||
# process has been restarted
|
||||
|
@ -1724,16 +1750,17 @@ class ConductorManager(service.PeriodicService):
|
|||
config_dict=config_dict)
|
||||
return True
|
||||
|
||||
def update_keystone_password(self, context):
|
||||
"""This method calls a puppet class
|
||||
'openstack::keystone::password::runtime'
|
||||
on keystone password change"""
|
||||
def _update_keystone_password(self, context, username):
|
||||
"""This method calls a puppet class to update the service config and
|
||||
|
||||
reload the related service on keystone password change"""
|
||||
|
||||
LOG.info("Updating service config for keystone user: %s" % username)
|
||||
personalities = [constants.CONTROLLER]
|
||||
config_uuid = self._config_update_hosts(context, personalities)
|
||||
config_dict = {
|
||||
"personalities": personalities,
|
||||
"classes": ["openstack::keystone::password::runtime"]
|
||||
"classes": [KEYSTONE_USER_PASSWORD_UPDATE[username]],
|
||||
}
|
||||
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
|
||||
|
||||
|
@ -10837,9 +10864,7 @@ class ConductorManager(service.PeriodicService):
|
|||
LOG.info("SYS_I Clear system config alarm: %s target config %s" %
|
||||
(ihost_obj.hostname, ihost_obj.config_target))
|
||||
|
||||
self.fm_api.clear_fault(
|
||||
fm_constants.FM_ALARM_ID_SYSCONFIG_OUT_OF_DATE,
|
||||
entity_instance_id)
|
||||
self._clear_config_out_of_date_alarm(entity_instance_id)
|
||||
|
||||
self._clear_runtime_class_apply_in_progress(host_uuids=[ihost_obj.uuid])
|
||||
|
||||
|
@ -10848,6 +10873,20 @@ class ConductorManager(service.PeriodicService):
|
|||
ihost_obj.config_status = None
|
||||
ihost_obj.save(context)
|
||||
|
||||
@retry(retry_on_result=lambda x: x is False, wait_fixed=5000,
|
||||
stop_max_attempt_number=5)
|
||||
def _clear_config_out_of_date_alarm(self, entity_instance_id):
|
||||
"""Apply a new config may result a temporary failure to clear the
|
||||
|
||||
config-out-of-date alarm, retry it until success
|
||||
"""
|
||||
if self.fm_api.clear_fault(
|
||||
fm_constants.FM_ALARM_ID_SYSCONFIG_OUT_OF_DATE,
|
||||
entity_instance_id):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _config_set_reboot_required(config_uuid):
|
||||
"""Set the reboot required flag for the supplied UUID
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2013-2019 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -345,7 +345,7 @@ class OpenStackOperator(object):
|
|||
########################
|
||||
# keystone user methods
|
||||
########################
|
||||
def _get_keystone_users(self, service_config):
|
||||
def get_keystone_users(self, service_config=PLATFORM_CONFIG):
|
||||
"""Get a list of all users in keystone otherwise an empty list."""
|
||||
user_list = []
|
||||
|
||||
|
@ -356,21 +356,9 @@ class OpenStackOperator(object):
|
|||
|
||||
return user_list
|
||||
|
||||
def get_platform_keystone_admin_user(self):
|
||||
"""Return platform keystone admin user otherwise None."""
|
||||
users = self._get_keystone_users(PLATFORM_CONFIG)
|
||||
|
||||
try:
|
||||
return [user for user in users if user.name == 'admin'][0]
|
||||
except Exception as e:
|
||||
LOG.error("Failed to get platform keystone admin user:\n%s"
|
||||
% str(e))
|
||||
|
||||
return None
|
||||
|
||||
def get_platform_keystone_user(self, username):
|
||||
"""Return platform keystone user otherwise None."""
|
||||
users = self._get_keystone_users(PLATFORM_CONFIG)
|
||||
users = self.get_keystone_users()
|
||||
|
||||
try:
|
||||
return [user for user in users if user.name == username][0]
|
||||
|
|
|
@ -1658,6 +1658,10 @@ class ManagerTestCase(base.DbTestCase):
|
|||
|
||||
def _clear_alarm(self, fm_id, fm_instance):
|
||||
self.alarm_raised = False
|
||||
# Need to return a value to simulate the return value of
|
||||
# fm_api.clear_fault, as we rely on it to see whether the the clear_fault
|
||||
# is succeeded and if a retry is required.
|
||||
return True
|
||||
|
||||
def _get_faults_by_id(self, alarm_id):
|
||||
return None
|
||||
|
|
Loading…
Reference in New Issue