From f3873fe67f92303bc75a9abdb959f7ae59ce2809 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 27 Mar 2019 16:40:49 +0000 Subject: [PATCH] Add pacemaker authkey To work with pacemaker remotes all pacemaker nodes (including the remotes) need to share a common key in the same way that corosync does. This change allows a user to set a pacemaker key via config in the same way as corosync. If the pacemaker key value is unset then the corosync key is user. Change-Id: I75247e7f3af29fc0907a94ae8e1678bdb9ee64e2 --- config.yaml | 12 +++ hooks/utils.py | 13 +++ unit_tests/test_hacluster_utils.py | 134 +++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/config.yaml b/config.yaml index 1590b77..10ff58a 100644 --- a/config.yaml +++ b/config.yaml @@ -53,6 +53,18 @@ options: . This configuration element is mandatory and the service will fail on install if it is not provided. The value must be base64 encoded. + pacemaker_key: + type: string + default: + description: | + This value will become the Pacemaker authentication key. To generate + a suitable value use: + . + dd if=/dev/urandom of=/tmp/authkey bs=2048 count=1 + cat /tmp/authkey | base64 -w 0 + . + If this configuration element is not set then the corosync key will be + reused as the pacemaker key. maintenance-mode: type: boolean default: false diff --git a/hooks/utils.py b/hooks/utils.py index 0af8e56..49dc533 100644 --- a/hooks/utils.py +++ b/hooks/utils.py @@ -108,6 +108,8 @@ COROSYNC_CONF_FILES = [ COROSYNC_HACLUSTER_ACL, ] SUPPORTED_TRANSPORTS = ['udp', 'udpu', 'multicast', 'unicast'] + +PCMKR_AUTHKEY = '/etc/pacemaker/authkey' PCMKR_MAX_RETRIES = 3 PCMKR_SLEEP_SECS = 5 @@ -346,6 +348,11 @@ def emit_corosync_conf(): return False +def get_pcmkr_key(): + """Return the pacemaker auth key""" + return config('pacemaker_key') or config('corosync_key') + + def emit_base_conf(): if not os.path.isdir(COROSYNC_HACLUSTER_ACL_DIR): os.mkdir(COROSYNC_HACLUSTER_ACL_DIR) @@ -362,6 +369,12 @@ def emit_base_conf(): write_file(path=COROSYNC_AUTHKEY, content=b64decode(corosync_key), perms=0o400) + pcmkr_key = get_pcmkr_key() + write_file(path=PCMKR_AUTHKEY, + owner='root', + group='haclient', + content=b64decode(pcmkr_key), + perms=0o440) return True return False diff --git a/unit_tests/test_hacluster_utils.py b/unit_tests/test_hacluster_utils.py index 073ea2d..a0631aa 100644 --- a/unit_tests/test_hacluster_utils.py +++ b/unit_tests/test_hacluster_utils.py @@ -442,3 +442,137 @@ class UtilsTestCase(unittest.TestCase): relation_get.assert_has_calls([ mock.call('json_testkey', 'neutron-api/0', 'hacluster:1'), ]) + + @mock.patch.object(utils, 'render_template') + @mock.patch.object(utils.os.path, 'isdir') + @mock.patch.object(utils.os, 'mkdir') + @mock.patch.object(utils, 'write_file') + @mock.patch.object(utils, 'config') + def test_emit_base_conf(self, config, write_file, mkdir, isdir, + render_template): + cfg = { + 'corosync_key': 'Y29yb3N5bmNrZXkK', + 'pacemaker_key': 'cGFjZW1ha2Vya2V5Cg==', + } + config.side_effect = lambda x: cfg.get(x) + isdir.return_value = False + render = { + 'corosync': 'corosync etc default config', + 'hacluster.acl': 'hacluster acl file', + } + render_template.side_effect = lambda x, y: render[x] + expect_write_calls = [ + mock.call( + content='corosync etc default config', + path='/etc/default/corosync'), + mock.call( + content='hacluster acl file', + path='/etc/corosync/uidgid.d/hacluster'), + mock.call( + content=b'corosynckey\n', + path='/etc/corosync/authkey', + perms=256), + mock.call( + content=b'pacemakerkey\n', + path='/etc/pacemaker/authkey', + perms=288, + group='haclient', + owner='root') + ] + expect_render_calls = [ + mock.call( + 'corosync', + {'corosync_enabled': 'yes'}), + mock.call( + 'hacluster.acl', + {}) + ] + self.assertTrue(utils.emit_base_conf()) + write_file.assert_has_calls(expect_write_calls) + render_template.assert_has_calls(expect_render_calls) + mkdir.assert_called_once_with('/etc/corosync/uidgid.d') + + @mock.patch.object(utils, 'render_template') + @mock.patch.object(utils.os.path, 'isdir') + @mock.patch.object(utils.os, 'mkdir') + @mock.patch.object(utils, 'write_file') + @mock.patch.object(utils, 'config') + def test_emit_base_conf_no_pcmkr_key(self, config, write_file, mkdir, + isdir, render_template): + cfg = { + 'corosync_key': 'Y29yb3N5bmNrZXkK', + } + config.side_effect = lambda x: cfg.get(x) + isdir.return_value = False + render = { + 'corosync': 'corosync etc default config', + 'hacluster.acl': 'hacluster acl file', + } + render_template.side_effect = lambda x, y: render[x] + expect_write_calls = [ + mock.call( + content='corosync etc default config', + path='/etc/default/corosync'), + mock.call( + content='hacluster acl file', + path='/etc/corosync/uidgid.d/hacluster'), + mock.call( + content=b'corosynckey\n', + path='/etc/corosync/authkey', + perms=256), + mock.call( + content=b'corosynckey\n', + path='/etc/pacemaker/authkey', + perms=288, + group='haclient', + owner='root') + ] + expect_render_calls = [ + mock.call( + 'corosync', + {'corosync_enabled': 'yes'}), + mock.call( + 'hacluster.acl', + {}) + ] + self.assertTrue(utils.emit_base_conf()) + write_file.assert_has_calls(expect_write_calls) + render_template.assert_has_calls(expect_render_calls) + mkdir.assert_called_once_with('/etc/corosync/uidgid.d') + + @mock.patch.object(utils, 'render_template') + @mock.patch.object(utils.os.path, 'isdir') + @mock.patch.object(utils.os, 'mkdir') + @mock.patch.object(utils, 'write_file') + @mock.patch.object(utils, 'config') + def test_emit_base_conf_no_coro_key(self, config, write_file, mkdir, + isdir, render_template): + cfg = { + } + config.side_effect = lambda x: cfg.get(x) + isdir.return_value = False + render = { + 'corosync': 'corosync etc default config', + 'hacluster.acl': 'hacluster acl file', + } + render_template.side_effect = lambda x, y: render[x] + expect_write_calls = [ + mock.call( + content='corosync etc default config', + path='/etc/default/corosync'), + mock.call( + content='hacluster acl file', + path='/etc/corosync/uidgid.d/hacluster'), + ] + expect_render_calls = [ + mock.call( + 'corosync', + {'corosync_enabled': 'yes'}), + mock.call( + 'hacluster.acl', + {}) + ] + self.assertFalse(utils.emit_base_conf()) + write_file.assert_has_calls(expect_write_calls) + render_template.assert_has_calls(expect_render_calls) + mkdir.assert_called_once_with('/etc/corosync/uidgid.d')