From fcb992a6b9980f83b906dcf547826bc49395755c Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Robles Date: Thu, 15 Jun 2017 15:58:27 +0300 Subject: [PATCH] Add fernet keys purging based no t-h-t parameter When fernet key rotations are done via the mistral action, the action will check the KeystoneFernetMaxActiveKeys and and purge the excess keys from the mistral environment or swift container. If the parameter is not present, a default value of 5 is taken. And if for some reason the user gives a smaller value, a minimum of 3 is taken into account. bp keystone-fernet-rotation Depends-On: I9c6b0708c2c03ad9918222599f8b6aad397d8089 Change-Id: I4a28073c93c210703871daa8fe660fc1914464e8 --- tripleo_common/actions/parameters.py | 50 +++++++++++++----- .../tests/actions/test_parameters.py | 51 +++++++++++++++++++ 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/tripleo_common/actions/parameters.py b/tripleo_common/actions/parameters.py index 08bc814cf..9229ce77b 100644 --- a/tripleo_common/actions/parameters.py +++ b/tripleo_common/actions/parameters.py @@ -263,11 +263,14 @@ class GetPasswordsAction(base.TripleOAction): parameter_defaults = env.get('parameter_defaults', {}) passwords = env.get('passwords', {}) + + return self._get_overriden_passwords(passwords, parameter_defaults) + + def _get_overriden_passwords(self, env_passwords, parameter_defaults): for name in constants.PASSWORD_PARAMETER_NAMES: if name in parameter_defaults: - passwords[name] = parameter_defaults[name] - - return passwords + env_passwords[name] = parameter_defaults[name] + return env_passwords class GenerateFencingParametersAction(base.TripleOAction): @@ -479,14 +482,14 @@ class RotateFernetKeysAction(GetPasswordsAction): return mistral_workflow_utils.Result(error=err_msg) parameter_defaults = env.get('parameter_defaults', {}) - passwords = env.get('passwords', {}) - for name in constants.PASSWORD_PARAMETER_NAMES: - if name in parameter_defaults: - passwords[name] = parameter_defaults[name] + passwords = self._get_overriden_passwords(env.get('passwords', {}), + parameter_defaults) next_index = self.get_next_index(passwords['KeystoneFernetKeys']) keys_map = self.rotate_keys(passwords['KeystoneFernetKeys'], next_index) + max_keys = self.get_max_keys_value(parameter_defaults) + keys_map = self.purge_excess_keys(max_keys, keys_map) env['passwords']['KeystoneFernetKeys'] = keys_map @@ -503,14 +506,20 @@ class RotateFernetKeysAction(GetPasswordsAction): return keys_map + @staticmethod + def get_key_index_from_path(path): + return int(path[path.rfind('/') + 1:]) + def get_next_index(self, keys_map): - def get_index(path): - return int(path[path.rfind('/') + 1:]) - return get_index(max(keys_map, key=get_index)) + 1 + return self.get_key_index_from_path( + max(keys_map, key=self.get_key_index_from_path)) + 1 + + def get_key_path(self, index): + return password_utils.KEYSTONE_FERNET_REPO + str(index) def rotate_keys(self, keys_map, next_index): - next_index_path = password_utils.KEYSTONE_FERNET_REPO + str(next_index) - zero_index_path = password_utils.KEYSTONE_FERNET_REPO + '0' + next_index_path = self.get_key_path(next_index) + zero_index_path = self.get_key_path(0) # promote staged key to be new primary keys_map[next_index_path] = keys_map[zero_index_path] @@ -518,3 +527,20 @@ class RotateFernetKeysAction(GetPasswordsAction): keys_map[zero_index_path] = { 'content': password_utils.create_keystone_credential()} return keys_map + + def get_max_keys_value(self, parameter_defaults): + # The number of max keys should always be positive. The minimum amount + # of keys is 3. + return max(parameter_defaults.get('KeystoneFernetMaxActiveKeys', 5), 3) + + def purge_excess_keys(self, max_keys, keys_map): + current_repo_size = len(keys_map) + if current_repo_size <= max_keys: + return keys_map + key_paths = sorted(keys_map.keys(), key=self.get_key_index_from_path) + + keys_to_be_purged = current_repo_size - max_keys + + for key_path in key_paths[1:keys_to_be_purged + 1]: + del keys_map[key_path] + return keys_map diff --git a/tripleo_common/tests/actions/test_parameters.py b/tripleo_common/tests/actions/test_parameters.py index 5ecb5c8ea..835d441c3 100644 --- a/tripleo_common/tests/actions/test_parameters.py +++ b/tripleo_common/tests/actions/test_parameters.py @@ -917,3 +917,54 @@ class RotateFernetKeysActionTest(base.TestCase): # primary key should be the previous staged key self.assertEqual('Some key', new_keys_map[new_primary_key_index]['content']) + + def test_purge_excess_keys_should_purge(self): + action = parameters.RotateFernetKeysAction() + keys_map = { + password_utils.KEYSTONE_FERNET_REPO + '0': { + 'content': 'key0'}, + password_utils.KEYSTONE_FERNET_REPO + '1': { + 'content': 'key1'}, + password_utils.KEYSTONE_FERNET_REPO + '2': { + 'content': 'key2'}, + password_utils.KEYSTONE_FERNET_REPO + '3': { + 'content': 'key3'}, + password_utils.KEYSTONE_FERNET_REPO + '4': { + 'content': 'key4'}, + } + max_keys = 3 + keys_map = action.purge_excess_keys(max_keys, keys_map) + self.assertEqual(max_keys, len(keys_map)) + # It should keep index 0, 3 and 4 + self.assertIn(password_utils.KEYSTONE_FERNET_REPO + '0', keys_map) + self.assertIn(password_utils.KEYSTONE_FERNET_REPO + '3', keys_map) + self.assertIn(password_utils.KEYSTONE_FERNET_REPO + '4', keys_map) + # It sould have removed index 1 and 2 + self.assertNotIn(password_utils.KEYSTONE_FERNET_REPO + '1', keys_map) + self.assertNotIn(password_utils.KEYSTONE_FERNET_REPO + '2', keys_map) + + def test_purge_excess_keys_should_not_purge_if_equal_to_max(self): + action = parameters.RotateFernetKeysAction() + keys_map = { + password_utils.KEYSTONE_FERNET_REPO + '0': { + 'content': 'key0'}, + password_utils.KEYSTONE_FERNET_REPO + '1': { + 'content': 'key1'}, + password_utils.KEYSTONE_FERNET_REPO + '2': { + 'content': 'key2'}, + } + max_keys = 3 + keys_map = action.purge_excess_keys(max_keys, keys_map) + self.assertEqual(max_keys, len(keys_map)) + + def test_purge_excess_keys_should_not_purge_if_less_than_max(self): + action = parameters.RotateFernetKeysAction() + keys_map = { + password_utils.KEYSTONE_FERNET_REPO + '0': { + 'content': 'key0'}, + password_utils.KEYSTONE_FERNET_REPO + '1': { + 'content': 'key1'}, + } + max_keys = 3 + keys_map = action.purge_excess_keys(max_keys, keys_map) + self.assertEqual(2, len(keys_map))