From 406d8c9e52b08652654ce52809db61c962f0bdaf Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Thu, 11 Apr 2024 13:37:19 -0300 Subject: [PATCH] Implement key rotation for OSD This patchset implements key rotation for OSD's. It does so by receiving an update in the relation data bag from ceph-mons (where the actions is started), which informs the OSD units for which OSD the key needs to be rotated and the key itself. The OSD units then check if they are managing the ID specified, and if so, proceed to rotate the key. Change-Id: I382a0a657b31c172a036ce7ca62facbbce32b4a0 --- hooks/ceph_hooks.py | 14 ++++++++++++++ hooks/utils.py | 23 +++++++++++++++++++---- unit_tests/test_ceph_hooks.py | 12 ++++++++++++ unit_tests/test_ceph_utils.py | 12 ++++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/hooks/ceph_hooks.py b/hooks/ceph_hooks.py index 993166a7..174e0f45 100755 --- a/hooks/ceph_hooks.py +++ b/hooks/ceph_hooks.py @@ -85,6 +85,7 @@ from utils import ( import_osd_upgrade_key, import_osd_removal_key, import_client_crash_key, + import_pending_key, get_host_ip, get_networks, assert_charm_supports_ipv6, @@ -738,8 +739,21 @@ def get_bdev_enable_discard(): "bdev-enable-discard: %s") % bdev_enable_discard) +def handle_pending_key(pending_key): + for osd_id, key in json.loads(pending_key).items(): + if not os.path.exists('/var/lib/ceph/osd/ceph-%s' % osd_id): + continue + import_pending_key(key, osd_id) + service_restart('ceph-osd@%s' % osd_id) + + @hooks.hook('mon-relation-changed') def mon_relation(): + pending_key = relation_get('pending_key') + if pending_key: + handle_pending_key(pending_key) + return + bootstrap_key = relation_get('osd_bootstrap_key') upgrade_key = relation_get('osd_upgrade_key') removal_key = relation_get('osd_disk_removal_key') diff --git a/hooks/utils.py b/hooks/utils.py index a86b99eb..b293e6a0 100644 --- a/hooks/utils.py +++ b/hooks/utils.py @@ -85,15 +85,17 @@ def is_osd_bootstrap_ready(): return os.path.exists(_bootstrap_keyring) -def _import_key(key, path, name): - if not os.path.exists(path): +def _import_key(key, path, name, override=False): + exists = os.path.exists(path) + if not exists or override: + create = ['--create-keyring'] if not exists else [] cmd = [ 'sudo', '-u', ceph.ceph_user(), 'ceph-authtool', - path, - '--create-keyring', + path + ] + create + [ '--name={}'.format(name), '--add-key={}'.format(key) ] @@ -140,6 +142,19 @@ def import_client_crash_key(key): _import_key(key, _client_crash_keyring, 'client.crash') +def import_pending_key(key, osd_id): + """ + Import a pending key, used for key rotation. + + :param key: The pending cephx key that will replace the current one. + :type key: str + :param osd_id: The OSD id whose key will be replaced. + :type osd_id: str + :raises: subprocess.CalledProcessError""" + _import_key(key, '/var/lib/ceph/osd/ceph-%s/keyring' % osd_id, + 'osd.%s' % osd_id, override=True) + + def render_template(template_name, context, template_dir=TEMPLATES_DIR): """Render Jinja2 template. diff --git a/unit_tests/test_ceph_hooks.py b/unit_tests/test_ceph_hooks.py index b4cceea0..a135aeb9 100644 --- a/unit_tests/test_ceph_hooks.py +++ b/unit_tests/test_ceph_hooks.py @@ -835,6 +835,18 @@ class CephHooksTestCase(unittest.TestCase): level=ceph_hooks.ERROR, ) + @patch.object(ceph_hooks, 'service_restart') + @patch.object(ceph_hooks, 'import_pending_key') + @patch.object(ceph_hooks.os.path, 'exists') + def test_handle_pending_key(self, exists, import_pending_key, + service_restart): + exists.return_value = True + pending_key = '0:some-key' + ceph_hooks.handle_pending_key(pending_key) + exists.assert_called_with('/var/lib/ceph/osd/ceph-0') + import_pending_key.assert_called_with('some-key', '0') + service_restart.assert_called_with('ceph-osd@0') + @patch.object(ceph_hooks, 'local_unit') @patch.object(ceph_hooks, 'relation_get') diff --git a/unit_tests/test_ceph_utils.py b/unit_tests/test_ceph_utils.py index f0fbabd6..2ae572aa 100644 --- a/unit_tests/test_ceph_utils.py +++ b/unit_tests/test_ceph_utils.py @@ -352,3 +352,15 @@ cset.uuid 57add9da-e5de-47c6-8f39-3e16aafb8d31 }] }''' self.assertEqual(utils.get_parent_device('/dev/loop1p1'), '/dev/loop1') + + @patch.object(utils.ceph, 'ceph_user') + @patch.object(utils.subprocess, 'check_call') + @patch.object(utils.os.path, 'exists') + def test_import_pending_key(self, exists, check_call, ceph_user): + ceph_user.return_value = 'ceph' + exists.return_value = True + utils.import_pending_key('some-key', '0') + exists.assert_called_with('/var/lib/ceph/osd/ceph-0/keyring') + check_call.assert_called_with(['sudo', '-u', 'ceph', 'ceph-authtool', + '/var/lib/ceph/osd/ceph-0/keyring', + '--name=osd.0', '--add-key=some-key'])