Merge "Move RotateFernetKeysAction functionality to utils"

This commit is contained in:
Zuul 2020-03-06 17:59:13 +00:00 committed by Gerrit Code Review
commit 163d4b3b4b
4 changed files with 171 additions and 160 deletions

View File

@ -330,78 +330,12 @@ class RotateFernetKeysAction(GetPasswordsAction):
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 actions.Result(error=err_msg)
parameter_defaults = env.get('parameter_defaults', {})
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
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 actions.Result(error=err_msg)
plan_utils.cache_delete(swift,
self.container,
"tripleo.parameters.get")
return keys_map
@staticmethod
def get_key_index_from_path(path):
return int(path[path.rfind('/') + 1:])
def get_next_index(self, keys_map):
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 = 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]
# Set new staged key
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
return plan_utils.update_plan_rotate_fernet_keys(
swift, self.container)
except Exception as err:
LOG.exception(six.text_typ(err))
return actions.Result(error=six.text_type(err))
class GetNetworkConfigAction(base.TripleOAction):

View File

@ -21,7 +21,6 @@ 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 = {
'PlacementPassword': 'VFJeqBKbatYhQm9jja67hufft',
@ -606,94 +605,6 @@ class GetProfileOfFlavorActionTest(base.TestCase):
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'])
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))
class GetNetworkConfigActionTest(base.TestCase):
@mock.patch('tripleo_common.utils.plan.'

View File

@ -21,6 +21,7 @@ import zlib
from swiftclient import exceptions as swiftexceptions
from tripleo_common.tests import base
from tripleo_common.utils import passwords as password_utils
from tripleo_common.utils import plan as plan_utils
@ -384,3 +385,83 @@ class PlanTest(base.TestCase):
cache_container,
cache_key
)
def test_get_next_index(self):
keys_map = {
password_utils.KEYSTONE_FERNET_REPO + '0': {
'content': 'Some key'},
password_utils.KEYSTONE_FERNET_REPO + '1': {
'content': 'Some other key'},
}
next_index = plan_utils.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):
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 = plan_utils.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'])
def test_purge_excess_keys_should_purge(self):
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 = plan_utils.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):
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 = plan_utils.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):
keys_map = {
password_utils.KEYSTONE_FERNET_REPO + '0': {
'content': 'key0'},
password_utils.KEYSTONE_FERNET_REPO + '1': {
'content': 'key1'},
}
max_keys = 3
keys_map = plan_utils.purge_excess_keys(max_keys, keys_map)
self.assertEqual(2, len(keys_map))

View File

@ -29,6 +29,7 @@ from swiftclient import exceptions as swiftexceptions
from tripleo_common import constants
from tripleo_common.image import kolla_builder
from tripleo_common.utils import passwords as password_utils
from tripleo_common.utils import swift as swiftutils
from tripleo_common.utils.validations import pattern_validator
@ -372,3 +373,87 @@ def update_plan_environment_with_image_parameters(
LOG.exception(err_msg)
raise RuntimeError(err_msg)
return env
def update_plan_rotate_fernet_keys(swift,
container=constants.DEFAULT_CONTAINER_NAME):
try:
env = get_env(swift, container)
except swiftexceptions.ClientException as err:
err_msg = ("Error retrieving environment for plan %s: %s" % (
container, err))
LOG.exception(err_msg)
raise RuntimeError(err_msg)
parameter_defaults = env.get('parameter_defaults', {})
passwords = get_overriden_passwords(env.get(
'passwords', {}), parameter_defaults)
next_index = get_next_index(passwords['KeystoneFernetKeys'])
keys_map = rotate_keys(passwords['KeystoneFernetKeys'],
next_index)
max_keys = get_max_keys_value(parameter_defaults)
keys_map = purge_excess_keys(max_keys, keys_map)
env['passwords']['KeystoneFernetKeys'] = keys_map
try:
put_env(swift, env)
except swiftexceptions.ClientException as err:
err_msg = "Error uploading to container: %s" % err
LOG.exception(err_msg)
raise RuntimeError(err_msg)
cache_delete(swift, container, "tripleo.parameters.get")
return keys_map
def get_overriden_passwords(env_passwords, parameter_defaults):
for name in constants.PASSWORD_PARAMETER_NAMES:
if name in parameter_defaults:
env_passwords[name] = parameter_defaults[name]
return env_passwords
def get_key_index_from_path(path):
return int(path[path.rfind('/') + 1:])
def get_next_index(keys_map):
return get_key_index_from_path(
max(keys_map, key=get_key_index_from_path)) + 1
def get_key_path(index):
return password_utils.KEYSTONE_FERNET_REPO + str(index)
def rotate_keys(keys_map, next_index):
next_index_path = get_key_path(next_index)
zero_index_path = get_key_path(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
def get_max_keys_value(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(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=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