From d8065b74098d123538122f700b2dc92b00e504f2 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 func-test-pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/1195 --- hooks/ceph_hooks.py | 21 +++++++++++++++++++++ hooks/utils.py | 23 +++++++++++++++++++---- unit_tests/test_ceph_hooks.py | 12 ++++++++++++ unit_tests/test_ceph_utils.py | 12 ++++++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/hooks/ceph_hooks.py b/hooks/ceph_hooks.py index 993166a7..5e794cdc 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,28 @@ def get_bdev_enable_discard(): "bdev-enable-discard: %s") % bdev_enable_discard) +def handle_pending_key(pending_key): + ix = pending_key.find(':') + if ix < 0: + log('invalid pending key: %s' % pending_key) + return + + osd_id = pending_key[:ix] + if not os.path.exists('/var/lib/ceph/osd/ceph-%s' % osd_id): + log('pending key not meant for this unit - skipping') + return + + import_pending_key(pending_key[ix + 1:], 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'])