Merge "Fix platform certificate subject during upgrades"
This commit is contained in:
commit
4356bb7ec4
@ -1,20 +1,32 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) 2023 Wind River Systems, Inc.
|
||||
#!/usr/bin/python3
|
||||
# Copyright (c) 2023-2024 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# This script creates required platform certificates for DX systems.
|
||||
# SX systems leverage the execution ansible upgrade playbook for this.
|
||||
# This script creates/updates required platform certificates during upgrade.
|
||||
# - Certificates are created using ansible playbooks.
|
||||
# - (Legacy) SX upgrade is already covered by upgrade playbook.
|
||||
#
|
||||
# - Subject is updated to match new defaults, if not otherwise customized by
|
||||
# the user:
|
||||
# - 'commonName' - default now is <cert_short_name>
|
||||
# - 'localities' - default now is <region>
|
||||
# - 'organization' - default now is 'starlingx'
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import yaml
|
||||
from controllerconfig.common import log
|
||||
from time import sleep
|
||||
import os
|
||||
|
||||
LOG = log.get_logger(__name__)
|
||||
KUBE_CMD = 'kubectl --kubeconfig=/etc/kubernetes/admin.conf '
|
||||
TMP_FILENAME = '/tmp/update_cert.yml'
|
||||
RETRIES = 3
|
||||
|
||||
|
||||
def get_system_mode():
|
||||
# get system_mode from platform.conf
|
||||
lines = [line.rstrip('\n') for line in
|
||||
open('/etc/platform/platform.conf')]
|
||||
for line in lines:
|
||||
@ -24,6 +36,42 @@ def get_system_mode():
|
||||
return None
|
||||
|
||||
|
||||
def get_distributed_cloud_role():
|
||||
lines = [line.rstrip('\n') for line in
|
||||
open('/etc/platform/platform.conf')]
|
||||
for line in lines:
|
||||
values = line.split('=')
|
||||
if values[0] == 'distributed_cloud_role':
|
||||
return values[1]
|
||||
return None
|
||||
|
||||
|
||||
def get_region_name():
|
||||
"""Get region name
|
||||
"""
|
||||
for line in open('/etc/platform/openrc'):
|
||||
if 'export ' in line:
|
||||
values = line.rstrip('\n').lstrip('export ').split('=')
|
||||
if values[0] == 'OS_REGION_NAME':
|
||||
return values[1]
|
||||
return None
|
||||
|
||||
|
||||
def get_oam_ip():
|
||||
cmd = 'source /etc/platform/openrc && ' \
|
||||
'(system addrpool-list --nowrap | awk \'$4 == "oam" { print $14 }\')'
|
||||
|
||||
sub = subprocess.Popen(
|
||||
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = sub.communicate()
|
||||
if sub.returncode == 0:
|
||||
return stdout.decode('utf-8').rstrip('\n')
|
||||
else:
|
||||
LOG.error('Command failed:\n %s\n. %s\n%s\n'
|
||||
% (cmd, stdout.decode('utf-8'), stderr.decode('utf-8')))
|
||||
raise Exception('Cannot retrieve OAM IP.')
|
||||
|
||||
|
||||
def create_platform_certificates(to_release):
|
||||
"""Run ansible playbook to create platform certificates
|
||||
"""
|
||||
@ -31,13 +79,162 @@ def create_platform_certificates(to_release):
|
||||
upgrade_script = 'create-platform-certificates-in-upgrade.yml'
|
||||
cmd = 'ansible-playbook {}/{} -e "software_version={}"'.format(
|
||||
playbooks_root, upgrade_script, to_release)
|
||||
|
||||
sub = subprocess.Popen(
|
||||
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = sub.communicate()
|
||||
if sub.returncode != 0:
|
||||
LOG.error('Command failed:\n %s\n. %s\n%s\n'
|
||||
% (cmd, stdout.decode('utf-8'), stderr.decode('utf-8')))
|
||||
raise Exception('Cannot create platform certificates.')
|
||||
LOG.info('Successfully created platform certificates. Output:\n%s\n'
|
||||
% stdout.decode('utf-8'))
|
||||
|
||||
|
||||
def certificate_exists(certificate, namespace='deployment'):
|
||||
"""Check if certificate exists
|
||||
"""
|
||||
cmd = (KUBE_CMD + 'get certificates -n ' + namespace +
|
||||
' -o custom-columns=NAME:metadata.name --no-headers')
|
||||
|
||||
sub = subprocess.Popen(cmd, shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = sub.communicate()
|
||||
if sub.returncode != 0:
|
||||
LOG.error('Command failed:\n %s\n. %s\n%s' % (cmd, stdout, stderr))
|
||||
raise Exception('Cannot create platform certificates.')
|
||||
LOG.info('Successfully created platform certificates.')
|
||||
if sub.returncode == 0:
|
||||
return certificate in stdout.decode('utf-8').splitlines()
|
||||
else:
|
||||
LOG.error('Command failed:\n %s\n. %s\n%s\n'
|
||||
% (cmd, stdout.decode('utf-8'), stderr.decode('utf-8')))
|
||||
raise Exception('Cannot retrieve existent certificates '
|
||||
'from namespace: %s.' % namespace)
|
||||
|
||||
|
||||
def retrieve_certificate(certificate, namespace='deployment'):
|
||||
"""Retrieve certificate (as YAML text)
|
||||
"""
|
||||
get_cmd = (KUBE_CMD + 'get certificate ' + certificate + ' -n ' +
|
||||
namespace + ' -o yaml')
|
||||
|
||||
sub = subprocess.Popen(
|
||||
get_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = sub.communicate()
|
||||
if sub.returncode == 0:
|
||||
return stdout.decode('utf-8')
|
||||
else:
|
||||
LOG.error('Command failed:\n %s\n. %s\n%s\n'
|
||||
% (get_cmd, stdout.decode('utf-8'), stderr.decode('utf-8')))
|
||||
raise Exception('Cannot dump Certificate %s from namespace: %s.'
|
||||
% (certificate, namespace))
|
||||
|
||||
|
||||
def get_old_default_CN_by_cert(certificate):
|
||||
"""Return the old default CN per certificate
|
||||
"""
|
||||
oam_ip = get_oam_ip()
|
||||
default_CN_by_cert = {
|
||||
'system-restapi-gui-certificate': oam_ip,
|
||||
'system-registry-local-certificate': oam_ip,
|
||||
'system-openldap-local-certificate': 'system-openldap'
|
||||
}
|
||||
return default_CN_by_cert[certificate]
|
||||
|
||||
|
||||
def update_certificate(certificate, short_name):
|
||||
"""Update the desired subject fields for the certificates
|
||||
"""
|
||||
LOG.info("Verifying subject of certificate: %s" % certificate)
|
||||
loaded_data = yaml.safe_load(retrieve_certificate(certificate))
|
||||
|
||||
if loaded_data.get('spec', None) is None:
|
||||
error = ('Certificate %s data is incorrect, missing \'spec\' field.'
|
||||
% certificate)
|
||||
LOG.error(error)
|
||||
raise Exception(error)
|
||||
|
||||
region = get_region_name()
|
||||
cert_changes = False
|
||||
same_CN = False
|
||||
|
||||
common_name = loaded_data['spec'].get('commonName', None)
|
||||
if common_name == get_old_default_CN_by_cert(certificate):
|
||||
same_CN = True
|
||||
if certificate != 'system-openldap-local-certificate':
|
||||
common_name = short_name
|
||||
loaded_data['spec'].update({'commonName': common_name})
|
||||
cert_changes = True
|
||||
|
||||
if same_CN and (loaded_data['spec'].get('subject', None) is None):
|
||||
loaded_data['spec'].update({
|
||||
'subject': {'localities': [region.lower()],
|
||||
'organizations': ['starlingx']}})
|
||||
cert_changes = True
|
||||
else:
|
||||
# If localities exists, it should have two entries:
|
||||
# 1) 'subject_L' override
|
||||
# 2) <subject_prefix>:<region_name>:<cert_short_name>
|
||||
# We will remove the 2nd to match the new configuration.
|
||||
localities = \
|
||||
loaded_data['spec'].get('subject', {}).get('localities', None)
|
||||
if localities:
|
||||
if len(localities) != 2:
|
||||
LOG.warning('Unexpected number of \'L\' entries in subject '
|
||||
'of certificate %s: %s'
|
||||
% (certificate, len(localities)))
|
||||
|
||||
unwanted_index = None
|
||||
for index, item in enumerate(localities):
|
||||
if (region.lower() + ':' + short_name) in item:
|
||||
unwanted_index = index
|
||||
break
|
||||
|
||||
if unwanted_index is not None:
|
||||
if len(localities) == 1:
|
||||
localities[0] = region.lower()
|
||||
else:
|
||||
localities.pop(unwanted_index)
|
||||
loaded_data['spec']['subject'].update(
|
||||
{'localities': localities})
|
||||
cert_changes = True
|
||||
else:
|
||||
LOG.warning('Expected subject \'L\' entry that identifies '
|
||||
'the certificate not found for %s.' % certificate)
|
||||
|
||||
if cert_changes:
|
||||
with open(TMP_FILENAME, 'w') as yaml_file:
|
||||
yaml.safe_dump(loaded_data, yaml_file, default_flow_style=False)
|
||||
|
||||
apply_cmd = KUBE_CMD + 'apply -f ' + TMP_FILENAME
|
||||
|
||||
sub = subprocess.Popen(apply_cmd, shell=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = sub.communicate()
|
||||
if sub.returncode != 0:
|
||||
LOG.error('Command failed:\n %s\n. %s\n%s\n' % (
|
||||
apply_cmd, stdout.decode('utf-8'), stderr.decode('utf-8')))
|
||||
raise Exception('Cannot apply change to certificate %s.'
|
||||
% certificate)
|
||||
else:
|
||||
os.remove(TMP_FILENAME)
|
||||
LOG.info('Updated subject entries for certificate: %s. '
|
||||
'Output:\n%s\n' % (certificate, stdout.decode('utf-8')))
|
||||
|
||||
|
||||
def reconfigure_certificates_subject():
|
||||
"""Reconfigure the subject for all desired certs
|
||||
"""
|
||||
certificate_short_name = {
|
||||
'system-restapi-gui-certificate': 'system-restapi-gui',
|
||||
'system-registry-local-certificate': 'system-registry-local',
|
||||
'system-openldap-local-certificate': 'system-openldap',
|
||||
}
|
||||
|
||||
cloud_role = get_distributed_cloud_role()
|
||||
for cert in certificate_short_name.keys():
|
||||
if (cert == 'system-openldap-local-certificate' and
|
||||
cloud_role == 'subcloud'):
|
||||
continue
|
||||
if certificate_exists(cert):
|
||||
update_certificate(cert, certificate_short_name[cert])
|
||||
|
||||
|
||||
def main():
|
||||
@ -67,14 +264,29 @@ def main():
|
||||
"action = %s"
|
||||
% (sys.argv[0], from_release, to_release, action))
|
||||
|
||||
mode = get_system_mode()
|
||||
|
||||
if mode == 'simplex':
|
||||
LOG.info("%s: System mode is %s. No actions required."
|
||||
% (sys.argv[0], mode))
|
||||
return 0
|
||||
|
||||
create_platform_certificates(to_release)
|
||||
for retry in range(0, RETRIES):
|
||||
try:
|
||||
reconfigure_certificates_subject()
|
||||
mode = get_system_mode()
|
||||
# For (legacy) SX upgrade, the role that creates the required
|
||||
# platform certificates is already executed by the upgrade
|
||||
# playbook.
|
||||
if mode != 'simplex':
|
||||
create_platform_certificates(to_release)
|
||||
LOG.info("Successfully created/updated required platform "
|
||||
"certificates.")
|
||||
except Exception as e:
|
||||
if retry == RETRIES - 1:
|
||||
LOG.error("Error updating required platform certificates. "
|
||||
"Please verify logs.")
|
||||
return 1
|
||||
else:
|
||||
LOG.exception(e)
|
||||
LOG.error("Exception ocurred during script execution, "
|
||||
"retrying after 5 seconds.")
|
||||
sleep(5)
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Loading…
Reference in New Issue
Block a user