Create mistral action to rotate fernet keys from passwords variable

This grabs the fernet keys from the passwords environment variable and
performs a rotation based on those keys. Subsequently, this parameter
will be passed to t-h-t in order for puppet to persist the rotation on a
stack update. Also, further work will be done in order for the deployer
to be able to do this without having to do a full stack update.

bp keystone-fernet-rotation
Change-Id: I18a3669e04021ad499973073a91e6bf78741ed20
This commit is contained in:
Juan Antonio Osorio Robles 2017-06-12 15:12:07 +03:00
parent 017a6da817
commit 4bc5c71a3e
3 changed files with 105 additions and 2 deletions

View File

@ -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

View File

@ -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'])

View File

@ -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()}
}