Skip to content

Commit 9302801

Browse files
committed
mgr/cephadm: Adding RGW migration for the new certmgr certs format
Signed-off-by: Redouane Kachach <[email protected]>
1 parent ade502d commit 9302801

File tree

2 files changed

+177
-26
lines changed

2 files changed

+177
-26
lines changed

src/pybind/mgr/cephadm/migrations.py

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
import logging
44
from typing import TYPE_CHECKING, Iterator, Optional, Dict, Any, List
55

6-
from ceph.deployment.service_spec import PlacementSpec, ServiceSpec, HostPlacementSpec, RGWSpec
6+
from ceph.deployment.service_spec import PlacementSpec, ServiceSpec, HostPlacementSpec, RGWSpec, CertificateSource
77
from cephadm.schedule import HostAssignment
88
from cephadm.utils import SpecialHostLabels
99
import rados
10+
from mgr_util import parse_combined_pem_file, get_cert_issuer_info
11+
from cephadm.tlsobject_types import CertKeyPair
1012

1113
from mgr_module import NFS_POOL_NAME
1214
from orchestrator import OrchestratorError, DaemonDescription
1315

1416
if TYPE_CHECKING:
1517
from .module import CephadmOrchestrator
1618

17-
LAST_MIGRATION = 7
19+
LAST_MIGRATION = 8
1820

1921
logger = logging.getLogger(__name__)
2022

@@ -41,6 +43,9 @@ def __init__(self, mgr: "CephadmOrchestrator"):
4143
r = mgr.get_store('rgw_migration_queue')
4244
self.rgw_migration_queue = json.loads(r) if r else []
4345

46+
r = mgr.get_store('rgw_ssl_migration_queue')
47+
self.rgw_ssl_migration_queue = json.loads(r) if r else []
48+
4449
# for some migrations, we don't need to do anything except for
4550
# incrementing migration_current.
4651
# let's try to shortcut things here.
@@ -81,6 +86,9 @@ def verify_no_migration(self) -> None:
8186
"cephadm migration still ongoing. Please wait, until the migration is complete.")
8287

8388
def migrate(self, startup: bool = False) -> None:
89+
90+
logger.info('running migrations')
91+
8492
if self.mgr.migration_current == 0:
8593
if self.migrate_0_1():
8694
self.set(1)
@@ -109,6 +117,10 @@ def migrate(self, startup: bool = False) -> None:
109117
if self.migrate_6_7():
110118
self.set(7)
111119

120+
if self.mgr.migration_current == 7:
121+
if self.migrate_7_8():
122+
self.set(8)
123+
112124
def migrate_0_1(self) -> bool:
113125
"""
114126
Migration 0 -> 1
@@ -401,7 +413,7 @@ def migrate_5_6(self) -> bool:
401413
Any extra arguments detected on rgw_frontend_type field will be parsed and passed in the
402414
new spec field rgw_frontend_extra_args.
403415
"""
404-
self.mgr.log.debug(f'Starting rgw migration (queue length is {len(self.rgw_migration_queue)})')
416+
logger.info(f'Starting rgw migration (queue length is {len(self.rgw_migration_queue)})')
405417
for s in self.rgw_migration_queue:
406418
spec = s['spec']
407419
if self.rgw_spec_needs_migration(spec):
@@ -411,6 +423,7 @@ def migrate_5_6(self) -> bool:
411423
self.mgr.spec_store.save(rgw_spec)
412424
else:
413425
logger.info(f"No Migration is needed for rgw spec: {spec}")
426+
414427
self.rgw_migration_queue = []
415428
return True
416429

@@ -421,27 +434,70 @@ def migrate_6_7(self) -> bool:
421434
logger.info(f'Migrating certs/keys for {spec.service_name()} spec to cert store')
422435
self.mgr.spec_store._save_certs_and_keys(spec)
423436

424-
# grafana certs are stored based on the host they are placed on
437+
# Grafana certs are stored based on the host they are placed on
438+
grafana_cephadm_signed_certs = True
425439
for grafana_daemon in self.mgr.cache.get_daemons_by_type('grafana'):
426440
logger.info(f'Checking for cert/key for {grafana_daemon.name()}')
427441
hostname = grafana_daemon.hostname
428442
assert hostname is not None # for mypy
429443
grafana_cert_path = f'{hostname}/grafana_crt'
430444
grafana_key_path = f'{hostname}/grafana_key'
431445
grafana_cert = self.mgr.get_store(grafana_cert_path)
432-
if grafana_cert:
433-
logger.info(f'Migrating {grafana_daemon.name()} cert to cert store')
434-
self.mgr.cert_mgr.save_cert('grafana_cert', grafana_cert, host=hostname)
435446
grafana_key = self.mgr.get_store(grafana_key_path)
436-
if grafana_key:
437-
logger.info(f'Migrating {grafana_daemon.name()} key to cert store')
438-
self.mgr.cert_mgr.save_key('grafana_key', grafana_key, host=hostname)
447+
if grafana_cert:
448+
(org, cn) = get_cert_issuer_info(grafana_cert)
449+
if org == 'Ceph':
450+
logger.info(f'Migrating {grafana_daemon.name()}/{hostname} cert/key to cert store (as cephadm-signed certs)')
451+
self.mgr.cert_mgr.register_self_signed_cert_key_pair('grafana')
452+
self.mgr.cert_mgr.save_self_signed_cert_key_pair('grafana', CertKeyPair(grafana_cert, grafana_key), host=hostname)
453+
else:
454+
logger.info(f'Migrating {grafana_daemon.name()}/{hostname} cert/key to cert store (as custom-certs)')
455+
grafana_cephadm_signed_certs = False
456+
self.mgr.cert_mgr.save_cert('grafana_ssl_cert', grafana_cert, host=hostname)
457+
self.mgr.cert_mgr.save_key('grafana_ssl_key', grafana_key, host=hostname)
458+
459+
if not grafana_cephadm_signed_certs:
460+
# Update the spec to specify the right certificate source
461+
grafana_spec = self.mgr.spec_store['grafana'].spec
462+
grafana_spec.certificate_source = CertificateSource.REFERENCE.value
463+
self.mgr.spec_store.save(grafana_spec)
439464

440465
# NOTE: prometheus, alertmanager, and node-exporter certs were not stored
441466
# and appeared to just be generated at daemon deploy time if secure_monitoring_stack
442467
# was set to true. Therefore we have nothing to migrate for those daemons
443468
return True
444469

470+
def migrate_7_8(self) -> bool:
471+
logger.info(f'Starting rgw SSL/TLS migration (queue length is {len(self.rgw_ssl_migration_queue)})')
472+
for s in self.rgw_ssl_migration_queue:
473+
474+
svc_spec = s['spec'] # this is the RGWspec
475+
476+
if 'spec' not in svc_spec:
477+
logger.info(f"No SSL/TLS fields migration is needed for rgw spec: {svc_spec}")
478+
continue
479+
480+
cert_field = svc_spec['spec'].get('rgw_frontend_ssl_certificate')
481+
if not cert_field:
482+
logger.info(f"No SSL/TLS fields migration is needed for rgw spec: {svc_spec}")
483+
continue
484+
485+
cert_str = '\n'.join(cert_field) if isinstance(cert_field, list) else cert_field
486+
ssl_cert, ssl_key = parse_combined_pem_file(cert_str)
487+
new_spec = svc_spec.copy()
488+
new_spec['spec'].update({
489+
'rgw_frontend_ssl_certificate': None,
490+
'certificate_source': CertificateSource.INLINE.value,
491+
'ssl_cert': ssl_cert,
492+
'ssl_key': ssl_key,
493+
})
494+
495+
logger.info(f"Migrating {svc_spec} to new RGW SSL/TLS format {new_spec}")
496+
self.mgr.spec_store.save(RGWSpec.from_json(new_spec))
497+
498+
self.rgw_ssl_migration_queue = []
499+
return True
500+
445501

446502
def queue_migrate_rgw_spec(mgr: "CephadmOrchestrator", spec_dict: Dict[Any, Any]) -> None:
447503
"""
@@ -453,7 +509,16 @@ def queue_migrate_rgw_spec(mgr: "CephadmOrchestrator", spec_dict: Dict[Any, Any]
453509
ls = json.loads(queued)
454510
ls.append(spec_dict)
455511
mgr.set_store('rgw_migration_queue', json.dumps(ls))
456-
mgr.log.info(f'Queued rgw.{service_id} for migration')
512+
logger.info(f'Queued rgw.{service_id} for migration')
513+
514+
515+
def queue_migrate_rgw_ssl_spec(mgr: "CephadmOrchestrator", spec_dict: Dict[Any, Any]) -> None:
516+
service_id = spec_dict['spec']['service_id']
517+
queued = mgr.get_store('rgw_ssl_migration_queue') or '[]'
518+
ls = json.loads(queued)
519+
ls.append(spec_dict)
520+
mgr.set_store('rgw_ssl_migration_queue', json.dumps(ls))
521+
logger.info(f'Queued rgw.{service_id} for TLS migration')
457522

458523

459524
def queue_migrate_nfs_spec(mgr: "CephadmOrchestrator", spec_dict: Dict[Any, Any]) -> None:

src/pybind/mgr/cephadm/tests/test_migration.py

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
HostPlacementSpec,
88
RGWSpec,
99
IngressSpec,
10-
IscsiServiceSpec
10+
IscsiServiceSpec,
11+
GrafanaSpec
1112
)
1213
from ceph.utils import datetime_to_str, datetime_now
1314
from cephadm import CephadmOrchestrator
@@ -18,6 +19,58 @@
1819
from orchestrator import DaemonDescription
1920
from tests import mock
2021

22+
COMBINED_CERT_KEY = """
23+
-----BEGIN CERTIFICATE-----
24+
MIIDZTCCAk2gAwIBAgIUcf+7lpo2INwTIulhXOb78i4PL7gwDQYJKoZIhvcNAQEL
25+
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
26+
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yNTAxMjgxNDE0MzlaFw0yNTA1MDgx
27+
NDE0MzlaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa
28+
BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB
29+
DwAwggEKAoIBAQC5xpfgFsX7I19HGW2YE6vz0TNni2dM1ItQoP0WaX55bNEwLsj9
30+
hHTZ7vgTH6ZkaNp0U73Mq+0tM8UPRrNFBKhy5cE/D+l7aV5KUr4mgPK6Tgrgk0iS
31+
83nymladgSKRjN75HH8SMg2lLVoivfrAAMh58JA2zFUFZaZQnD1eL/+waht9qpCd
32+
ilsY3MVKuElZ3ndxSaTuISLhPS8GO7jkCbCThfkrnk5IeCd5trN8ho55Ev5U5Axg
33+
bUgHlJxzUr9wLTzKW0x9D5qbLTvaC9VsUN+SdQW01pTs4MLPuKsnjLGaG91sEbZl
34+
n4Ub7bXvNey9z0heGE/NJX+Q5EkkhFV5TLvZAgMBAAGjUzBRMB0GA1UdDgQWBBSz
35+
OgD/EZsfAuDpt4wv1qVMcNlbajAfBgNVHSMEGDAWgBSzOgD/EZsfAuDpt4wv1qVM
36+
cNlbajAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBJ/PMFQFn2
37+
6PeHEneLnxQqggg2FulM6tYc+XHuRCUW9koNBHVn5/CTw6MZ6oxRwVtY4w9GHZSk
38+
TvL6xAwk5exIwYJFdLe5XMNXtIy6Hz9BVVLRkL9u/yDXh0Bsi5vVwF14rL7956K4
39+
XQQXdUCuT5GF3u+2g+nnbYz1N00XG8YMiT0a8ZKrVUFi3l12muULzrw5YsBWenGC
40+
DdVBRQEsl2ZJYN+/01TO9fScbv9ANQFUJpvtVCQjTWj4WOIhnhm8dHXD3ppMdccT
41+
y7jEpinQvVQxfGIshLMi4rtK5sMpS4Qx5gzyU4ccHSDgdSrIC7zjNY9YdS0X7+je
42+
QTkccglYXmZ6
43+
-----END CERTIFICATE-----
44+
-----BEGIN PRIVATE KEY-----
45+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5xpfgFsX7I19H
46+
GW2YE6vz0TNni2dM1ItQoP0WaX55bNEwLsj9hHTZ7vgTH6ZkaNp0U73Mq+0tM8UP
47+
RrNFBKhy5cE/D+l7aV5KUr4mgPK6Tgrgk0iS83nymladgSKRjN75HH8SMg2lLVoi
48+
vfrAAMh58JA2zFUFZaZQnD1eL/+waht9qpCdilsY3MVKuElZ3ndxSaTuISLhPS8G
49+
O7jkCbCThfkrnk5IeCd5trN8ho55Ev5U5AxgbUgHlJxzUr9wLTzKW0x9D5qbLTva
50+
C9VsUN+SdQW01pTs4MLPuKsnjLGaG91sEbZln4Ub7bXvNey9z0heGE/NJX+Q5Ekk
51+
hFV5TLvZAgMBAAECggEACCGMWi871/X3YJn9mdiISSjsLcS7OEwTgOt/fyd7vhCD
52+
7IoY0j6lwqXazzN3ksgRONAzNOTPyyH5XZyD207DmT4XHVbFGFmQbILsmtDSTuTq
53+
IK1WLSBhjHJW4irHerKGcrNdmHC101MYH0lxHATRU8PW/Ay7c1cqVoCZRnHvFgLQ
54+
YZHxhskDnMTaXX0lw+CCq7ajUg2Su2u7tC7LiG/n4cjBNTblB7vmyAiFo1xoYqam
55+
GuwtkLGZW1RxvCi13HGIKAU9VnwKOyzhJp9ZBcx1Xshiaqazwhpf8PhP8mT2kLFg
56+
ti5NVxadbD78VGMC5bfH6lZdm4/MLlaqMejb6QXCRQKBgQDcd72c4FJpXpXWMR6g
57+
ROw60tn6qjSpH0YJ96bf19UGgNcYVUCiZrgG7ENx6SabjUJwqxi3qCxneD+J7caL
58+
Befd2Can4vf6U3o3DV/a86Dz6Qd4n7n6MU39aOg2jsCriknfOUkWfnGgvMaPzduU
59+
O1rFF0xpezIQkU3HjaN4aLGSswKBgQDXt3/EsRIk8xYQvcUTaWAQdaxtRewS9Tc2
60+
m6MdU6der8C6fTydggUBdkURawFehdpNmKiymBJJFniCs/EuGmKKHjupW04Kmwin
61+
isaA+tSwLQ01tL1G7xhydb85sbfBXzel4fztmk2OB+IpB4rvTFlP8t2z/bQQumjN
62+
WPLUwz7NQwKBgFZ4AD5PHQOGvW3Mxh5F6gEIQcY2i4Dpaybtot2YYUyzq6k3hqor
63+
b3IHqEw9DY9kz/IwqPkfVIsgdos6XuyX3GD+Lesa8feUVhLRhA70DuSbOPruapre
64+
S6BgTPNY+ehNzLtoVGomHZrVb2tnaf+xZ+B1Str0Hqaw1ri1rK/FICBRAoGBALbn
65+
T95mhQvvUPZA8ajT4DAUlm7QqqooYPhcXqGvHGqcer2lEpA6fiQPM+Dg6fhLZh4F
66+
IoTLjDWMaAHqsMR2erbBi7S9Rh6X9W6ZrFYQV+ZJTLoM1bAfaosia1Fv7m53Xae5
67+
Rcvw2XFkHc7MJnFgOxoewvyqUNMeO15h3QOpyMYhAoGABm6bQcIdmv3e+GVoraXA
68+
lsmM4/lRi/HmRHGtQ7kjKvT09YBQ3/qm04QwvwQtik7ws7t8VODQSgZC6re0TU7Y
69+
RPw+RGrt0nnmMUP2jJ6SKPCXmw55tW7FcvBJeAM4komEUoLrnKfwkaRy8SKSt8a0
70+
HlBxebJND7cfu20WpwErmhU=
71+
-----END PRIVATE KEY-----
72+
"""
73+
2174

2275
@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('[]'))
2376
def test_migrate_scheduler(cephadm_module: CephadmOrchestrator):
@@ -349,8 +402,53 @@ def test_migrate_rgw_spec(cephadm_module: CephadmOrchestrator, rgw_spec_store_en
349402
assert 'rgw.foo' not in cephadm_module.spec_store.all_specs
350403

351404

405+
@mock.patch('cephadm.migrations.get_cert_issuer_info')
406+
def test_migrate_grafana_cephadm_signed(mock_get_cert_issuer_info, cephadm_module: CephadmOrchestrator):
407+
mock_get_cert_issuer_info.return_value = ('Ceph', 'MockCephCN')
408+
409+
cephadm_module.set_store('host1/grafana_crt', 'grafana_cert1')
410+
cephadm_module.set_store('host1/grafana_key', 'grafana_key1')
411+
cephadm_module.set_store('host2/grafana_crt', 'grafana_cert2')
412+
cephadm_module.set_store('host2/grafana_key', 'grafana_key2')
413+
cephadm_module.cache.daemons = {'host1': {'grafana.host1': DaemonDescription('grafana', 'host1', 'host1')},
414+
'host2': {'grafana.host2': DaemonDescription('grafana', 'host2', 'host2')}}
415+
416+
cephadm_module.migration.migrate_6_7()
417+
418+
assert cephadm_module.cert_mgr.get_cert('cephadm-signed_grafana_cert', host='host1')
419+
assert cephadm_module.cert_mgr.get_cert('cephadm-signed_grafana_cert', host='host2')
420+
assert cephadm_module.cert_mgr.get_key('cephadm-signed_grafana_key', host='host1')
421+
assert cephadm_module.cert_mgr.get_key('cephadm-signed_grafana_key', host='host2')
422+
423+
424+
@mock.patch('cephadm.migrations.get_cert_issuer_info')
425+
def test_migrate_grafana_custom_certs(mock_get_cert_issuer_info, cephadm_module: CephadmOrchestrator):
426+
from datetime import datetime, timezone
427+
428+
grafana_spec = GrafanaSpec(service_id='grafana', ssl=True)
429+
cephadm_module.spec_store._specs = {
430+
'grafana': grafana_spec,
431+
}
432+
cephadm_module.spec_store.spec_created['grafana'] = datetime.now(timezone.utc)
433+
434+
cephadm_module.set_store('host1/grafana_crt', 'grafana_cert1')
435+
cephadm_module.set_store('host1/grafana_key', 'grafana_key1')
436+
cephadm_module.set_store('host2/grafana_crt', 'grafana_cert2')
437+
cephadm_module.set_store('host2/grafana_key', 'grafana_key2')
438+
cephadm_module.cache.daemons = {'host1': {'grafana.host1': DaemonDescription('grafana', 'host1', 'host1')},
439+
'host2': {'grafana.host2': DaemonDescription('grafana', 'host2', 'host2')}}
440+
441+
mock_get_cert_issuer_info.return_value = ('CustomOrg', 'MockCustomOrg') # Force grafana certs to be custom
442+
cephadm_module.migration.migrate_6_7()
443+
444+
assert cephadm_module.cert_mgr.get_cert('grafana_ssl_cert', host='host1')
445+
assert cephadm_module.cert_mgr.get_cert('grafana_ssl_cert', host='host2')
446+
assert cephadm_module.cert_mgr.get_key('grafana_ssl_key', host='host1')
447+
assert cephadm_module.cert_mgr.get_key('grafana_ssl_key', host='host2')
448+
449+
352450
def test_migrate_cert_store(cephadm_module: CephadmOrchestrator):
353-
rgw_spec = RGWSpec(service_id='foo', rgw_frontend_ssl_certificate='rgw_cert', ssl=True)
451+
rgw_spec = RGWSpec(service_id='foo', rgw_frontend_ssl_certificate=COMBINED_CERT_KEY, ssl=True)
354452
iscsi_spec = IscsiServiceSpec(service_id='foo', pool='foo', ssl_cert='iscsi_cert', ssl_key='iscsi_key')
355453
ingress_spec = IngressSpec(service_id='rgw.foo', ssl_cert='ingress_cert', ssl_key='ingress_key', ssl=True)
356454
cephadm_module.spec_store._specs = {
@@ -364,22 +462,10 @@ def test_migrate_cert_store(cephadm_module: CephadmOrchestrator):
364462
cephadm_module.set_store('service_discovery/root/cert', 'service_discovery_cert')
365463
cephadm_module.set_store('service_discovery/root/key', 'service_discovery_key')
366464

367-
cephadm_module.set_store('host1/grafana_crt', 'grafana_cert1')
368-
cephadm_module.set_store('host1/grafana_key', 'grafana_key1')
369-
cephadm_module.set_store('host2/grafana_crt', 'grafana_cert2')
370-
cephadm_module.set_store('host2/grafana_key', 'grafana_key2')
371-
cephadm_module.cache.daemons = {'host1': {'grafana.host1': DaemonDescription('grafana', 'host1', 'host1')},
372-
'host2': {'grafana.host2': DaemonDescription('grafana', 'host2', 'host2')}}
373-
374465
cephadm_module.migration.migrate_6_7()
375466

376-
assert cephadm_module.cert_mgr.get_cert('rgw_frontend_ssl_cert', service_name='rgw.foo')
467+
assert cephadm_module.cert_mgr.get_cert('rgw_ssl_cert', service_name='rgw.foo')
377468
assert cephadm_module.cert_mgr.get_cert('iscsi_ssl_cert', service_name='iscsi.foo')
378469
assert cephadm_module.cert_mgr.get_key('iscsi_ssl_key', service_name='iscsi.foo')
379470
assert cephadm_module.cert_mgr.get_cert('ingress_ssl_cert', service_name='ingress.rgw.foo')
380471
assert cephadm_module.cert_mgr.get_key('ingress_ssl_key', service_name='ingress.rgw.foo')
381-
382-
assert cephadm_module.cert_mgr.get_cert('grafana_cert', host='host1')
383-
assert cephadm_module.cert_mgr.get_cert('grafana_cert', host='host2')
384-
assert cephadm_module.cert_mgr.get_key('grafana_key', host='host1')
385-
assert cephadm_module.cert_mgr.get_key('grafana_key', host='host2')

0 commit comments

Comments
 (0)