From e6d29d951b402eb5b07a197f6629737fa74cab66 Mon Sep 17 00:00:00 2001 From: Andy Ning Date: Mon, 21 Nov 2022 14:20:08 -0500 Subject: [PATCH] Add new mode openldap_ca to certificate-install API This change added a new mode "openldap_ca" to certificate-install sysinv API. With this mode, an openldap CA certificate can be installed as an k8s secret. Note that using this new mode to install openldap CA cert is only supported in the API, it is not supported by the certificate-install CLI, (will return invalid mode), since openldap certs are managed internally. This commit also include an unit test for the new mode. Test Plan: PASS: Call the API to install openldap CA certificate as k8s secret. PASS: While the secret exists, call the API to install the CA cert again, verify the secret is updated. PASS: Run "system certificate-install -m openldap_ca ", verify "Warning: Invalid mode: openldap_ca" is returned. PASS: DC multi-nodes subcloud upgrade. Story: 2009834 Task: 46893 Signed-off-by: Andy Ning Change-Id: I47eba6353c8cb64b65b291acca9b9c96a7c0e466 --- .../cgtsclient/v1/certificate_shell.py | 4 +- .../sysinv/sysinv/sysinv/common/constants.py | 2 + .../sysinv/sysinv/sysinv/conductor/manager.py | 35 ++++++++++++ .../sysinv/tests/api/test_certificate.py | 53 +++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py index 6d034538b4..169e898c89 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/certificate_shell.py @@ -83,8 +83,8 @@ def do_certificate_install(cc, args): data = {'passphrase': args.passphrase, 'mode': args.mode} - if data['mode'] == 'openldap': - raise exc.CommandError('Warning: Invalid mode: openldap') + if data['mode'] in ['openldap', 'openldap_ca']: + raise exc.CommandError('Warning: Invalid mode: %s' % data['mode']) has_private_key = False try: diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index ae5ee52949..d1f81f4f74 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -1553,12 +1553,14 @@ CERT_MODE_DOCKER_REGISTRY = 'docker_registry' CERT_MODE_OPENSTACK = 'openstack' CERT_MODE_OPENSTACK_CA = 'openstack_ca' CERT_MODE_OPENLDAP = 'openldap' +CERT_MODE_OPENLDAP_CA = 'openldap_ca' CERT_MODES_SUPPORTED = [CERT_MODE_SSL, CERT_MODE_SSL_CA, CERT_MODE_DOCKER_REGISTRY, CERT_MODE_OPENSTACK, CERT_MODE_OPENSTACK_CA, CERT_MODE_OPENLDAP, + CERT_MODE_OPENLDAP_CA, ] CERT_MODES_SUPPORTED_CERT_MANAGER = [CERT_MODE_SSL, CERT_MODE_DOCKER_REGISTRY, diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index e242863176..bdb17a158f 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -13213,6 +13213,41 @@ class ConductorManager(service.PeriodicService): config_uuid, config_dict, force=True) + # Special mode for openldap CA certificate. + # This CA certificate will be stored in k8s as an opaque secret + elif mode == constants.CERT_MODE_OPENLDAP_CA: + kube_operator = kubernetes.KubeOperator() + public_bytes = self._get_public_bytes(cert_list) + cert_secret = base64.encode_as_text(public_bytes) + + body = { + 'apiVersion': 'v1', + 'type': 'Opaque', + 'kind': 'Secret', + 'metadata': { + 'name': constants.OPENLDAP_CA_CERT_SECRET_NAME, + 'namespace': constants.CERT_NAMESPACE_PLATFORM_CA_CERTS + }, + 'data': { + 'ca.crt': cert_secret, + } + } + + try: + secret = kube_operator.kube_get_secret( + constants.OPENLDAP_CA_CERT_SECRET_NAME, + constants.CERT_NAMESPACE_PLATFORM_CA_CERTS) + if secret is not None: + kube_operator.kube_delete_secret( + constants.OPENLDAP_CA_CERT_SECRET_NAME, + constants.CERT_NAMESPACE_PLATFORM_CA_CERTS) + kube_operator.kube_create_secret( + constants.CERT_NAMESPACE_PLATFORM_CA_CERTS, body) + except Exception as e: + msg = "Failed to store openldap CA in k8s secret: %s" % str(e) + LOG.error(msg) + return msg + elif mode == constants.CERT_MODE_DOCKER_REGISTRY: LOG.info("Docker registry certificate install") # docker registry requires a PKCS1 key for the token server diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_certificate.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_certificate.py index 2e47b30eb8..6d91f59014 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_certificate.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_certificate.py @@ -625,6 +625,59 @@ class ApiCertificatePostTestSuite(ApiCertificateTestCaseMixin, resp = json.loads(response.body) self.assertIn('certificates', resp) + # Test install an openldap_ca certificate + def test_install_openldap_ca_certificate(self): + mode = 'openldap_ca' + certfile = os.path.join(os.path.dirname(__file__), "data", + 'ca-cert-one-cert.pem') + + in_certs = self.extract_certs_from_pem_file(certfile) + fake_config_certificate_return = [] + for index, in_cert in enumerate(in_certs): + fake_config_certificate_return.append( + {'signature': self.get_cert_signature(mode, in_cert), + 'not_valid_before': in_cert.not_valid_before, + 'not_valid_after': in_cert.not_valid_after}) + self.fake_conductor_api.\ + setup_config_certificate(fake_config_certificate_return) + + data = {'mode': mode} + files = [('file', certfile)] + response = self.post_with_files('%s/%s' % (self.API_PREFIX, 'certificate_install'), + data, + upload_files=files, + headers=self.API_HEADERS, + expect_errors=False) + + self.assertEqual(response.status_code, http_client.OK) + resp = json.loads(response.body) + self.assertIn('certificates', resp) + ret_certs = resp.get('certificates') + + self.assertEqual(len(ret_certs), 1) + ret_cert = ret_certs[0] + in_cert = in_certs[0] + + self.assertIn('certtype', ret_cert) + self.assertEqual(ret_cert.get('certtype'), mode) + self.assertIn('signature', ret_cert) + self.assertIn('start_date', ret_cert) + self.assertIn('expiry_date', ret_cert) + + ret_cert_start_date = str(ret_cert.get('start_date')) + ret_cert_start_date = ret_cert_start_date.replace('+00:00', '') + ret_cert_expiry_date = str(ret_cert.get('expiry_date')) + ret_cert_expiry_date = ret_cert_expiry_date.replace('+00:00', '') + found_match = False + if ret_cert.get('signature') == \ + self.get_cert_signature(mode, in_cert) and \ + ret_cert_start_date == \ + str(in_cert.not_valid_before) and \ + ret_cert_expiry_date == \ + str(in_cert.not_valid_after): + found_match = True + self.assertTrue(found_match) + class ApiCertificateDeleteTestSuite(ApiCertificateTestCaseMixin, base.FunctionalTest):