From: Redouane Kachach Date: Tue, 11 Mar 2025 09:34:12 +0000 (+0100) Subject: mgr/cephadm: adding UT and adjusting existing unit-tests X-Git-Tag: testing/wip-pdonnell-testing-20250312.174620-debug~10^2~15 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=6ce19fab00231061f5e82c1739048af480fe7787;p=ceph-ci.git mgr/cephadm: adding UT and adjusting existing unit-tests Signed-off-by: Redouane Kachach --- diff --git a/src/pybind/mgr/cephadm/tests/test_cephadm.py b/src/pybind/mgr/cephadm/tests/test_cephadm.py index 77615bd055a..24dc5647bb8 100644 --- a/src/pybind/mgr/cephadm/tests/test_cephadm.py +++ b/src/pybind/mgr/cephadm/tests/test_cephadm.py @@ -11,10 +11,6 @@ from cephadm.serve import CephadmServe from cephadm.inventory import ( HostCacheStatus, ClientKeyringSpec, - Cert, - PrivKey, - CERT_STORE_CERT_PREFIX, - CERT_STORE_KEY_PREFIX, SpecDescription, ) from cephadm.services.osd import OSD, OSDRemovalQueue, OsdIdClaims @@ -1743,207 +1739,6 @@ class TestCephadm(object): assert cephadm_module.cache._get_host_cache_entry_status( 'host.nothing.com') == HostCacheStatus.stray - @mock.patch("cephadm.module.CephadmOrchestrator.set_store") - def test_cert_store_save_cert(self, _set_store, cephadm_module: CephadmOrchestrator): - cephadm_module.cert_key_store._init_known_cert_key_dicts() - - rgw_frontend_rgw_foo_host2_cert = 'fake-rgw-cert' - nvmeof_client_cert = 'fake-nvmeof-client-cert' - nvmeof_server_cert = 'fake-nvmeof-server-cert' - nvmeof_root_ca_cert = 'fake-nvmeof-root-ca-cert' - grafana_cert_host_1 = 'grafana-cert-host-1' - grafana_cert_host_2 = 'grafana-cert-host-2' - cephadm_module.cert_key_store.save_cert('rgw_frontend_ssl_cert', rgw_frontend_rgw_foo_host2_cert, service_name='rgw.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('nvmeof_server_cert', nvmeof_server_cert, service_name='nvmeof.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('nvmeof_client_cert', nvmeof_client_cert, service_name='nvmeof.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('nvmeof_root_ca_cert', nvmeof_root_ca_cert, service_name='nvmeof.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('grafana_cert', grafana_cert_host_1, host='host-1', user_made=True) - cephadm_module.cert_key_store.save_cert('grafana_cert', grafana_cert_host_2, host='host-2', user_made=True) - - expected_calls = [ - mock.call(f'{CERT_STORE_CERT_PREFIX}rgw_frontend_ssl_cert', json.dumps({'rgw.foo': Cert(rgw_frontend_rgw_foo_host2_cert, True).to_json()})), - mock.call(f'{CERT_STORE_CERT_PREFIX}nvmeof_server_cert', json.dumps({'nvmeof.foo': Cert(nvmeof_server_cert, True).to_json()})), - mock.call(f'{CERT_STORE_CERT_PREFIX}nvmeof_client_cert', json.dumps({'nvmeof.foo': Cert(nvmeof_client_cert, True).to_json()})), - mock.call(f'{CERT_STORE_CERT_PREFIX}nvmeof_root_ca_cert', json.dumps({'nvmeof.foo': Cert(nvmeof_root_ca_cert, True).to_json()})), - mock.call(f'{CERT_STORE_CERT_PREFIX}grafana_cert', json.dumps({'host-1': Cert(grafana_cert_host_1, True).to_json()})), - mock.call(f'{CERT_STORE_CERT_PREFIX}grafana_cert', json.dumps({'host-1': Cert(grafana_cert_host_1, True).to_json(), - 'host-2': Cert(grafana_cert_host_2, True).to_json()})) - ] - _set_store.assert_has_calls(expected_calls) - - @mock.patch("cephadm.module.CephadmOrchestrator.set_store") - def test_cert_store_cert_ls(self, _set_store, cephadm_module: CephadmOrchestrator): - cephadm_module.cert_key_store._init_known_cert_key_dicts() - - expected_ls = { - 'rgw_frontend_ssl_cert': False, - 'iscsi_ssl_cert': False, - 'ingress_ssl_cert': False, - 'mgmt_gw_cert': False, - 'oauth2_proxy_cert': False, - 'cephadm_root_ca_cert': False, - 'grafana_cert': False, - 'nvmeof_client_cert': False, - 'nvmeof_server_cert': False, - 'nvmeof_root_ca_cert': False, - } - assert cephadm_module.cert_key_store.cert_ls() == expected_ls - - cephadm_module.cert_key_store.save_cert('rgw_frontend_ssl_cert', 'xxx', service_name='rgw.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('rgw_frontend_ssl_cert', 'xxx', service_name='rgw.bar', user_made=True) - expected_ls['rgw_frontend_ssl_cert'] = {} - expected_ls['rgw_frontend_ssl_cert']['rgw.foo'] = True - expected_ls['rgw_frontend_ssl_cert']['rgw.bar'] = True - assert cephadm_module.cert_key_store.cert_ls() == expected_ls - - cephadm_module.cert_key_store.save_cert('nvmeof_client_cert', 'xxx', service_name='nvmeof.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('nvmeof_server_cert', 'xxx', service_name='nvmeof.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('nvmeof_root_ca_cert', 'xxx', service_name='nvmeof.foo', user_made=True) - expected_ls['nvmeof_client_cert'] = {} - expected_ls['nvmeof_client_cert']['nvmeof.foo'] = True - expected_ls['nvmeof_server_cert'] = {} - expected_ls['nvmeof_server_cert']['nvmeof.foo'] = True - expected_ls['nvmeof_root_ca_cert'] = {} - expected_ls['nvmeof_root_ca_cert']['nvmeof.foo'] = True - assert cephadm_module.cert_key_store.cert_ls() == expected_ls - - @mock.patch("cephadm.module.CephadmOrchestrator.set_store") - def test_cert_store_save_key(self, _set_store, cephadm_module: CephadmOrchestrator): - cephadm_module.cert_key_store._init_known_cert_key_dicts() - - grafana_host1_key = 'fake-grafana-host1-key' - grafana_host2_key = 'fake-grafana-host2-key' - nvmeof_client_key = 'nvmeof-client-key' - nvmeof_server_key = 'nvmeof-server-key' - nvmeof_encryption_key = 'nvmeof-encryption-key' - cephadm_module.cert_key_store.save_key('grafana_key', grafana_host1_key, host='host1') - cephadm_module.cert_key_store.save_key('grafana_key', grafana_host2_key, host='host2') - cephadm_module.cert_key_store.save_key('nvmeof_client_key', nvmeof_client_key, service_name='nvmeof.foo') - cephadm_module.cert_key_store.save_key('nvmeof_server_key', nvmeof_server_key, service_name='nvmeof.foo') - cephadm_module.cert_key_store.save_key('nvmeof_encryption_key', nvmeof_encryption_key, service_name='nvmeof.foo') - - expected_calls = [ - mock.call(f'{CERT_STORE_KEY_PREFIX}grafana_key', json.dumps({'host1': PrivKey(grafana_host1_key).to_json()})), - mock.call(f'{CERT_STORE_KEY_PREFIX}grafana_key', json.dumps({'host1': PrivKey(grafana_host1_key).to_json(), - 'host2': PrivKey(grafana_host2_key).to_json()})), - mock.call(f'{CERT_STORE_KEY_PREFIX}nvmeof_client_key', json.dumps({'nvmeof.foo': PrivKey(nvmeof_client_key).to_json()})), - mock.call(f'{CERT_STORE_KEY_PREFIX}nvmeof_server_key', json.dumps({'nvmeof.foo': PrivKey(nvmeof_server_key).to_json()})), - mock.call(f'{CERT_STORE_KEY_PREFIX}nvmeof_encryption_key', json.dumps({'nvmeof.foo': PrivKey(nvmeof_encryption_key).to_json()})), - ] - _set_store.assert_has_calls(expected_calls) - - @mock.patch("cephadm.module.CephadmOrchestrator.set_store") - def test_cert_store_key_ls(self, _set_store, cephadm_module: CephadmOrchestrator): - cephadm_module.cert_key_store._init_known_cert_key_dicts() - - expected_ls = { - 'grafana_key': False, - 'mgmt_gw_key': False, - 'oauth2_proxy_key': False, - 'cephadm_root_ca_key': False, - 'iscsi_ssl_key': False, - 'ingress_ssl_key': False, - 'nvmeof_client_key': False, - 'nvmeof_server_key': False, - 'nvmeof_encryption_key': False, - } - assert cephadm_module.cert_key_store.key_ls() == expected_ls - - cephadm_module.cert_key_store.save_key('nvmeof_client_key', 'xxx', service_name='nvmeof.foo') - cephadm_module.cert_key_store.save_key('nvmeof_server_key', 'xxx', service_name='nvmeof.foo') - cephadm_module.cert_key_store.save_key('nvmeof_encryption_key', 'xxx', service_name='nvmeof.foo') - expected_ls['nvmeof_server_key'] = {} - expected_ls['nvmeof_server_key']['nvmeof.foo'] = True - expected_ls['nvmeof_client_key'] = {} - expected_ls['nvmeof_client_key']['nvmeof.foo'] = True - expected_ls['nvmeof_encryption_key'] = {} - expected_ls['nvmeof_encryption_key']['nvmeof.foo'] = True - assert cephadm_module.cert_key_store.key_ls() == expected_ls - - @mock.patch("cephadm.module.CephadmOrchestrator.get_store_prefix") - def test_cert_store_load(self, _get_store_prefix, cephadm_module: CephadmOrchestrator): - cephadm_module.cert_key_store._init_known_cert_key_dicts() - - rgw_frontend_rgw_foo_host2_cert = 'fake-rgw-cert' - grafana_host1_key = 'fake-grafana-host1-cert' - nvmeof_server_cert = 'nvmeof-server-cert' - nvmeof_client_cert = 'nvmeof-client-cert' - nvmeof_root_ca_cert = 'nvmeof-root-ca-cert' - nvmeof_server_key = 'nvmeof-server-key' - nvmeof_client_key = 'nvmeof-client-key' - nvmeof_encryption_key = 'nvmeof-encryption-key' - - def _fake_prefix_store(key): - if key == 'cert_store.cert.': - return { - f'{CERT_STORE_CERT_PREFIX}rgw_frontend_ssl_cert': json.dumps({'rgw.foo': Cert(rgw_frontend_rgw_foo_host2_cert, True).to_json()}), - f'{CERT_STORE_CERT_PREFIX}nvmeof_server_cert': json.dumps({'nvmeof.foo': Cert(nvmeof_server_cert, True).to_json()}), - f'{CERT_STORE_CERT_PREFIX}nvmeof_client_cert': json.dumps({'nvmeof.foo': Cert(nvmeof_client_cert, True).to_json()}), - f'{CERT_STORE_CERT_PREFIX}nvmeof_root_ca_cert': json.dumps({'nvmeof.foo': Cert(nvmeof_root_ca_cert, True).to_json()}), - } - elif key == 'cert_store.key.': - return { - f'{CERT_STORE_KEY_PREFIX}grafana_key': json.dumps({'host1': PrivKey(grafana_host1_key).to_json()}), - f'{CERT_STORE_KEY_PREFIX}nvmeof_server_key': json.dumps({'nvmeof.foo': PrivKey(nvmeof_server_key).to_json()}), - f'{CERT_STORE_KEY_PREFIX}nvmeof_client_key': json.dumps({'nvmeof.foo': PrivKey(nvmeof_client_key).to_json()}), - f'{CERT_STORE_KEY_PREFIX}nvmeof_encryption_key': json.dumps({'nvmeof.foo': PrivKey(nvmeof_encryption_key).to_json()}), - } - else: - raise Exception(f'Get store with unexpected value {key}') - - _get_store_prefix.side_effect = _fake_prefix_store - cephadm_module.cert_key_store.load() - assert cephadm_module.cert_key_store.known_certs['rgw_frontend_ssl_cert']['rgw.foo'] == Cert(rgw_frontend_rgw_foo_host2_cert, True) - assert cephadm_module.cert_key_store.known_certs['nvmeof_server_cert']['nvmeof.foo'] == Cert(nvmeof_server_cert, True) - assert cephadm_module.cert_key_store.known_certs['nvmeof_client_cert']['nvmeof.foo'] == Cert(nvmeof_client_cert, True) - assert cephadm_module.cert_key_store.known_certs['nvmeof_root_ca_cert']['nvmeof.foo'] == Cert(nvmeof_root_ca_cert, True) - assert cephadm_module.cert_key_store.known_keys['grafana_key']['host1'] == PrivKey(grafana_host1_key) - assert cephadm_module.cert_key_store.known_keys['nvmeof_server_key']['nvmeof.foo'] == PrivKey(nvmeof_server_key) - assert cephadm_module.cert_key_store.known_keys['nvmeof_client_key']['nvmeof.foo'] == PrivKey(nvmeof_client_key) - assert cephadm_module.cert_key_store.known_keys['nvmeof_encryption_key']['nvmeof.foo'] == PrivKey(nvmeof_encryption_key) - - def test_cert_store_get_cert_key(self, cephadm_module: CephadmOrchestrator): - cephadm_module.cert_key_store._init_known_cert_key_dicts() - - rgw_frontend_rgw_foo_host2_cert = 'fake-rgw-cert' - nvmeof_client_cert = 'fake-nvmeof-client-cert' - nvmeof_server_cert = 'fake-nvmeof-server-cert' - cephadm_module.cert_key_store.save_cert('rgw_frontend_ssl_cert', rgw_frontend_rgw_foo_host2_cert, service_name='rgw.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('nvmeof_server_cert', nvmeof_server_cert, service_name='nvmeof.foo', user_made=True) - cephadm_module.cert_key_store.save_cert('nvmeof_client_cert', nvmeof_client_cert, service_name='nvmeof.foo', user_made=True) - - assert cephadm_module.cert_key_store.get_cert('rgw_frontend_ssl_cert', service_name='rgw.foo') == rgw_frontend_rgw_foo_host2_cert - assert cephadm_module.cert_key_store.get_cert('nvmeof_server_cert', service_name='nvmeof.foo') == nvmeof_server_cert - assert cephadm_module.cert_key_store.get_cert('nvmeof_client_cert', service_name='nvmeof.foo') == nvmeof_client_cert - assert cephadm_module.cert_key_store.get_cert('grafana_cert', host='host1') == '' - assert cephadm_module.cert_key_store.get_cert('iscsi_ssl_cert', service_name='iscsi.foo') == '' - assert cephadm_module.cert_key_store.get_cert('nvmeof_root_ca_cert', service_name='nvmeof.foo') == '' - - with pytest.raises(OrchestratorError, match='Attempted to access cert for unknown entity'): - cephadm_module.cert_key_store.get_cert('unknown_entity') - with pytest.raises(OrchestratorError, match='Need host to access cert for entity'): - cephadm_module.cert_key_store.get_cert('grafana_cert') - with pytest.raises(OrchestratorError, match='Need service name to access cert for entity'): - cephadm_module.cert_key_store.get_cert('rgw_frontend_ssl_cert', host='foo') - - grafana_host1_key = 'fake-grafana-host1-cert' - nvmeof_server_key = 'nvmeof-server-key' - nvmeof_encryption_key = 'nvmeof-encryption-key' - cephadm_module.cert_key_store.save_key('grafana_key', grafana_host1_key, host='host1') - cephadm_module.cert_key_store.save_key('grafana_key', grafana_host1_key, host='host1') - cephadm_module.cert_key_store.save_key('nvmeof_server_key', nvmeof_server_key, service_name='nvmeof.foo') - cephadm_module.cert_key_store.save_key('nvmeof_encryption_key', nvmeof_encryption_key, service_name='nvmeof.foo') - - assert cephadm_module.cert_key_store.get_key('grafana_key', host='host1') == grafana_host1_key - assert cephadm_module.cert_key_store.get_key('nvmeof_server_key', service_name='nvmeof.foo') == nvmeof_server_key - assert cephadm_module.cert_key_store.get_key('nvmeof_client_key', service_name='nvmeof.foo') == '' - assert cephadm_module.cert_key_store.get_key('nvmeof_encryption_key', service_name='nvmeof.foo') == nvmeof_encryption_key - - with pytest.raises(OrchestratorError, match='Attempted to access priv key for unknown entity'): - cephadm_module.cert_key_store.get_key('unknown_entity') - with pytest.raises(OrchestratorError, match='Need host to access priv key for entity'): - cephadm_module.cert_key_store.get_key('grafana_key') - @mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) @mock.patch("cephadm.services.nfs.NFSService.run_grace_tool", mock.MagicMock()) @mock.patch("cephadm.services.nfs.NFSService.purge", mock.MagicMock()) diff --git a/src/pybind/mgr/cephadm/tests/test_certmgr.py b/src/pybind/mgr/cephadm/tests/test_certmgr.py new file mode 100644 index 00000000000..fe54d74f327 --- /dev/null +++ b/src/pybind/mgr/cephadm/tests/test_certmgr.py @@ -0,0 +1,855 @@ +import unittest +import re +import pytest +import json +from tests import mock + +from cephadm.tlsobject_types import Cert, PrivKey, TLSObjectException, TLSObjectProtocol +from cephadm.tlsobject_store import TLSOBJECT_STORE_PREFIX, TLSObjectStore, TLSObjectScope +from cephadm.module import CephadmOrchestrator +from cephadm.cert_mgr import CertInfo, CertMgr + +EXPIRED_CERT = """ +-----BEGIN CERTIFICATE----- +MIIFZTCCA02gAwIBAgIUBaoYVIPd+asHvOjo8caYaiipytowDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMzEyMjQwNzE1NDdaFw0yMzEyMzAw +NzE1NDdaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQC6l2Og/7iE5MCSAgRhBE572eHMckaaLSLhkuuuXvCO6BSUfkxq +7z1xSPu4Hj2iSeSLEo+QG1H6Kh1WOT4x604MNTwtVQb32sXqPFhk7KYLKrC4kfIa +GjfNE2YMtxrJerOiYUZuoxSOkEd/iaIKzNLcAh18gzaUl9YgMEx5UG6a5uZaHpic +SrX3g1jqhQb1EvUZId7vbeN0z+eAaw2lxKeHro02ohDHMZofpoa1q8v1saOYQdIv +g5CgOgNsVeAAKDQMFgWVmSoSf9ds7rV2WJ1heiSPvKlv8Y1OMfuPAfhsPEcyfcxA +Bx1osRFYkTm3TV9m8s8uzmQkMJJO4JOuyQNadchZgqglljavQmBQcl3Euo3C6ptQ +E0ZR9TWznJRasJI51P+f/lihZyuKhFv3TpVqkjthmGFp6PF+xbIXDA22MXD7BO1u +8rwDuPnrNorqrGd/xTM8cmo4K5pDFkmvLDqPi+MtCpQ0Sv0Koenhm7vvmJNaHDga +mSkSAxAooF84xJcCthyWu+wkHa/fKavLCMgi6cYRZ6F8LzYIVs0FJx1SgXsgt6lk +qu25MwzxHnjlutm/cEd3NI4p5hDWH1EKiE6xigJokaTdsB9iFdyMzlYe0Pf/QXzT +/zxhEkyl3EwskZF2/AsxOrzu/6gKOCBC+XW5FtEKw2RKuz+b1RfPyoVM4wIDAQAB +o1MwUTAdBgNVHQ4EFgQUF0pnn/69WPD8vEFoK/+ryRnR/3QwHwYDVR0jBBgwFoAU +F0pnn/69WPD8vEFoK/+ryRnR/3QwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAgEAFwvA0iHX8sBrF2G/B1fJ7yx9o0ar2sM27TIwlU7hGAFa9db1YSCe +984cgZVbK1B29cP6jslY9Ev6NcF2z5V9312CjXHBcKuRs30Z6kruGrm0/LaNVJQl +VrKh+whshdd1yR0HypCIEBhOh2xTagnCnc2vBAj0XiV9vGTaCyycxlH1YNvpLusP +5H1Iw6rIzJR18i5pFAY6WaqJfbjZo/jSPEewV+HpxGR6UvLOcrBXNiI/7BZllRE5 +ro2PxY/cMlyaeT2arJg4Vduebp4Bn9AHaOa04VSKUP/uGJbWKGearIFVcneeEtzK +rRiv1zsBC1wuAq5qfxW8/O4MRIWBpPimmDalePwVU2Ob3ddTv7pCNnEAqSsUdrC8 +R7V/0CyemGh5tLeNuVfaz2TOsJIuRZgyxXW1Mk7bgo6whS5w35goDzUSyu3sdLyW +LxpCRzmRFAcSxdEY5FyOC7hbwzbS+onoB5RFfxRSTps0IYYuGl9t2mHTyEQlwWjl +gyWZ5MjyFPHpjar/VnW7fQaKdEzG4PGYEU3H8IwnazdXEF7AbvRXFMt8uk+12wFA +CCycPdcObZZJtfaL0NyGQ/gXP/RptOEo8zfzj5z/1TdHwcQmBW4ctrACbbdPjYJ6 +w/0XhqnUQ6AL4Kc3tFaICvFpks1snRXdhbgwAzltREEyAHs050k+1HU= +-----END CERTIFICATE----- +""" + + +INVALID_CERT = """ +-----BEGIN CERTIFICATE----- +INVALIDCERTDATA +-----END CERTIFICATE----- +""" + +NON_MATCHING_KEY = """ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5xpfgFsX7I19H +GW2YE6vz0TNni2dM1ItQoP0WaX55bNEwLsj9hHTZ7vgTH6ZkaNp0U73Mq+0tM8UP +RrNFBKhy5cE/D+l7aV5KUr4mgPK6Tgrgk0iS83nymladgSKRjN75HH8SMg2lLVoi +vfrAAMh58JA2zFUFZaZQnD1eL/+waht9qpCdilsY3MVKuElZ3ndxSaTuISLhPS8G +O7jkCbCThfkrnk5IeCd5trN8ho55Ev5U5AxgbUgHlJxzUr9wLTzKW0x9D5qbLTva +C9VsUN+SdQW01pTs4MLPuKsnjLGaG91sEbZln4Ub7bXvNey9z0heGE/NJX+Q5Ekk +hFV5TLvZAgMBAAECggEACCGMWi871/X3YJn9mdiISSjsLcS7OEwTgOt/fyd7vhCD +7IoY0j6lwqXazzN3ksgRONAzNOTPyyH5XZyD207DmT4XHVbFGFmQbILsmtDSTuTq +IK1WLSBhjHJW4irHerKGcrNdmHC101MYH0lxHATRU8PW/Ay7c1cqVoCZRnHvFgLQ +YZHxhskDnMTaXX0lw+CCq7ajUg2Su2u7tC7LiG/n4cjBNTblB7vmyAiFo1xoYqam +GuwtkLGZW1RxvCi13HGIKAU9VnwKOyzhJp9ZBcx1Xshiaqazwhpf8PhP8mT2kLFg +ti5NVxadbD78VGMC5bfH6lZdm4/MLlaqMejb6QXCRQKBgQDcd72c4FJpXpXWMR6g +ROw60tn6qjSpH0YJ96bf19UGgNcYVUCiZrgG7ENx6SabjUJwqxi3qCxneD+J7caL +Befd2Can4vf6U3o3DV/a86Dz6Qd4n7n6MU39aOg2jsCriknfOUkWfnGgvMaPzduU +O1rFF0xpezIQkU3HjaN4aLGSswKBgQDXt3/EsRIk8xYQvcUTaWAQdaxtRewS9Tc2 +m6MdU6der8C6fTydggUBdkURawFehdpNmKiymBJJFniCs/EuGmKKHjupW04Kmwin +isaA+tSwLQ01tL1G7xhydb85sbfBXzel4fztmk2OB+IpB4rvTFlP8t2z/bQQumjN +WPLUwz7NQwKBgFZ4AD5PHQOGvW3Mxh5F6gEIQcY2i4Dpaybtot2YYUyzq6k3hqor +b3IHqEw9DY9kz/IwqPkfVIsgdos6XuyX3GD+Lesa8feUVhLRhA70DuSbOPruapre +S6BgTPNY+ehNzLtoVGomHZrVb2tnaf+xZ+B1Str0Hqaw1ri1rK/FICBRAoGBALbn +T95mhQvvUPZA8ajT4DAUlm7QqqooYPhcXqGvHGqcer2lEpA6fiQPM+Dg6fhLZh4F +IoTLjDWMaAHqsMR2erbBi7S9Rh6X9W6ZrFYQV+ZJTLoM1bAfaosia1Fv7m53Xae5 +Rcvw2XFkHc7MJnFgOxoewvyqUNMeO15h3QOpyMYhAoGABm6bQcIdmv3e+GVoraXA +lsmM4/lRi/HmRHGtQ7kjKvT09YBQ3/qm04QwvwQtik7ws7t8VODQSgZC6re0TU7Y +RPw+RGrt0nnmMUP2jJ6SKPCXmw55tW7FcvBJeAM4komEUoLrnKfwkaRy8SKSt8a0 +HlBxebJND7cfu20WpwErmhU= +-----END PRIVATE KEY----- +""" + +MATCHING_KEY = """ +-----BEGIN PRIVATE KEY----- +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 +ofQkxELjoeKZpTzmXhngzU5ltvvP +-----END PRIVATE KEY----- +""" + +CEPHADM_SELF_GENERATED_CERT_1 = """ +-----BEGIN CERTIFICATE----- +MIIFFzCCAv+gAwIBAgIUHOJMW2YGSTs30hRXi8OhP9TFzuUwDQYJKoZIhvcNAQEL +BQAwJjENMAsGA1UECgwEQ2VwaDEVMBMGA1UEAwwMY2VwaGFkbS1yb290MB4XDTI1 +MDEyMTE2MDY1NloXDTI4MDEyMTE2MDY1NlowGjEYMBYGA1UEAwwPMTkyLjE2OC4x +MDAuMTAwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAj9WNrDts3RUu +8ikG2Qdi5YL2IX1P/9PLXIGM6VBK/DVz4Oh13YBAH54gzJGeBjvZxwDpATgUQuuu +AgREE+3uigzfq6sS31Ru+Gx5WU5LhrHHKOGkofD2Xa04mC0nvcXuOWanItfy1Dmc +MKcl9QywIHp+1qQw0ammYjpvqvRgBIZgg2qqa2rpu+zVHC4omJHaNxAruE6wPTpL +RF5oAIXyZ/0+cyC7YgVqxBOaVQZuH/D36aSSXN/S5ZuNiMUo5Y01hfz989T2iVw6 +2KROt6FmBG55EAGNYw7MDtjaBBdkG9zuA7TkdqvgEE2ty7p035rllmu6CxuiToHR +C0w20FrYDkudhHaANB2EQspEE2JhiBnHk8OGhm09mtau9IohQCBGxkz9LJ27IeVK +rs9ElECtn9Ql1a5K6MTxH/dCGKAsH7/0Qg7OHH+2pS7TZ1yd9PhGYdR9ybephxth +288bki1cyg5bXTnhUKBSqORV+26b/dWx3dZ9v7N+oRXmoq6FyOxBP/4DWNAFPUax +gmTGrLM4pa/8IlLkDvn4poyQ4aacjk978LVBJoJlFo4r47gEl3I2WNiTU0D05oXP +aje0xXlgYFOfhZXHBfiny9hPJvpfjIF4MoQJJItfrLhrq5RZpY2QTETOj2A2lzki +wlLZ4D7YwQd0p0xuXDbVPmhCsWmF1C8CAwEAAaNJMEcwNwYDVR0RBDAwLoIVY2Vw +aC1ub2RlLTAuY2VwaC1vcmNogg9ncmFmYW5hX3NlcnZlcnOHBMCoZGQwDAYDVR0T +AQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEARJgeYvvbuZkf+h6GGXXRVBmdY6FE +Jt+IHjtLJSmJZyqxNatAPXWHTydnwjN45fOMr2DS4r2Ll8LaUGS0fs+DBUJLSQIN +KDzqj1tnSMMZiHrOAO52m81lz6DE1c9BsOfdc82xCxbFaWqU+5KWtacLMSFAB7oV +ZezjA/G4LkUeNszv2YXw65nj7XKZbo+nn7p802aalbTxyiOpsYd9sa0FGhpczfkf +CZDZfTOhWnmn4yxHhcXI4ndIj6L3njki7/fyljROZjPZU4I3cPvPB2ihhm9H4RiC +A4XtWdZZH1uM+saneO1TGKKBYtEfR5YUnatutHhJ4V6jVUGuRiz6FL+a921dk/IU +kyiQL7MfoeVpoTYCsWM38UL/PeeaU1j47wYHBZJm65tjBsFzpqe/ljYiUHVEJdqn +Didgf03tKBS8KEUAT42EheFDnkEOk4uC16CuTmvREXSWHCfesSIdGH8tIlS6OE6Y +pAVcOecURaqpvdNNGD1tg5qKoO6Fj/In9xMfBD4vct70GIK6n1DsvZDGc29D7TBq +cMKK0xnnIgKE1wea9mNEi3CayN2zwNi4zpINVeJDqJG/fRcYCqVkG7iQFObh63wq +R0AaiVaSDXmCtB0pchTNUEOiGXlO0HUrC4YFVbRvzJRT/d3C5wG76QXbLjSfAzeS +3+7M6o3tu4UActk= +-----END CERTIFICATE----- +""" + +CEPHADM_SELF_GENERATED_CERT_2 = """ +-----BEGIN CERTIFICATE----- +MIIFFzCCAv+gAwIBAgIUX4PkdhdD70oeykAelVSyhL6AdsUwDQYJKoZIhvcNAQEL +BQAwJjENMAsGA1UECgwEQ2VwaDEVMBMGA1UEAwwMY2VwaGFkbS1yb290MB4XDTI1 +MDEyNDEyMjEyMloXDTI4MDEyNDEyMjEyMlowGjEYMBYGA1UEAwwPMTkyLjE2OC4x +MDAuMTAyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxiIbWtQf48Cd +UuSFA7PWo3aYmHBkfFluxTi9u0flAaTgA2057DZOOxn3xkXATUFpcsKzGdsHZvR1 +C0q0qX38Bsp+21Ctei4vsEVyZA86msxd9AvhLQF5VTyyo0Wc7702qb5dOOp9peXk +1OS+FA46Y2+J9HsF2Y5K3OPPeYx+D1hTY8tUZaus1Zpi72w/zq+zdrRa2pB2Hc9O +BSNOg+ZoU8vZm3LyQK7p5bjCa0llfFD5WIRjJsuQZ+s56ELSTg7asqT3VaiuskrI +xe+atQ+lZj/XJw3TLxoWwYAm9mRtbiOlNL2LsyZVKHlz5Dw1y3RXNbnIZWwZusUn +7aRO3kVj8ZIS+Yhz+TcLqP1p3y9mjmzywZWXrgfiRsCCUIduIczqQbCIeev7JX8D +e7QRQYmVGxEJGJZHmiNvaHUvuReHjbSo4OfogH7GZKX6TBc8pJ2Qfz8ciUaUo6Gi +yf6UdkC7KIMvtYR/YtyuJPH89yFpNi8J/DUvSJYUospBUoQE3S6TF6dIN86NJLDX +7ab+/CxZaePn7XLxxwY1uLjrxQ8J5dxVOU5twS70CK6pfOaK5XfPyUT48jXfHmTd +YLwdstMg/eN/xldAawoqWJP+u3jsQ9eNr28lzMw3enufRdaNoHC1C6ox5tqxYht0 +AdcsImFW17FjluN3NnM94DBMud4qK30CAwEAAaNJMEcwNwYDVR0RBDAwLoIVY2Vw +aC1ub2RlLTIuY2VwaC1vcmNogg9ncmFmYW5hX3NlcnZlcnOHBMCoZGYwDAYDVR0T +AQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAXMSDznkjm7LvNqDnvOWYOSWd/mOT +X/BkfdpYc9QmEKJzaIuTaWMyMW4SQt0kZSHG7//1JSF0lBEK882QEkdJ7CUachu4 +fcGlNI7zcmOYXVGEW5yBxiJFjeGNZNkkL8uE4uOg1rANf1oWalTNlEWWUNDE1eds +sGcpph1AZMvHQsFhC8q9jyqMkUSyzegK6WYkXrxl2v8mYOKGmQudvblc+mztSHQp +0XTWlowL5dzMHDjCdlFJVfbDkk73acwTTwODizWXa0+EU6JgfViMyypypW/C4/Gl +Z4kAzt8PeVkSQCA30s+63ApMAMgpuItyziBORfv3btleX4gC6zaMtGJQLQrZot69 +/fCha1jzxsKbEpZ4/7E4GluSRGA3XshPqzPROITYXWjTuR0J1P8v9kzFrlj17bbj +FSY5HAaKptLHZM8h/g+vTqnAFRIrbKoTnOsnzGtZ9UrIT4BxhNZo3m/8skEXbpau +IfADqfsEQkm09WC1hUn+cSp2AIveqkTQnivL+N7vkiv7tFEeACKEYe3rXUuTM2fq +cYVn8aLcRMIDqOR09aOnLDkqfbnoQEeIALnEZP2sn58hmbP7gfjQjRF/+J2evn7l +MqnoatrcAJsFLNw0hvbQNNBDXzjn4tZ0GQondRp0M2Eqd0qOxQYbrfTe8Zj3g49E +YHhohGcnp4nmo1g= +-----END CERTIFICATE----- +""" + +CEPHADM_SELF_GENERATED_KEY_4096 = """ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- +""" + +CEPHADM_SELF_GENERATED_KEY_2048 = """ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCswSOXU7cXhku8 +5dp8u41UBg5d/3/biWI2nIt7B/XKvZhDS2JlR9sg0/sMW2NxzOOM8o8R1orc8n+F +jJEivYkBJOc2UwlpHsCQ8Y6bHtQdmoLQge0TWovODzW56e6cFbqkAdEuA1z+CaWj +CkJ8hQH0YP4cNwRaqcDfYClH3iidCnPS2egfOeIJiyyAfsFvgnsJPeYrb3ajgRZL +JlNY9tpkKrPym2Gr2fD0U6hbJeDSASs6vhz6l1uoiWKyJ6aNlfhhS90F7WYXYCbY +3zxnkxDUadCbGhp3g2/kdzaPzDkTP4hQ6wWbtTLMZvJo67SkhtOzdA2N+KSDY41D +Gwz2u3WlAgMBAAECgf9AaydkRoSJj+z85fRYGKuavR2KtJRHq30HzJNKJhAy2IAp +0vJ7ltm9AZ3DOO5mCwD2tXxVZZd2LvcPrlsOD3ffzfa4raCZpgVjuGDrKFi1djcq +JtKjPYLkez2hPV8y1Od1VJqYUtAfTSrZPr8V/oMeifjVUYdOICYuOUsEC3EZk8wx +ZxtXLqqp5vlrwP0ZWueMB2GMvZmlysfh0uMIZmRGlQxWPILMZruhH0xwZIMXVnAd +Igs8y+CIsa6fNgWrou1k07XChOLWgo9elzJfBwETSeERVuYNuF5CtTPzWeI9MDrh +6BHWNnb9bZe/yjy8Py1TA4qPamzPGipgSOJKHQECgYEA56sv10J5iHXSwTscbKp8 +YbG77pGkk6eWBRhCt8fnng+ShK0Mt2rO2YdvGpsSnQZooSfBsmWgkYoUEg2TPb1w +8N9NU5L8Wqsr4h07QnzPq6ybW7+KNhiCFBjWIzih8cKJJP2mw45KvOof0IQsunXA +EiPfGx4Ezfgwf0/rG6BWgmUCgYEAvuXw18pxHfYJbbR5x9vTIxfxwq8gTJekm0CI +bifo90T8miNgPxlCZRzUc0jYZfqei/UxoKqjwwtkcGN5TyOTBs7FvHpfU0ixPdYN +ud1fXxqk6KjZNqgFYnK4/9WThAfoFSjRfEtegylSJzGyBIg+biJbjDsNdPyvuBcc +eS8AUkECgYBDRttlz9AraMwDgX8Hr5rfZBYHehQpLQnMAPefF1aT+EG8deSzfzkC +wuno+A+3uhy4cCs3+3tdyJN7iqWv0Ev0J2T1WEIgsoTT7VlAPa6xVDbptf4VJ5je +7TeCkve0laHuNBsxvyjFI9iJXVj/7SISIoiv/0+14NV0o2jLZQy6YQKBgQC3u45r +0z++K4D1or+XWX9EhpY839tBfK6Ecr8c7rKt0ysgm63V7VTXBsF/1/vXYzjX0f2a ++sG1RzC7bzJhtgabhcYRWuKGwS8+Kdh6LJPPcFCKUYEGtv6/u1VNft2FNSrtuqSt +cckcile0u4LwE8WqsMzWEjwofdAOacgQ5ujzQQKBgQC0Ui8bB/Vs4+blmz5mjFdh +JllS3dRKx7Re5bY9XdkxlQd56jxyWIIyXDQ7Dk9GXaCjgGy5J7LaLCi2R1DZx28p +JhTdU/3sfz8NwTTFtR/m84rmirdecK9fYsU5wQzaqq/+IvVD6O8/46tK62YsYkQd +IwuZ9Cw+0P6sn81cI8FaeA== +-----END PRIVATE KEY----- +""" + +TLSOBJECT_STORE_CERT_PREFIX = f'{TLSOBJECT_STORE_PREFIX}cert.' +TLSOBJECT_STORE_KEY_PREFIX = f'{TLSOBJECT_STORE_PREFIX}key.' + + +class TestCertMgr(object): + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_tlsobject_store_save_cert(self, _set_store, cephadm_module: CephadmOrchestrator): + + rgw_frontend_rgw_foo_host2_cert = 'fake-rgw-cert' + nvmeof_client_cert = 'fake-nvmeof-client-cert' + nvmeof_server_cert = 'fake-nvmeof-server-cert' + nvmeof_root_ca_cert = 'fake-nvmeof-root-ca-cert' + grafana_cert_host_1 = 'grafana-cert-host-1' + grafana_cert_host_2 = 'grafana-cert-host-2' + cephadm_module.cert_mgr.save_cert('rgw_frontend_ssl_cert', rgw_frontend_rgw_foo_host2_cert, service_name='rgw.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_server_cert', nvmeof_server_cert, service_name='nvmeof.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_client_cert', nvmeof_client_cert, service_name='nvmeof.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_root_ca_cert', nvmeof_root_ca_cert, service_name='nvmeof.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('grafana_cert', grafana_cert_host_1, host='host-1', user_made=True) + cephadm_module.cert_mgr.save_cert('grafana_cert', grafana_cert_host_2, host='host-2', user_made=True) + + expected_calls = [ + mock.call(f'{TLSOBJECT_STORE_CERT_PREFIX}rgw_frontend_ssl_cert', json.dumps({'rgw.foo': Cert(rgw_frontend_rgw_foo_host2_cert, True).to_json()})), + mock.call(f'{TLSOBJECT_STORE_CERT_PREFIX}nvmeof_server_cert', json.dumps({'nvmeof.foo': Cert(nvmeof_server_cert, True).to_json()})), + mock.call(f'{TLSOBJECT_STORE_CERT_PREFIX}nvmeof_client_cert', json.dumps({'nvmeof.foo': Cert(nvmeof_client_cert, True).to_json()})), + mock.call(f'{TLSOBJECT_STORE_CERT_PREFIX}nvmeof_root_ca_cert', json.dumps({'nvmeof.foo': Cert(nvmeof_root_ca_cert, True).to_json()})), + mock.call(f'{TLSOBJECT_STORE_CERT_PREFIX}grafana_cert', json.dumps({'host-1': Cert(grafana_cert_host_1, True).to_json()})), + mock.call(f'{TLSOBJECT_STORE_CERT_PREFIX}grafana_cert', json.dumps({'host-1': Cert(grafana_cert_host_1, True).to_json(), + 'host-2': Cert(grafana_cert_host_2, True).to_json()})) + ] + _set_store.assert_has_calls(expected_calls) + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_tlsobject_store_cert_ls(self, _set_store, cephadm_module: CephadmOrchestrator): + + def get_generated_cephadm_cert_info_1(): + return { + "extensions": { + "basicConstraints": {"ca": False, "path_length": None}, + "subjectAltName": { + "DNSNames": ["ceph-node-0.ceph-orch", "grafana_servers"], + "IPAddresses": ["192.168.100.100"], + }, + }, + "issuer": {"commonName": "cephadm-root", "organizationName": "Ceph"}, + "public_key": {"key_size": 4096, "key_type": "RSA"}, + "subject": {"commonName": "192.168.100.100"}, + "validity": { + "not_after": "2028-01-21T16:06:56", + "not_before": "2025-01-21T16:06:56", + "remaining_days": 1092, + }, + } + + def get_generated_cephadm_cert_info_2(): + return { + "extensions": { + "basicConstraints": {"ca": False, "path_length": None}, + "subjectAltName": { + "DNSNames": ["ceph-node-2.ceph-orch", "grafana_servers"], + "IPAddresses": ["192.168.100.102"], + }, + }, + "issuer": {"commonName": "cephadm-root", "organizationName": "Ceph"}, + "public_key": {"key_size": 4096, "key_type": "RSA"}, + "subject": {"commonName": "192.168.100.102"}, + "validity": { + "not_after": "2028-01-24T12:21:22", + "not_before": "2025-01-24T12:21:22", + "remaining_days": 1094, + }, + } + + def compare_certls_dicts(expected_ls): + actual_ls = cephadm_module.cert_mgr.cert_ls(include_datails=True) + assert actual_ls.keys() == expected_ls.keys() + for svc_cert_name, value in expected_ls.items(): + expected_certs_entry = value['certificates'] + actual_certs_entry = actual_ls[svc_cert_name]['certificates'] + scope = value['scope'] + if scope == 'global': + assert 'validity' in expected_certs_entry + validity = expected_certs_entry['validity'] + assert re.match(validity['not_after'], actual_certs_entry['validity']['not_after']) + assert re.match(validity['not_before'], actual_certs_entry['validity']['not_before']) + else: # case of per service/host certificates + for target, cert_info in expected_certs_entry.items(): + assert 'validity' in cert_info + validity = cert_info['validity'] + assert re.match(validity['not_after'], actual_certs_entry[target]['validity']['not_after']) + assert re.match(validity['not_before'], actual_certs_entry[target]['validity']['not_before']) + + expected_ls = { + "cephadm_root_ca_cert": { + "certificates": { + "extensions": { + "basicConstraints": {"ca": True, "path_length": None}, + "subjectAltName": {"DNSNames": [], "IPAddresses": ["::1"]}, + }, + "issuer": { + "commonName": "cephadm-root", + "organizationName": "Ceph", + }, + "public_key": {"key_size": 4096, "key_type": "RSA"}, + "subject": { + "commonName": "cephadm-root", + "organizationName": "Ceph", + }, + "validity": { + "not_after": re.compile(".*"), + "not_before": re.compile(".*"), + "remaining_days": re.compile(".*"), + }, + }, + "scope": "global" + } + } + + # default certificate list (cephadm root CA) + compare_certls_dicts(expected_ls) + + # Services with sevice_name target/scope + cephadm_module.cert_mgr.save_cert('rgw_frontend_ssl_cert', CEPHADM_SELF_GENERATED_CERT_1, service_name='rgw.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('rgw_frontend_ssl_cert', CEPHADM_SELF_GENERATED_CERT_2, service_name='rgw.bar', user_made=True) + expected_ls["rgw_frontend_ssl_cert"] = { + "scope": "service", + "certificates": { + "rgw.foo": get_generated_cephadm_cert_info_1(), + "rgw.bar": get_generated_cephadm_cert_info_2(), + }, + } + compare_certls_dicts(expected_ls) + + # Services with host target/scope + cephadm_module.cert_mgr.save_cert('grafana_cert', CEPHADM_SELF_GENERATED_CERT_1, host='host1', user_made=True) + cephadm_module.cert_mgr.save_cert('grafana_cert', CEPHADM_SELF_GENERATED_CERT_2, host='host2', user_made=True) + expected_ls['grafana_cert'] = { + 'scope': 'host', + 'certificates': { + 'host1': get_generated_cephadm_cert_info_1(), + 'host2': get_generated_cephadm_cert_info_2(), + }, + } + compare_certls_dicts(expected_ls) + + # Services with global target/scope + cephadm_module.cert_mgr.save_cert('mgmt_gw_cert', CEPHADM_SELF_GENERATED_CERT_1, user_made=True) + cephadm_module.cert_mgr.save_cert('oauth2_proxy_cert', CEPHADM_SELF_GENERATED_CERT_2, user_made=True) + expected_ls['mgmt_gw_cert'] = {'scope': 'global', 'certificates': get_generated_cephadm_cert_info_1()} + expected_ls['oauth2_proxy_cert'] = {'scope': 'global', 'certificates': get_generated_cephadm_cert_info_2()} + compare_certls_dicts(expected_ls) + + # nvmeof certificates + cephadm_module.cert_mgr.save_cert('nvmeof_client_cert', CEPHADM_SELF_GENERATED_CERT_1, service_name='nvmeof.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_server_cert', CEPHADM_SELF_GENERATED_CERT_1, service_name='nvmeof.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_root_ca_cert', CEPHADM_SELF_GENERATED_CERT_2, service_name='nvmeof.foo', user_made=True) + expected_ls.update( + { + "nvmeof_client_cert": { + "scope": "service", + "certificates": { + "nvmeof.foo": get_generated_cephadm_cert_info_1(), + }, + }, + "nvmeof_server_cert": { + "scope": "service", + "certificates": { + "nvmeof.foo": get_generated_cephadm_cert_info_1(), + }, + }, + "nvmeof_root_ca_cert": { + "scope": "service", + "certificates": { + "nvmeof.foo": get_generated_cephadm_cert_info_2(), + }, + }, + } + ) + compare_certls_dicts(expected_ls) + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_tlsobject_store_save_key(self, _set_store, cephadm_module: CephadmOrchestrator): + + grafana_host1_key = 'fake-grafana-host1-key' + grafana_host2_key = 'fake-grafana-host2-key' + nvmeof_client_key = 'nvmeof-client-key' + nvmeof_server_key = 'nvmeof-server-key' + nvmeof_encryption_key = 'nvmeof-encryption-key' + cephadm_module.cert_mgr.save_key('grafana_key', grafana_host1_key, host='host1') + cephadm_module.cert_mgr.save_key('grafana_key', grafana_host2_key, host='host2') + cephadm_module.cert_mgr.save_key('nvmeof_client_key', nvmeof_client_key, service_name='nvmeof.foo') + cephadm_module.cert_mgr.save_key('nvmeof_server_key', nvmeof_server_key, service_name='nvmeof.foo') + cephadm_module.cert_mgr.save_key('nvmeof_encryption_key', nvmeof_encryption_key, service_name='nvmeof.foo') + + expected_calls = [ + mock.call(f'{TLSOBJECT_STORE_KEY_PREFIX}grafana_key', json.dumps({'host1': PrivKey(grafana_host1_key).to_json()})), + mock.call(f'{TLSOBJECT_STORE_KEY_PREFIX}grafana_key', json.dumps({'host1': PrivKey(grafana_host1_key).to_json(), + 'host2': PrivKey(grafana_host2_key).to_json()})), + mock.call(f'{TLSOBJECT_STORE_KEY_PREFIX}nvmeof_client_key', json.dumps({'nvmeof.foo': PrivKey(nvmeof_client_key).to_json()})), + mock.call(f'{TLSOBJECT_STORE_KEY_PREFIX}nvmeof_server_key', json.dumps({'nvmeof.foo': PrivKey(nvmeof_server_key).to_json()})), + mock.call(f'{TLSOBJECT_STORE_KEY_PREFIX}nvmeof_encryption_key', json.dumps({'nvmeof.foo': PrivKey(nvmeof_encryption_key).to_json()})), + ] + _set_store.assert_has_calls(expected_calls) + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_tlsobject_store_key_ls(self, _set_store, cephadm_module: CephadmOrchestrator): + expected_ls = { + 'nvmeof_server_key': { + 'scope': 'service', + 'keys': { + 'nvmeof.foo': { + 'key_type': 'RSA', + 'key_size': 4096 + } + } + }, + 'nvmeof_client_key': { + 'scope': 'service', + 'keys': { + 'nvmeof.foo': { + 'key_type': 'RSA', + 'key_size': 4096 + } + } + }, + 'nvmeof_encryption_key': { + 'scope': 'service', + 'keys': { + 'nvmeof.foo': { + 'key_type': 'RSA', + 'key_size': 2048 + } + } + } + } + + cephadm_module.cert_mgr.save_key('nvmeof_client_key', CEPHADM_SELF_GENERATED_KEY_4096, service_name='nvmeof.foo') + cephadm_module.cert_mgr.save_key('nvmeof_server_key', CEPHADM_SELF_GENERATED_KEY_4096, service_name='nvmeof.foo') + cephadm_module.cert_mgr.save_key('nvmeof_encryption_key', CEPHADM_SELF_GENERATED_KEY_2048, service_name='nvmeof.foo') + assert cephadm_module.cert_mgr.key_ls() == expected_ls + + cephadm_module.cert_mgr.save_key('ingress_ssl_key', 'invalid_key', service_name='ingress.foo') + assert 'Error parsing key' in cephadm_module.cert_mgr.key_ls()['ingress_ssl_key']['keys']['ingress.foo']['Error'] + + @mock.patch("cephadm.module.CephadmOrchestrator.get_store_prefix") + def test_tlsobject_store_load(self, _get_store_prefix, cephadm_module: CephadmOrchestrator): + + rgw_frontend_rgw_foo_host2_cert = 'fake-rgw-cert' + grafana_host1_key = 'fake-grafana-host1-cert' + nvmeof_server_cert = 'nvmeof-server-cert' + nvmeof_client_cert = 'nvmeof-client-cert' + nvmeof_root_ca_cert = 'nvmeof-root-ca-cert' + nvmeof_server_key = 'nvmeof-server-key' + nvmeof_client_key = 'nvmeof-client-key' + nvmeof_encryption_key = 'nvmeof-encryption-key' + + def _fake_prefix_store(key): + if key == 'cert_store.cert.': + return { + f'{TLSOBJECT_STORE_CERT_PREFIX}rgw_frontend_ssl_cert': json.dumps({'rgw.foo': Cert(rgw_frontend_rgw_foo_host2_cert, True).to_json()}), + f'{TLSOBJECT_STORE_CERT_PREFIX}nvmeof_server_cert': json.dumps({'nvmeof.foo': Cert(nvmeof_server_cert, True).to_json()}), + f'{TLSOBJECT_STORE_CERT_PREFIX}nvmeof_client_cert': json.dumps({'nvmeof.foo': Cert(nvmeof_client_cert, True).to_json()}), + f'{TLSOBJECT_STORE_CERT_PREFIX}nvmeof_root_ca_cert': json.dumps({'nvmeof.foo': Cert(nvmeof_root_ca_cert, True).to_json()}), + } + elif key == 'cert_store.key.': + return { + f'{TLSOBJECT_STORE_KEY_PREFIX}grafana_key': json.dumps({'host1': PrivKey(grafana_host1_key).to_json()}), + f'{TLSOBJECT_STORE_KEY_PREFIX}nvmeof_server_key': json.dumps({'nvmeof.foo': PrivKey(nvmeof_server_key).to_json()}), + f'{TLSOBJECT_STORE_KEY_PREFIX}nvmeof_client_key': json.dumps({'nvmeof.foo': PrivKey(nvmeof_client_key).to_json()}), + f'{TLSOBJECT_STORE_KEY_PREFIX}nvmeof_encryption_key': json.dumps({'nvmeof.foo': PrivKey(nvmeof_encryption_key).to_json()}), + } + else: + raise Exception(f'Get store with unexpected value {key}') + + _get_store_prefix.side_effect = _fake_prefix_store + cephadm_module.cert_mgr.load() + assert cephadm_module.cert_mgr.cert_store.known_entities['rgw_frontend_ssl_cert']['rgw.foo'] == Cert(rgw_frontend_rgw_foo_host2_cert, True) + assert cephadm_module.cert_mgr.cert_store.known_entities['nvmeof_server_cert']['nvmeof.foo'] == Cert(nvmeof_server_cert, True) + assert cephadm_module.cert_mgr.cert_store.known_entities['nvmeof_client_cert']['nvmeof.foo'] == Cert(nvmeof_client_cert, True) + assert cephadm_module.cert_mgr.cert_store.known_entities['nvmeof_root_ca_cert']['nvmeof.foo'] == Cert(nvmeof_root_ca_cert, True) + assert cephadm_module.cert_mgr.key_store.known_entities['grafana_key']['host1'] == PrivKey(grafana_host1_key) + assert cephadm_module.cert_mgr.key_store.known_entities['nvmeof_server_key']['nvmeof.foo'] == PrivKey(nvmeof_server_key) + assert cephadm_module.cert_mgr.key_store.known_entities['nvmeof_client_key']['nvmeof.foo'] == PrivKey(nvmeof_client_key) + assert cephadm_module.cert_mgr.key_store.known_entities['nvmeof_encryption_key']['nvmeof.foo'] == PrivKey(nvmeof_encryption_key) + + def test_tlsobject_store_get_cert_key(self, cephadm_module: CephadmOrchestrator): + + rgw_frontend_rgw_foo_host2_cert = 'fake-rgw-cert' + nvmeof_client_cert = 'fake-nvmeof-client-cert' + nvmeof_server_cert = 'fake-nvmeof-server-cert' + cephadm_module.cert_mgr.save_cert('rgw_frontend_ssl_cert', rgw_frontend_rgw_foo_host2_cert, service_name='rgw.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_server_cert', nvmeof_server_cert, service_name='nvmeof.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_client_cert', nvmeof_client_cert, service_name='nvmeof.foo', user_made=True) + + assert cephadm_module.cert_mgr.get_cert('rgw_frontend_ssl_cert', service_name='rgw.foo') == rgw_frontend_rgw_foo_host2_cert + assert cephadm_module.cert_mgr.get_cert('nvmeof_server_cert', service_name='nvmeof.foo') == nvmeof_server_cert + assert cephadm_module.cert_mgr.get_cert('nvmeof_client_cert', service_name='nvmeof.foo') == nvmeof_client_cert + assert cephadm_module.cert_mgr.get_cert('grafana_cert', host='host1') is None + assert cephadm_module.cert_mgr.get_cert('iscsi_ssl_cert', service_name='iscsi.foo') is None + assert cephadm_module.cert_mgr.get_cert('nvmeof_root_ca_cert', service_name='nvmeof.foo') is None + + with pytest.raises(TLSObjectException, match='Attempted to access cert for unknown entity'): + cephadm_module.cert_mgr.get_cert('unknown_entity') + with pytest.raises(TLSObjectException, match='Need host to access cert for entity'): + cephadm_module.cert_mgr.get_cert('grafana_cert') + with pytest.raises(TLSObjectException, match='Need service name to access cert for entity'): + cephadm_module.cert_mgr.get_cert('rgw_frontend_ssl_cert', host='foo') + + grafana_host1_key = 'fake-grafana-host1-cert' + nvmeof_server_key = 'nvmeof-server-key' + nvmeof_encryption_key = 'nvmeof-encryption-key' + cephadm_module.cert_mgr.save_key('grafana_key', grafana_host1_key, host='host1') + cephadm_module.cert_mgr.save_key('grafana_key', grafana_host1_key, host='host1') + cephadm_module.cert_mgr.save_key('nvmeof_server_key', nvmeof_server_key, service_name='nvmeof.foo') + cephadm_module.cert_mgr.save_key('nvmeof_encryption_key', nvmeof_encryption_key, service_name='nvmeof.foo') + + assert cephadm_module.cert_mgr.get_key('grafana_key', host='host1') == grafana_host1_key + assert cephadm_module.cert_mgr.get_key('nvmeof_server_key', service_name='nvmeof.foo') == nvmeof_server_key + assert cephadm_module.cert_mgr.get_key('nvmeof_client_key', service_name='nvmeof.foo') is None + assert cephadm_module.cert_mgr.get_key('nvmeof_encryption_key', service_name='nvmeof.foo') == nvmeof_encryption_key + + with pytest.raises(TLSObjectException, match='Attempted to access privkey for unknown entity'): + cephadm_module.cert_mgr.get_key('unknown_entity') + with pytest.raises(TLSObjectException, match='Need host to access privkey for entity'): + cephadm_module.cert_mgr.get_key('grafana_key') + + def test_tlsobject_store_rm_cert(self, cephadm_module: CephadmOrchestrator): + + # Save some certificates and ensure certificates are present + cephadm_module.cert_mgr.save_cert('rgw_frontend_ssl_cert', 'fake-rgw-cert', service_name='rgw.foo', user_made=True) + cephadm_module.cert_mgr.save_cert('nvmeof_server_cert', 'fake-nvmeof-server-cert', service_name='nvmeof.foo', user_made=True) + assert cephadm_module.cert_mgr.get_cert('rgw_frontend_ssl_cert', service_name='rgw.foo') == 'fake-rgw-cert' + assert cephadm_module.cert_mgr.get_cert('nvmeof_server_cert', service_name='nvmeof.foo') == 'fake-nvmeof-server-cert' + + # Remove certificates and ensure certificates are removed + cephadm_module.cert_mgr.rm_cert('rgw_frontend_ssl_cert', service_name='rgw.foo') + cephadm_module.cert_mgr.rm_cert('nvmeof_server_cert', service_name='nvmeof.foo') + assert cephadm_module.cert_mgr.get_cert('rgw_frontend_ssl_cert', service_name='rgw.foo') is None + assert cephadm_module.cert_mgr.get_cert('nvmeof_server_cert', service_name='nvmeof.foo') is None + + def test_tlsobject_store_rm_key(self, cephadm_module: CephadmOrchestrator): + + # Save some keys and ensure keys are present + cephadm_module.cert_mgr.save_key('grafana_key', 'fake-grafana-host1-key', host='host1') + cephadm_module.cert_mgr.save_key('nvmeof_server_key', 'fake-nvmeof-server-key', service_name='nvmeof.foo') + assert cephadm_module.cert_mgr.get_key('grafana_key', host='host1') == 'fake-grafana-host1-key' + assert cephadm_module.cert_mgr.get_key('nvmeof_server_key', service_name='nvmeof.foo') == 'fake-nvmeof-server-key' + + # Remove keys and ensure keys are removed + cephadm_module.cert_mgr.rm_key('grafana_key', host='host1') + cephadm_module.cert_mgr.rm_key('nvmeof_server_key', service_name='nvmeof.foo') + assert cephadm_module.cert_mgr.get_key('grafana_key', host='host1') is None + assert cephadm_module.cert_mgr.get_key('nvmeof_server_key', service_name='nvmeof.foo') is None + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_expired_certificate_detection(self, _set_store, cephadm_module: CephadmOrchestrator): + """ Test that expired certificates are flagged correctly """ + cert_mgr = cephadm_module.cert_mgr + cert_info = cert_mgr.check_certificate_state("test_service", "test_target", EXPIRED_CERT, MATCHING_KEY) + assert not cert_info.is_valid + assert "expired" in cert_info.error_info.lower() + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_invalid_certificate_detection(self, _set_store, cephadm_module: CephadmOrchestrator): + """ Test that invalid certificates are correctly detected """ + cert_mgr = cephadm_module.cert_mgr + cert_info = cert_mgr.check_certificate_state("test_service", "test_target", INVALID_CERT, MATCHING_KEY) + assert not cert_info.is_valid + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_non_matching_key(self, _set_store, cephadm_module: CephadmOrchestrator): + """ Test that certificates with non-matching keys are flagged """ + cert_mgr = cephadm_module.cert_mgr + cert_info = cert_mgr.check_certificate_state("test_service", "test_target", CEPHADM_SELF_GENERATED_CERT_1, NON_MATCHING_KEY) + assert not cert_info.is_valid + assert 'invalid cert/key pair' in cert_info.error_info.lower() + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_certificate_renewal_for_self_signed(self, _set_store, cephadm_module: CephadmOrchestrator): + """ Test that self-signed certificates close to expiration are renewed """ + cert_mgr = cephadm_module.cert_mgr + + # for services with host scope + cert_mgr.save_cert('grafana_cert', EXPIRED_CERT, host="test_host", user_made=True) + cert_info = CertInfo("grafana_cert", "test_host", is_valid=True, is_close_to_expiration=True, days_to_expiration=5) + cert_obj = Cert(EXPIRED_CERT, user_made=False) + with mock.patch.object(cert_mgr.ssl_certs, "renew_cert", return_value=("mock_new_cert", "mock_new_key")) as renew_mock: + cert_mgr._renew_self_signed_certificate(cert_info, cert_obj) + renew_mock.assert_called_once() + + # for services with service scope + cert_mgr.save_cert('ingress_ssl_cert', EXPIRED_CERT, service_name="test_service", user_made=True) + cert_info = CertInfo('ingress_ssl_cert', "test_service", is_valid=True, is_close_to_expiration=True, days_to_expiration=5) + cert_obj = Cert(EXPIRED_CERT, user_made=False) + with mock.patch.object(cert_mgr.ssl_certs, "renew_cert", return_value=("mock_new_cert", "mock_new_key")) as renew_mock: + cert_mgr._renew_self_signed_certificate(cert_info, cert_obj) + renew_mock.assert_called_once() + + # for services with global scope + cert_mgr.save_cert('mgmt_gw_cert', EXPIRED_CERT, user_made=True) + cert_info = CertInfo('mgmt_gw_cert', "test_service", is_valid=True, is_close_to_expiration=True, days_to_expiration=5) + cert_obj = Cert(EXPIRED_CERT, user_made=False) + with mock.patch.object(cert_mgr.ssl_certs, "renew_cert", return_value=("mock_new_cert", "mock_new_key")) as renew_mock: + cert_mgr._renew_self_signed_certificate(cert_info, cert_obj) + renew_mock.assert_called_once() + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_health_errors_appending(self, _set_store, cephadm_module: CephadmOrchestrator): + """ Test in case of appending new errors we also report previous ones """ + cert_mgr = cephadm_module.cert_mgr + + # Test health error is raised if any invalid cert is detected + problematic_certs = [ + CertInfo("test_service_1", "target_1", user_made=True, is_valid=False, error_info="expired"), + CertInfo("test_service_2", "target_2", is_valid=False, error_info="invalid format"), + ] + with mock.patch.object(cert_mgr.mgr, "set_health_error") as health_mock: + cert_mgr._notify_certificates_health_status(problematic_certs) + health_mock.assert_called_with('CEPHADM_CERT_ERROR', + 'Detected 2 cephadm certificate(s) issues: 1 invalid, 1 expired', + 2, + ["Certificate 'test_service_1 (target_1)' (user-made) has expired", + "Certificate 'test_service_2 (target_2)' (self-signed) is not valid (error: invalid format)"]) + + # Test in case of appending new errors we also report previous ones + problematic_certs = [ + CertInfo("test_service_3", "target_3", is_valid=True, is_close_to_expiration=True), + ] + with mock.patch.object(cert_mgr.mgr, "set_health_error") as health_mock: + cert_mgr._notify_certificates_health_status(problematic_certs) + health_mock.assert_called_with('CEPHADM_CERT_ERROR', + 'Detected 3 cephadm certificate(s) issues: 1 invalid, 1 expired, 1 expiring', + 3, + ["Certificate 'test_service_1 (target_1)' (user-made) has expired", + "Certificate 'test_service_2 (target_2)' (self-signed) is not valid (error: invalid format)", + "Certificate 'test_service_3 (target_3)' (self-signed) is about to expire (remaining days: 0)"]) + + @mock.patch("cephadm.module.CephadmOrchestrator.set_store") + def test_health_warning_on_bad_certificates(self, _set_store, cephadm_module: CephadmOrchestrator): + """ Test that invalid and expired certificates trigger health warnings """ + cert_mgr = cephadm_module.cert_mgr + + # Test health error is raised if any invalid cert is detected + problematic_certs = [ + CertInfo("test_service", "test_target", is_valid=False, error_info="expired"), + CertInfo("test_service", "test_target", is_valid=False, error_info="invalid format"), + ] + + with mock.patch.object(cert_mgr.mgr, "set_health_error") as health_mock: + cert_mgr._notify_certificates_health_status(problematic_certs) + health_mock.assert_called_once() + + # Test health warning is raised if valid but close to expire cert is detected + problematic_certs = [ + CertInfo("test_service", "test_target", is_valid=True, is_close_to_expiration=True, error_info="about to expire"), + ] + cert_mgr.certificates_health_report = [] + with mock.patch.object(cert_mgr.mgr, "set_health_warning") as health_mock: + cert_mgr._notify_certificates_health_status(problematic_certs) + health_mock.assert_called_once() + + # Test in case of no bad certificates issues the error is cleared correctly + problematic_certs = [] + cert_mgr.certificates_health_report = [] + with mock.patch.object(cert_mgr.mgr, "set_health_warning") as warning_mock, \ + mock.patch.object(cert_mgr.mgr, "set_health_error") as error_mock, \ + mock.patch.object(cert_mgr.mgr, "remove_health_warning") as remove_warning_mock: + + cert_mgr._notify_certificates_health_status(problematic_certs) + # Ensure that neither warnings nor errors were raised + warning_mock.assert_not_called() + error_mock.assert_not_called() + remove_warning_mock.assert_called_once_with(CertMgr.CEPHADM_CERTMGR_HEALTH_ERR) + + +class MockTLSObject(TLSObjectProtocol): + STORAGE_PREFIX = "mocktls" + + def __init__(self, data: str = "", user_made: bool = False): + self.data = data + self.user_made = user_made + + def __bool__(self): + return bool(self.data) + + @staticmethod + def to_json(obj): + return {"data": obj.data, "user_made": obj.user_made} + + @staticmethod + def from_json(json_data): + return MockTLSObject(json_data["data"], json_data["user_made"]) + + +class MockCephadmOrchestrator: + def __init__(self): + self.store = {} + + def set_store(self, key, value): + self.store[key] = value + + def get_store_prefix(self, prefix): + return {k: v for k, v in self.store.items() if k.startswith(prefix)} + + +class TestTLSObjectStore(unittest.TestCase): + def setUp(self): + known_entities = { + TLSObjectScope.GLOBAL: ["global_cert_1", "global_cert_2"], + TLSObjectScope.SERVICE: ["per_service1", "per_service2"], + TLSObjectScope.HOST: ["per_host1", "per_host2"], + } + self.mgr = MockCephadmOrchestrator() + self.store = TLSObjectStore(self.mgr, MockTLSObject, known_entities) + + def test_save_and_get_tlsobject(self): + self.store.save_tlsobject("per_service1", "my_cert_data", service_name="my_service") + obj = self.store.get_tlsobject("per_service1", service_name="my_service") + self.assertIsNotNone(obj) + self.assertEqual(obj.data, "my_cert_data") + + def test_remove_tlsobject(self): + self.store.save_tlsobject("per_host1", "cert_data", host="my_host") + self.store.rm_tlsobject("per_host1", host="my_host") + obj = self.store.get_tlsobject("per_host1", host="my_host") + self.assertIsNone(obj) + + def test_get_tlsobject_scope_and_target(self): + scope, target = self.store.get_tlsobject_scope_and_target("per_service1", service_name="my_service") + self.assertEqual(scope, TLSObjectScope.SERVICE) + self.assertEqual(target, "my_service") + + scope, target = self.store.get_tlsobject_scope_and_target("per_host1", host="my_host") + self.assertEqual(scope, TLSObjectScope.HOST) + self.assertEqual(target, "my_host") + + scope, target = self.store.get_tlsobject_scope_and_target("global_cert_1") + self.assertEqual(scope, TLSObjectScope.GLOBAL) + self.assertEqual(target, None) + + def test_list_tlsobjects(self): + self.store.save_tlsobject("global_cert_1", "cert_data1") + self.store.save_tlsobject("global_cert_2", "cert_data2") + self.store.save_tlsobject("per_service1", "cert_data1", service_name="my_service_1") + self.store.save_tlsobject("per_host1", "cert_data2", host="my_host") + tlsobjects = self.store.list_tlsobjects() + expected_entries = {("global_cert_1", None), + ("global_cert_2", None), + ("per_service1", "my_service_1"), + ("per_host1", "my_host")} + actual_entries = {(entity, target) for entity, tlsobject, target in tlsobjects if isinstance(tlsobject, MockTLSObject)} + self.assertEqual(len(tlsobjects), 4) + assert expected_entries == actual_entries + + def test_invalid_entity_access(self): + with self.assertRaises(TLSObjectException): + self.store.get_tlsobject("unknown_entity") + + def test_validate_tlsobject_entity(self): + with self.assertRaises(TLSObjectException): + self.store._validate_tlsobject_entity("unknown_entity") + with self.assertRaises(TLSObjectException): + self.store._validate_tlsobject_entity("per_host1") + with self.assertRaises(TLSObjectException): + self.store._validate_tlsobject_entity("per_service1") diff --git a/src/pybind/mgr/cephadm/tests/test_migration.py b/src/pybind/mgr/cephadm/tests/test_migration.py index 6d770de1870..b12dd30bf4b 100644 --- a/src/pybind/mgr/cephadm/tests/test_migration.py +++ b/src/pybind/mgr/cephadm/tests/test_migration.py @@ -372,13 +372,13 @@ def test_migrate_cert_store(cephadm_module: CephadmOrchestrator): cephadm_module.migration.migrate_6_7() - assert cephadm_module.cert_key_store.get_cert('rgw_frontend_ssl_cert', service_name='rgw.foo') - assert cephadm_module.cert_key_store.get_cert('iscsi_ssl_cert', service_name='iscsi.foo') - assert cephadm_module.cert_key_store.get_key('iscsi_ssl_key', service_name='iscsi.foo') - assert cephadm_module.cert_key_store.get_cert('ingress_ssl_cert', service_name='ingress.rgw.foo') - assert cephadm_module.cert_key_store.get_key('ingress_ssl_key', service_name='ingress.rgw.foo') - - assert cephadm_module.cert_key_store.get_cert('grafana_cert', host='host1') - assert cephadm_module.cert_key_store.get_cert('grafana_cert', host='host2') - assert cephadm_module.cert_key_store.get_key('grafana_key', host='host1') - assert cephadm_module.cert_key_store.get_key('grafana_key', host='host2') + assert cephadm_module.cert_mgr.get_cert('rgw_frontend_ssl_cert', service_name='rgw.foo') + assert cephadm_module.cert_mgr.get_cert('iscsi_ssl_cert', service_name='iscsi.foo') + assert cephadm_module.cert_mgr.get_key('iscsi_ssl_key', service_name='iscsi.foo') + assert cephadm_module.cert_mgr.get_cert('ingress_ssl_cert', service_name='ingress.rgw.foo') + assert cephadm_module.cert_mgr.get_key('ingress_ssl_key', service_name='ingress.rgw.foo') + + assert cephadm_module.cert_mgr.get_cert('grafana_cert', host='host1') + assert cephadm_module.cert_mgr.get_cert('grafana_cert', host='host2') + assert cephadm_module.cert_mgr.get_key('grafana_key', host='host1') + assert cephadm_module.cert_mgr.get_key('grafana_key', host='host2') diff --git a/src/pybind/mgr/cephadm/tests/test_services.py b/src/pybind/mgr/cephadm/tests/test_services.py index 387c0aee0ce..ac8f7ddfdcd 100644 --- a/src/pybind/mgr/cephadm/tests/test_services.py +++ b/src/pybind/mgr/cephadm/tests/test_services.py @@ -44,11 +44,11 @@ from orchestrator._interface import DaemonDescription from typing import Dict, List -cephadm_root_ca = """-----BEGIN CERTIFICATE-----\\nMIIE7DCCAtSgAwIBAgIUE8b2zZ64geu2ns3Zfn3/4L+Cf6MwDQYJKoZIhvcNAQEL\\nBQAwFzEVMBMGA1UEAwwMY2VwaGFkbS1yb290MB4XDTI0MDYyNjE0NDA1M1oXDTM0\\nMDYyNzE0NDA1M1owFzEVMBMGA1UEAwwMY2VwaGFkbS1yb290MIICIjANBgkqhkiG\\n9w0BAQEFAAOCAg8AMIICCgKCAgEAsZRJsdtTr9GLG1lWFql5SGc46ldFanNJd1Gl\\nqXq5vgZVKRDTmNgAb/XFuNEEmbDAXYIRZolZeYKMHfn0pouPRSel0OsC6/02ZUOW\\nIuN89Wgo3IYleCFpkVIumD8URP3hwdu85plRxYZTtlruBaTRH38lssyCqxaOdEt7\\nAUhvYhcMPJThB17eOSQ73mb8JEC83vB47fosI7IhZuvXvRSuZwUW30rJanWNhyZq\\neS2B8qw2RSO0+77H6gA4ftBnitfsE1Y8/F9Z/f92JOZuSMQXUB07msznPbRJia3f\\nueO8gOc32vxd1A1/Qzp14uX34yEGY9ko2lW226cZO29IVUtXOX+LueQttwtdlpz8\\ne6Npm09pXhXAHxV/OW3M28MdXmobIqT/m9MfkeAErt5guUeC5y8doz6/3VQRjFEn\\nRpN0WkblgnNAQ3DONPc+Qd9Fi/wZV2X7bXoYpNdoWDsEOiE/eLmhG1A2GqU/mneP\\nzQ6u79nbdwTYpwqHpa+PvusXeLfKauzI8lLUJotdXy9EK8iHUofibB61OljYye6B\\nG3b8C4QfGsw8cDb4APZd/6AZYyMx/V3cGZ+GcOV7WvsC8k7yx5Uqasm/kiGQ3EZo\\nuNenNEYoGYrjb8D/8QzqNUTwlEh27/ps80tO7l2GGTvWVZL0PRZbmLDvO77amtOf\\nOiRXMoUCAwEAAaMwMC4wGwYDVR0RBBQwEocQAAAAAAAAAAAAAAAAAAAAATAPBgNV\\nHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAxwzX5AhYEWhTV4VUwUj5+\\nqPdl4Q2tIxRokqyE+cDxoSd+6JfGUefUbNyBxDt0HaBq8obDqqrbcytxnn7mpnDu\\nhtiauY+I4Amt7hqFOiFA4cCLi2mfok6g2vL53tvhd9IrsfflAU2wy7hL76Ejm5El\\nA+nXlkJwps01Whl9pBkUvIbOn3pXX50LT4hb5zN0PSu957rjd2xb4HdfuySm6nW4\\n4GxtVWfmGA6zbC4XMEwvkuhZ7kD2qjkAguGDF01uMglkrkCJT3OROlNBuSTSBGqt\\ntntp5VytHvb7KTF7GttM3ha8/EU2KYaHM6WImQQTrOfiImAktOk4B3lzUZX3HYIx\\n+sByO4P4dCvAoGz1nlWYB2AvCOGbKf0Tgrh4t4jkiF8FHTXGdfvWmjgi1pddCNAy\\nn65WOCmVmLZPERAHOk1oBwqyReSvgoCFo8FxbZcNxJdlhM0Z6hzKggm3O3Dl88Xl\\n5euqJjh2STkBW8Xuowkg1TOs5XyWvKoDFAUzyzeLOL8YSG+gXV22gPTUaPSVAqdb\\nwd0Fx2kjConuC5bgTzQHs8XWA930U3XWZraj21Vaa8UxlBLH4fUro8H5lMSYlZNE\\nJHRNW8BkznAClaFSDG3dybLsrzrBFAu/Qb5zVkT1xyq0YkepGB7leXwq6vjWA5Pw\\nmZbKSphWfh0qipoqxqhfkw==\\n-----END CERTIFICATE-----\\n""" +cephadm_root_ca = """-----BEGIN CERTIFICATE-----\nMIIE7DCCAtSgAwIBAgIUE8b2zZ64geu2ns3Zfn3/4L+Cf6MwDQYJKoZIhvcNAQEL\nBQAwFzEVMBMGA1UEAwwMY2VwaGFkbS1yb290MB4XDTI0MDYyNjE0NDA1M1oXDTM0\nMDYyNzE0NDA1M1owFzEVMBMGA1UEAwwMY2VwaGFkbS1yb290MIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEAsZRJsdtTr9GLG1lWFql5SGc46ldFanNJd1Gl\nqXq5vgZVKRDTmNgAb/XFuNEEmbDAXYIRZolZeYKMHfn0pouPRSel0OsC6/02ZUOW\nIuN89Wgo3IYleCFpkVIumD8URP3hwdu85plRxYZTtlruBaTRH38lssyCqxaOdEt7\nAUhvYhcMPJThB17eOSQ73mb8JEC83vB47fosI7IhZuvXvRSuZwUW30rJanWNhyZq\neS2B8qw2RSO0+77H6gA4ftBnitfsE1Y8/F9Z/f92JOZuSMQXUB07msznPbRJia3f\nueO8gOc32vxd1A1/Qzp14uX34yEGY9ko2lW226cZO29IVUtXOX+LueQttwtdlpz8\ne6Npm09pXhXAHxV/OW3M28MdXmobIqT/m9MfkeAErt5guUeC5y8doz6/3VQRjFEn\nRpN0WkblgnNAQ3DONPc+Qd9Fi/wZV2X7bXoYpNdoWDsEOiE/eLmhG1A2GqU/mneP\nzQ6u79nbdwTYpwqHpa+PvusXeLfKauzI8lLUJotdXy9EK8iHUofibB61OljYye6B\nG3b8C4QfGsw8cDb4APZd/6AZYyMx/V3cGZ+GcOV7WvsC8k7yx5Uqasm/kiGQ3EZo\nuNenNEYoGYrjb8D/8QzqNUTwlEh27/ps80tO7l2GGTvWVZL0PRZbmLDvO77amtOf\nOiRXMoUCAwEAAaMwMC4wGwYDVR0RBBQwEocQAAAAAAAAAAAAAAAAAAAAATAPBgNV\nHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAxwzX5AhYEWhTV4VUwUj5+\nqPdl4Q2tIxRokqyE+cDxoSd+6JfGUefUbNyBxDt0HaBq8obDqqrbcytxnn7mpnDu\nhtiauY+I4Amt7hqFOiFA4cCLi2mfok6g2vL53tvhd9IrsfflAU2wy7hL76Ejm5El\nA+nXlkJwps01Whl9pBkUvIbOn3pXX50LT4hb5zN0PSu957rjd2xb4HdfuySm6nW4\n4GxtVWfmGA6zbC4XMEwvkuhZ7kD2qjkAguGDF01uMglkrkCJT3OROlNBuSTSBGqt\ntntp5VytHvb7KTF7GttM3ha8/EU2KYaHM6WImQQTrOfiImAktOk4B3lzUZX3HYIx\n+sByO4P4dCvAoGz1nlWYB2AvCOGbKf0Tgrh4t4jkiF8FHTXGdfvWmjgi1pddCNAy\nn65WOCmVmLZPERAHOk1oBwqyReSvgoCFo8FxbZcNxJdlhM0Z6hzKggm3O3Dl88Xl\n5euqJjh2STkBW8Xuowkg1TOs5XyWvKoDFAUzyzeLOL8YSG+gXV22gPTUaPSVAqdb\nwd0Fx2kjConuC5bgTzQHs8XWA930U3XWZraj21Vaa8UxlBLH4fUro8H5lMSYlZNE\nJHRNW8BkznAClaFSDG3dybLsrzrBFAu/Qb5zVkT1xyq0YkepGB7leXwq6vjWA5Pw\nmZbKSphWfh0qipoqxqhfkw==\n-----END CERTIFICATE-----\n""" -ceph_generated_cert = """-----BEGIN CERTIFICATE-----\\nMIICxjCCAa4CEQDIZSujNBlKaLJzmvntjukjMA0GCSqGSIb3DQEBDQUAMCExDTAL\\nBgNVBAoMBENlcGgxEDAOBgNVBAMMB2NlcGhhZG0wHhcNMjIwNzEzMTE0NzA3WhcN\\nMzIwNzEwMTE0NzA3WjAhMQ0wCwYDVQQKDARDZXBoMRAwDgYDVQQDDAdjZXBoYWRt\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyMe4DMA+MeYK7BHZMHB\\nq7zjliEOcNgxomjU8qbf5USF7Mqrf6+/87XWqj4pCyAW8x0WXEr6A56a+cmBVmt+\\nqtWDzl020aoId6lL5EgLLn6/kMDCCJLq++Lg9cEofMSvcZh+lY2f+1p+C+00xent\\nrLXvXGOilAZWaQfojT2BpRnNWWIFbpFwlcKrlg2G0cFjV5c1m6a0wpsQ9JHOieq0\\nSvwCixajwq3CwAYuuiU1wjI4oJO4Io1+g8yB3nH2Mo/25SApCxMXuXh4kHLQr/T4\\n4hqisvG4uJYgKMcSIrWj5o25mclByGi1UI/kZkCUES94i7Z/3ihx4Bad0AMs/9tw\\nFwIDAQABMA0GCSqGSIb3DQEBDQUAA4IBAQAf+pwz7Gd7mDwU2LY0TQXsK6/8KGzh\\nHuX+ErOb8h5cOAbvCnHjyJFWf6gCITG98k9nxU9NToG0WYuNm/max1y/54f0dtxZ\\npUo6KSNl3w6iYCfGOeUIj8isi06xMmeTgMNzv8DYhDt+P2igN6LenqWTVztogkiV\\nxQ5ZJFFLEw4sN0CXnrZX3t5ruakxLXLTLKeE0I91YJvjClSBGkVJq26wOKQNHMhx\\npWxeydQ5EgPZY+Aviz5Dnxe8aB7oSSovpXByzxURSabOuCK21awW5WJCGNpmqhWK\\nZzACBDEstccj57c4OGV0eayHJRsluVr2e9NHRINZA3qdB37e6gsI1xHo\\n-----END CERTIFICATE-----\\n""" +ceph_generated_cert = """-----BEGIN CERTIFICATE-----\nMIICxjCCAa4CEQDIZSujNBlKaLJzmvntjukjMA0GCSqGSIb3DQEBDQUAMCExDTAL\nBgNVBAoMBENlcGgxEDAOBgNVBAMMB2NlcGhhZG0wHhcNMjIwNzEzMTE0NzA3WhcN\nMzIwNzEwMTE0NzA3WjAhMQ0wCwYDVQQKDARDZXBoMRAwDgYDVQQDDAdjZXBoYWRt\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyMe4DMA+MeYK7BHZMHB\nq7zjliEOcNgxomjU8qbf5USF7Mqrf6+/87XWqj4pCyAW8x0WXEr6A56a+cmBVmt+\nqtWDzl020aoId6lL5EgLLn6/kMDCCJLq++Lg9cEofMSvcZh+lY2f+1p+C+00xent\nrLXvXGOilAZWaQfojT2BpRnNWWIFbpFwlcKrlg2G0cFjV5c1m6a0wpsQ9JHOieq0\nSvwCixajwq3CwAYuuiU1wjI4oJO4Io1+g8yB3nH2Mo/25SApCxMXuXh4kHLQr/T4\n4hqisvG4uJYgKMcSIrWj5o25mclByGi1UI/kZkCUES94i7Z/3ihx4Bad0AMs/9tw\nFwIDAQABMA0GCSqGSIb3DQEBDQUAA4IBAQAf+pwz7Gd7mDwU2LY0TQXsK6/8KGzh\nHuX+ErOb8h5cOAbvCnHjyJFWf6gCITG98k9nxU9NToG0WYuNm/max1y/54f0dtxZ\npUo6KSNl3w6iYCfGOeUIj8isi06xMmeTgMNzv8DYhDt+P2igN6LenqWTVztogkiV\nxQ5ZJFFLEw4sN0CXnrZX3t5ruakxLXLTLKeE0I91YJvjClSBGkVJq26wOKQNHMhx\npWxeydQ5EgPZY+Aviz5Dnxe8aB7oSSovpXByzxURSabOuCK21awW5WJCGNpmqhWK\nZzACBDEstccj57c4OGV0eayHJRsluVr2e9NHRINZA3qdB37e6gsI1xHo\n-----END CERTIFICATE-----\n""" -ceph_generated_key = """-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDLIx7gMwD4x5gr\\nsEdkwcGrvOOWIQ5w2DGiaNTypt/lRIXsyqt/r7/ztdaqPikLIBbzHRZcSvoDnpr5\\nyYFWa36q1YPOXTbRqgh3qUvkSAsufr+QwMIIkur74uD1wSh8xK9xmH6VjZ/7Wn4L\\n7TTF6e2ste9cY6KUBlZpB+iNPYGlGc1ZYgVukXCVwquWDYbRwWNXlzWbprTCmxD0\\nkc6J6rRK/AKLFqPCrcLABi66JTXCMjigk7gijX6DzIHecfYyj/blICkLExe5eHiQ\\nctCv9PjiGqKy8bi4liAoxxIitaPmjbmZyUHIaLVQj+RmQJQRL3iLtn/eKHHgFp3Q\\nAyz/23AXAgMBAAECggEAVoTB3Mm8azlPlaQB9GcV3tiXslSn+uYJ1duCf0sV52dV\\nBzKW8s5fGiTjpiTNhGCJhchowqxoaew+o47wmGc2TvqbpeRLuecKrjScD0GkCYyQ\\neM2wlshEbz4FhIZdgS6gbuh9WaM1dW/oaZoBNR5aTYo7xYTmNNeyLA/jO2zr7+4W\\n5yES1lMSBXpKk7bDGKYY4bsX2b5RLr2Grh2u2bp7hoLABCEvuu8tSQdWXLEXWpXo\\njwmV3hc6tabypIa0mj2Dmn2Dmt1ppSO0AZWG/WAizN3f4Z0r/u9HnbVrVmh0IEDw\\n3uf2LP5o3msG9qKCbzv3lMgt9mMr70HOKnJ8ohMSKQKBgQDLkNb+0nr152HU9AeJ\\nvdz8BeMxcwxCG77iwZphZ1HprmYKvvXgedqWtS6FRU+nV6UuQoPUbQxJBQzrN1Qv\\nwKSlOAPCrTJgNgF/RbfxZTrIgCPuK2KM8I89VZv92TSGi362oQA4MazXC8RAWjoJ\\nSu1/PHzK3aXOfVNSLrOWvIYeZQKBgQD/dgT6RUXKg0UhmXj7ExevV+c7oOJTDlMl\\nvLngrmbjRgPO9VxLnZQGdyaBJeRngU/UXfNgajT/MU8B5fSKInnTMawv/tW7634B\\nw3v6n5kNIMIjJmENRsXBVMllDTkT9S7ApV+VoGnXRccbTiDapBThSGd0wri/CuwK\\nNWK1YFOeywKBgEDyI/XG114PBUJ43NLQVWm+wx5qszWAPqV/2S5MVXD1qC6zgCSv\\nG9NLWN1CIMimCNg6dm7Wn73IM7fzvhNCJgVkWqbItTLG6DFf3/DPODLx1wTMqLOI\\nqFqMLqmNm9l1Nec0dKp5BsjRQzq4zp1aX21hsfrTPmwjxeqJZdioqy2VAoGAXR5X\\nCCdSHlSlUW8RE2xNOOQw7KJjfWT+WAYoN0c7R+MQplL31rRU7dpm1bLLRBN11vJ8\\nMYvlT5RYuVdqQSP6BkrX+hLJNBvOLbRlL+EXOBrVyVxHCkDe+u7+DnC4epbn+N8P\\nLYpwqkDMKB7diPVAizIKTBxinXjMu5fkKDs5n+sCgYBbZheYKk5M0sIxiDfZuXGB\\nkf4mJdEkTI1KUGRdCwO/O7hXbroGoUVJTwqBLi1tKqLLarwCITje2T200BYOzj82\\nqwRkCXGtXPKnxYEEUOiFx9OeDrzsZV00cxsEnX0Zdj+PucQ/J3Cvd0dWUspJfLHJ\\n39gnaegswnz9KMQAvzKFdg==\\n-----END PRIVATE KEY-----\\n""" +ceph_generated_key = """-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDLIx7gMwD4x5gr\nsEdkwcGrvOOWIQ5w2DGiaNTypt/lRIXsyqt/r7/ztdaqPikLIBbzHRZcSvoDnpr5\nyYFWa36q1YPOXTbRqgh3qUvkSAsufr+QwMIIkur74uD1wSh8xK9xmH6VjZ/7Wn4L\n7TTF6e2ste9cY6KUBlZpB+iNPYGlGc1ZYgVukXCVwquWDYbRwWNXlzWbprTCmxD0\nkc6J6rRK/AKLFqPCrcLABi66JTXCMjigk7gijX6DzIHecfYyj/blICkLExe5eHiQ\nctCv9PjiGqKy8bi4liAoxxIitaPmjbmZyUHIaLVQj+RmQJQRL3iLtn/eKHHgFp3Q\nAyz/23AXAgMBAAECggEAVoTB3Mm8azlPlaQB9GcV3tiXslSn+uYJ1duCf0sV52dV\nBzKW8s5fGiTjpiTNhGCJhchowqxoaew+o47wmGc2TvqbpeRLuecKrjScD0GkCYyQ\neM2wlshEbz4FhIZdgS6gbuh9WaM1dW/oaZoBNR5aTYo7xYTmNNeyLA/jO2zr7+4W\n5yES1lMSBXpKk7bDGKYY4bsX2b5RLr2Grh2u2bp7hoLABCEvuu8tSQdWXLEXWpXo\njwmV3hc6tabypIa0mj2Dmn2Dmt1ppSO0AZWG/WAizN3f4Z0r/u9HnbVrVmh0IEDw\n3uf2LP5o3msG9qKCbzv3lMgt9mMr70HOKnJ8ohMSKQKBgQDLkNb+0nr152HU9AeJ\nvdz8BeMxcwxCG77iwZphZ1HprmYKvvXgedqWtS6FRU+nV6UuQoPUbQxJBQzrN1Qv\nwKSlOAPCrTJgNgF/RbfxZTrIgCPuK2KM8I89VZv92TSGi362oQA4MazXC8RAWjoJ\nSu1/PHzK3aXOfVNSLrOWvIYeZQKBgQD/dgT6RUXKg0UhmXj7ExevV+c7oOJTDlMl\nvLngrmbjRgPO9VxLnZQGdyaBJeRngU/UXfNgajT/MU8B5fSKInnTMawv/tW7634B\nw3v6n5kNIMIjJmENRsXBVMllDTkT9S7ApV+VoGnXRccbTiDapBThSGd0wri/CuwK\nNWK1YFOeywKBgEDyI/XG114PBUJ43NLQVWm+wx5qszWAPqV/2S5MVXD1qC6zgCSv\nG9NLWN1CIMimCNg6dm7Wn73IM7fzvhNCJgVkWqbItTLG6DFf3/DPODLx1wTMqLOI\nqFqMLqmNm9l1Nec0dKp5BsjRQzq4zp1aX21hsfrTPmwjxeqJZdioqy2VAoGAXR5X\nCCdSHlSlUW8RE2xNOOQw7KJjfWT+WAYoN0c7R+MQplL31rRU7dpm1bLLRBN11vJ8\nMYvlT5RYuVdqQSP6BkrX+hLJNBvOLbRlL+EXOBrVyVxHCkDe+u7+DnC4epbn+N8P\nLYpwqkDMKB7diPVAizIKTBxinXjMu5fkKDs5n+sCgYBbZheYKk5M0sIxiDfZuXGB\nkf4mJdEkTI1KUGRdCwO/O7hXbroGoUVJTwqBLi1tKqLLarwCITje2T200BYOzj82\nqwRkCXGtXPKnxYEEUOiFx9OeDrzsZV00cxsEnX0Zdj+PucQ/J3Cvd0dWUspJfLHJ\n39gnaegswnz9KMQAvzKFdg==\n-----END PRIVATE KEY-----\n""" class FakeInventory: @@ -1537,6 +1537,16 @@ class TestMonitoring: def test_grafana_config_with_mgmt_gw_and_ouath2_proxy(self, _run_cephadm, cephadm_module: CephadmOrchestrator): _run_cephadm.side_effect = async_side_effect(("{}", "", 0)) + def inline_certificate(multi_line_cert): + """ + Converts a multi-line certificate into a one-line string with escaped newlines. + """ + return '\\n'.join([line.strip() for line in multi_line_cert.splitlines()]) + + oneline_cephadm_root_ca = inline_certificate(cephadm_root_ca) + oneline_ceph_generated_cert = inline_certificate(ceph_generated_cert) + oneline_ceph_generated_key = inline_certificate(ceph_generated_key) + y = dedent(f""" # This file is generated by cephadm. apiVersion: 1 @@ -1562,9 +1572,9 @@ class TestMonitoring: tlsSkipVerify: false secureJsonData: basicAuthPassword: admin - tlsCACert: "{cephadm_root_ca}" - tlsClientCert: "{ceph_generated_cert}" - tlsClientKey: "{ceph_generated_key}" + tlsCACert: "{oneline_cephadm_root_ca}" + tlsClientCert: "{oneline_ceph_generated_cert}" + tlsClientKey: "{oneline_ceph_generated_key}" - name: 'Loki' type: 'loki' @@ -1583,8 +1593,8 @@ class TestMonitoring: ssl_certificate_key=ceph_generated_key) with with_host(cephadm_module, "test"): - cephadm_module.cert_key_store.save_cert('grafana_cert', ceph_generated_cert, host='test') - cephadm_module.cert_key_store.save_key('grafana_key', ceph_generated_key, host='test') + cephadm_module.cert_mgr.save_cert('grafana_cert', ceph_generated_cert, host='test') + cephadm_module.cert_mgr.save_key('grafana_key', ceph_generated_key, host='test') with with_service(cephadm_module, PrometheusSpec("prometheus")) as _, \ with_service(cephadm_module, MgmtGatewaySpec("mgmt-gateway")) as _, \ with_service(cephadm_module, oauth2_spec) as _, \ @@ -1688,6 +1698,16 @@ class TestMonitoring: def test_grafana_config_with_mgmt_gw(self, _run_cephadm, cephadm_module: CephadmOrchestrator): _run_cephadm.side_effect = async_side_effect(("{}", "", 0)) + def inline_certificate(multi_line_cert): + """ + Converts a multi-line certificate into a one-line string with escaped newlines. + """ + return '\\n'.join([line.strip() for line in multi_line_cert.splitlines()]) + + oneline_cephadm_root_ca = inline_certificate(cephadm_root_ca) + oneline_ceph_generated_cert = inline_certificate(ceph_generated_cert) + oneline_ceph_generated_key = inline_certificate(ceph_generated_key) + y = dedent(f""" # This file is generated by cephadm. apiVersion: 1 @@ -1713,9 +1733,9 @@ class TestMonitoring: tlsSkipVerify: false secureJsonData: basicAuthPassword: admin - tlsCACert: "{cephadm_root_ca}" - tlsClientCert: "{ceph_generated_cert}" - tlsClientKey: "{ceph_generated_key}" + tlsCACert: "{oneline_cephadm_root_ca}" + tlsClientCert: "{oneline_ceph_generated_cert}" + tlsClientKey: "{oneline_ceph_generated_key}" - name: 'Loki' type: 'loki' @@ -1726,8 +1746,8 @@ class TestMonitoring: editable: false""").lstrip() with with_host(cephadm_module, "test"): - cephadm_module.cert_key_store.save_cert('grafana_cert', ceph_generated_cert, host='test') - cephadm_module.cert_key_store.save_key('grafana_key', ceph_generated_key, host='test') + cephadm_module.cert_mgr.save_cert('grafana_cert', ceph_generated_cert, host='test') + cephadm_module.cert_mgr.save_key('grafana_key', ceph_generated_key, host='test') with with_service( cephadm_module, PrometheusSpec("prometheus") ) as _, with_service(cephadm_module, MgmtGatewaySpec("mgmt-gateway")) as _, \ @@ -1819,8 +1839,8 @@ class TestMonitoring: _run_cephadm.side_effect = async_side_effect(("{}", "", 0)) with with_host(cephadm_module, "test"): - cephadm_module.cert_key_store.save_cert('grafana_cert', ceph_generated_cert, host='test') - cephadm_module.cert_key_store.save_key('grafana_key', ceph_generated_key, host='test') + cephadm_module.cert_mgr.save_cert('grafana_cert', ceph_generated_cert, host='test') + cephadm_module.cert_mgr.save_key('grafana_key', ceph_generated_key, host='test') with with_service( cephadm_module, PrometheusSpec("prometheus") ) as _, with_service(cephadm_module, ServiceSpec("mgr")) as _, with_service(