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
This commit is contained in:
Juan Antonio Osorio Robles 2017-06-15 15:58:27 +03:00
parent dea4a8264c
commit fcb992a6b9
2 changed files with 89 additions and 12 deletions

View File

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

View File

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