distcloud/distributedcloud/dcmanager/audit/kube_rootca_update_audit.py

214 lines
8.9 KiB
Python

#
# Copyright (c) 2021-2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from keystoneauth1 import exceptions as keystone_exceptions
from oslo_config import cfg
from oslo_log import log as logging
from fm_api.constants import FM_ALARM_ID_CERT_EXPIRED
from fm_api.constants import FM_ALARM_ID_CERT_EXPIRING_SOON
from dccommon import consts as dccommon_consts
from dccommon import utils as dccommon_utils
from dccommon.drivers.openstack.fm import FmClient
from dccommon.drivers.openstack.sdk_platform import OpenStackDriver
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
from dcmanager.audit.auditor import Auditor
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
KUBE_ROOTCA_ALARM_LIST = [FM_ALARM_ID_CERT_EXPIRED,
FM_ALARM_ID_CERT_EXPIRING_SOON, ]
MONITORED_ALARM_ENTITIES = ['system.certificate.kubernetes-root-ca', ]
class KubeRootcaUpdateAudit(Auditor):
"""Manages tasks related to kube rootca update audits."""
def __init__(self, context, dcmanager_state_rpc_client):
super(KubeRootcaUpdateAudit, self).__init__(
context,
dcmanager_state_rpc_client,
dccommon_consts.ENDPOINT_TYPE_KUBE_ROOTCA
)
self.audit_type = "kube rootca update"
LOG.debug("%s audit initialized" % self.audit_type)
def get_regionone_audit_data(self):
"""Query RegionOne to determine kube rootca update information.
Kube rootca audit is based on the root CA cert ID. This identifier will
consist of a hash from certificate issuer representation and its serial
number.
:return: A string of the root CA cert ID
"""
try:
m_os_ks_client = OpenStackDriver(
region_name=dccommon_consts.DEFAULT_REGION_NAME,
region_clients=None).keystone_client
endpoint = m_os_ks_client.endpoint_cache.get_endpoint('sysinv')
sysinv_client = SysinvClient(
dccommon_consts.DEFAULT_REGION_NAME, m_os_ks_client.session,
endpoint=endpoint)
except Exception:
LOG.exception("Failed init OS Client, skip Kubernetes root CA "
"audit.")
return None
try:
# Ignore the success flag as the sysinv get_kube_rootca_id is
# already introduced on system controllers.
_, cc_cert = sysinv_client.get_kube_rootca_cert_id()
except Exception:
# Cannot get the cert ID from central cloud, return None
LOG.exception("Failed to get Kubernetes root CA from Region One, "
"skip Kubernetes root CA audit.")
return None
regionone_rootca_certid = cc_cert.cert_id
LOG.debug("RegionOne kubernetes rootca update data: "
f"{regionone_rootca_certid}.")
return regionone_rootca_certid
def subcloud_kube_rootca_audit(self, subcloud, regionone_rootca_certid):
"""Perform an audit of kube root CA update info in a subcloud.
The audit logic is as follow:
CentOS subclouds -> alarm based
Debian subclouds:
not rehomed(initially deployed or re-deployed) -> alarm based
rehomed subclouds:
Not region one cert ID -> skip audit
subcloud doesn't have the API to get cert ID -> alarm based
region one cert ID -> cert based
:param subcloud: the subcloud obj
:param region_one_audit_data: the audit data of the region one
"""
subcloud_name = subcloud.name
subcloud_region = subcloud.region_name
LOG.info("Triggered %s audit for: %s" % (self.audit_type,
subcloud_name))
try:
sc_os_client = OpenStackDriver(region_name=subcloud_region,
region_clients=None).keystone_client
session = sc_os_client.session
endpoint = sc_os_client.endpoint_cache.get_endpoint('sysinv')
except (keystone_exceptions.EndpointNotFound,
keystone_exceptions.ConnectFailure,
keystone_exceptions.ConnectTimeout,
IndexError):
LOG.exception("Endpoint for online subcloud:(%s) not found, skip "
"%s audit." % (subcloud_name, self.audit_type))
return
# Firstly, apply alarm based audit against the subclouds deployed in
# the distributed cloud and the subcloud running on old software
# version that cannot search for the k8s root CA cert id.
if dccommon_utils.is_centos(subcloud.software_version) or \
not subcloud.rehomed:
self.subcloud_audit_alarm_based(subcloud_name, subcloud_region,
session)
return
# Skip the audit if cannot get the region one cert ID.
if not regionone_rootca_certid:
self.set_subcloud_endpoint_in_sync(subcloud_name, subcloud_region)
LOG.debug(f"No region one audit data, skip {self.audit_type} "
f"audit for subcloud: {subcloud_name}.")
return
try:
sysinv_client = SysinvClient(subcloud_region, session,
endpoint=endpoint)
success, subcloud_cert_data = \
sysinv_client.get_kube_rootca_cert_id()
except Exception:
LOG.exception("Failed to get Kubernetes root CA cert ID of "
f"subcloud: {subcloud_name}, skip "
f"{self.audit_type} audit.")
return
if not success:
# if not success, the subcloud is a Debian based subcloud without
# the sysinv API to get the cert ID, audit the subcloud based on
# its alarm.
self.subcloud_audit_alarm_based(subcloud_name, subcloud_region,
session)
else:
self.subcloud_audit_cert_based(subcloud_name, subcloud_region,
subcloud_cert_data,
regionone_rootca_certid)
def subcloud_audit_alarm_based(self, subcloud_name,
subcloud_region, session):
"""The subcloud doesn't have the method to get Kubernetes root CA
cert ID, use alarm based audit.
:param subcloud_name: the name of the subcloud
:param subcloud_region: the region of the subcloud
:param session: the keystone session of the subcloud
"""
try:
fm_client = FmClient(subcloud_region, session)
except (keystone_exceptions.EndpointNotFound,
keystone_exceptions.ConnectFailure,
keystone_exceptions.ConnectTimeout,
IndexError):
LOG.exception("Endpoint for online subcloud:(%s) not found, skip "
"%s audit." % (subcloud_name, self.audit_type))
return
out_of_sync = False
detected_alarms = fm_client.get_alarms_by_ids(KUBE_ROOTCA_ALARM_LIST)
if detected_alarms:
for alarm in detected_alarms:
if alarm.entity_instance_id in MONITORED_ALARM_ENTITIES:
out_of_sync = True
break
if out_of_sync:
self.set_subcloud_endpoint_out_of_sync(subcloud_name,
subcloud_region)
else:
self.set_subcloud_endpoint_in_sync(subcloud_name, subcloud_region)
LOG.info("%s audit completed for: %s" % (self.audit_type,
subcloud_name))
def subcloud_audit_cert_based(self, subcloud_name, subcloud_region,
subcloud_cert_data, regionone_rootca_certid):
"""Audit if a subcloud's k8s root CA cert is the same as the central
:param subcloud_name: the name of the subcloud
:param subcloud_region: the region of the subcloud
:param regionone_rootca_certid: the cert ID of the region one
:param subcloud_cert: subcloud's cert info
"""
out_of_sync = False
if subcloud_cert_data.error:
LOG.exception("Failed to get Kubernetes root CA cert id for "
f"subcloud:{subcloud_name}, error: "
f"{subcloud_cert_data.error}, skip {self.audit_type} "
"audit.")
return
elif subcloud_cert_data.cert_id != regionone_rootca_certid:
out_of_sync = True
if out_of_sync:
self.set_subcloud_endpoint_out_of_sync(subcloud_name,
subcloud_region)
else:
self.set_subcloud_endpoint_in_sync(subcloud_name, subcloud_region)
LOG.info("%s audit completed for: %s" % (self.audit_type,
subcloud_name))