diff --git a/tripleo_common/actions/parameters.py b/tripleo_common/actions/parameters.py index 8e5c8ad88..08bc814cf 100644 --- a/tripleo_common/actions/parameters.py +++ b/tripleo_common/actions/parameters.py @@ -454,3 +454,67 @@ class GetProfileOfFlavorAction(base.TripleOAction): except exception.DeriveParamsError as err: LOG.error('Derive Params Error: %s', err) return mistral_workflow_utils.Result(error=str(err)) + + +class RotateFernetKeysAction(GetPasswordsAction): + """Rotate fernet keys from the environment + + This method rotates the fernet keys that are saved in the environment, in + the passwords parameter. + """ + + def __init__(self, container=constants.DEFAULT_CONTAINER_NAME): + super(RotateFernetKeysAction, self).__init__() + self.container = container + + def run(self, context): + swift = self.get_object_client(context) + + try: + env = plan_utils.get_env(swift, self.container) + except swiftexceptions.ClientException as err: + err_msg = ("Error retrieving environment for plan %s: %s" % ( + self.container, err)) + LOG.exception(err_msg) + 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] + + next_index = self.get_next_index(passwords['KeystoneFernetKeys']) + keys_map = self.rotate_keys(passwords['KeystoneFernetKeys'], + next_index) + + env['passwords']['KeystoneFernetKeys'] = keys_map + + try: + plan_utils.put_env(swift, env) + except swiftexceptions.ClientException as err: + err_msg = "Error uploading to container: %s" % err + LOG.exception(err_msg) + return mistral_workflow_utils.Result(error=err_msg) + + self.cache_delete(context, + self.container, + "tripleo.parameters.get") + + return keys_map + + 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 + + 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' + + # promote staged key to be new primary + keys_map[next_index_path] = keys_map[zero_index_path] + # Set new staged key + keys_map[zero_index_path] = { + 'content': password_utils.create_keystone_credential()} + return keys_map diff --git a/tripleo_common/tests/actions/test_parameters.py b/tripleo_common/tests/actions/test_parameters.py index f155eb2c2..5ecb5c8ea 100644 --- a/tripleo_common/tests/actions/test_parameters.py +++ b/tripleo_common/tests/actions/test_parameters.py @@ -21,6 +21,7 @@ from tripleo_common.actions import parameters from tripleo_common import constants from tripleo_common import exception from tripleo_common.tests import base +from tripleo_common.utils import passwords as password_utils _EXISTING_PASSWORDS = { 'MistralPassword': 'VFJeqBKbatYhQm9jja67hufft', @@ -879,3 +880,40 @@ class GetProfileOfFlavorActionTest(base.TestCase): result = action.run(mock_ctx) self.assertTrue(result.is_error()) mock_get_profile_of_flavor.assert_called_once() + + +class RotateFernetKeysActionTest(base.TestCase): + + def test_get_next_index(self): + action = parameters.RotateFernetKeysAction() + keys_map = { + password_utils.KEYSTONE_FERNET_REPO + '0': { + 'content': 'Some key'}, + password_utils.KEYSTONE_FERNET_REPO + '1': { + 'content': 'Some other key'}, + } + next_index = action.get_next_index(keys_map) + self.assertEqual(next_index, 2) + + @mock.patch('tripleo_common.utils.passwords.' + 'create_keystone_credential') + def test_rotate_keys(self, mock_keystone_creds): + action = parameters.RotateFernetKeysAction() + mock_keystone_creds.return_value = 'Some new key' + + staged_key_index = password_utils.KEYSTONE_FERNET_REPO + '0' + new_primary_key_index = password_utils.KEYSTONE_FERNET_REPO + '2' + keys_map = { + password_utils.KEYSTONE_FERNET_REPO + '0': { + 'content': 'Some key'}, + password_utils.KEYSTONE_FERNET_REPO + '1': { + 'content': 'Some other key'}, + } + new_keys_map = action.rotate_keys(keys_map, 2) + + # Staged key should be the new key + self.assertEqual('Some new key', + new_keys_map[staged_key_index]['content']) + # primary key should be the previous staged key + self.assertEqual('Some key', + new_keys_map[new_primary_key_index]['content']) diff --git a/tripleo_common/utils/passwords.py b/tripleo_common/utils/passwords.py index 37c9add8c..52da657f5 100644 --- a/tripleo_common/utils/passwords.py +++ b/tripleo_common/utils/passwords.py @@ -27,6 +27,7 @@ from tripleo_common import constants _MIN_PASSWORD_SIZE = 25 +KEYSTONE_FERNET_REPO = '/etc/keystone/fernet-keys/' LOG = logging.getLogger(__name__) @@ -80,9 +81,9 @@ def generate_passwords(mistralclient=None, stack_env=None): def create_fernet_keys_repo_structure_and_keys(): return { - '/etc/keystone/fernet-keys/0': { + KEYSTONE_FERNET_REPO + '0': { 'content': create_keystone_credential()}, - '/etc/keystone/fernet-keys/1': { + KEYSTONE_FERNET_REPO + '1': { 'content': create_keystone_credential()} }