diff --git a/keystone/cmd/cli.py b/keystone/cmd/cli.py index ad397ddb69..89a4028db2 100644 --- a/keystone/cmd/cli.py +++ b/keystone/cmd/cli.py @@ -525,7 +525,10 @@ class FernetSetup(BasePermissionsSetup): @classmethod def main(cls): from keystone.common import fernet_utils as utils - fernet_utils = utils.FernetUtils() + fernet_utils = utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) keystone_user_id, keystone_group_id = cls.get_user_group() fernet_utils.create_key_directory(keystone_user_id, keystone_group_id) @@ -557,7 +560,10 @@ class FernetRotate(BasePermissionsSetup): @classmethod def main(cls): from keystone.common import fernet_utils as utils - fernet_utils = utils.FernetUtils() + fernet_utils = utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) keystone_user_id, keystone_group_id = cls.get_user_group() if fernet_utils.validate_key_repository(requires_write=True): diff --git a/keystone/cmd/doctor/tokens_fernet.py b/keystone/cmd/doctor/tokens_fernet.py index e3561567d1..bf7d30db79 100644 --- a/keystone/cmd/doctor/tokens_fernet.py +++ b/keystone/cmd/doctor/tokens_fernet.py @@ -25,7 +25,10 @@ def symptom_usability_of_Fernet_key_repository(): keystone, but not world-readable, because it contains security-sensitive secrets. """ - fernet_utils = utils.FernetUtils() + fernet_utils = utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) return ( 'fernet' in CONF.token.provider and not fernet_utils.validate_key_repository()) @@ -39,7 +42,10 @@ def symptom_keys_in_Fernet_key_repository(): with keys, and periodically rotate your keys with `keystone-manage fernet_rotate`. """ - fernet_utils = utils.FernetUtils() + fernet_utils = utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) return ( 'fernet' in CONF.token.provider and not fernet_utils.load_keys()) diff --git a/keystone/common/fernet_utils.py b/keystone/common/fernet_utils.py index f8b926263f..4dc6f27356 100644 --- a/keystone/common/fernet_utils.py +++ b/keystone/common/fernet_utils.py @@ -27,31 +27,35 @@ CONF = keystone.conf.CONF class FernetUtils(object): + def __init__(self, key_repository=None, max_active_keys=None): + self.key_repository = key_repository + self.max_active_keys = max_active_keys + def validate_key_repository(self, requires_write=False): """Validate permissions on the key repository directory.""" # NOTE(lbragstad): We shouldn't need to check if the directory was # passed in as None because we don't set allow_no_values to True. # ensure current user has sufficient access to the key repository - is_valid = (os.access(CONF.fernet_tokens.key_repository, os.R_OK) and - os.access(CONF.fernet_tokens.key_repository, os.X_OK)) + is_valid = (os.access(self.key_repository, os.R_OK) and + os.access(self.key_repository, os.X_OK)) if requires_write: is_valid = (is_valid and - os.access(CONF.fernet_tokens.key_repository, os.W_OK)) + os.access(self.key_repository, os.W_OK)) if not is_valid: LOG.error( _LE('Either [fernet_tokens] key_repository does not exist or ' 'Keystone does not have sufficient permission to access ' - 'it: %s'), CONF.fernet_tokens.key_repository) + 'it: %s'), self.key_repository) else: # ensure the key repository isn't world-readable - stat_info = os.stat(CONF.fernet_tokens.key_repository) + stat_info = os.stat(self.key_repository) if(stat_info.st_mode & stat.S_IROTH or stat_info.st_mode & stat.S_IXOTH): LOG.warning(_LW( - '[fernet_tokens] key_repository is world readable: %s'), - CONF.fernet_tokens.key_repository) + 'key_repository is world readable: %s'), + self.key_repository) return is_valid @@ -73,30 +77,29 @@ class FernetUtils(object): def create_key_directory(self, keystone_user_id=None, keystone_group_id=None): """Attempt to create the key directory if it doesn't exist.""" - if not os.access(CONF.fernet_tokens.key_repository, os.F_OK): + if not os.access(self.key_repository, os.F_OK): LOG.info(_LI( - '[fernet_tokens] key_repository does not appear to exist; ' - 'attempting to create it')) + 'key_repository does not appear to exist; attempting to ' + 'create it')) try: - os.makedirs(CONF.fernet_tokens.key_repository, 0o700) + os.makedirs(self.key_repository, 0o700) except OSError: LOG.error(_LE( - 'Failed to create [fernet_tokens] key_repository: either' - 'it already exists or you don\'t have sufficient ' - 'permissions to create it')) + 'Failed to create key_repository: either it already ' + 'exists or you don\'t have sufficient permissions to ' + 'create it')) if keystone_user_id and keystone_group_id: os.chown( - CONF.fernet_tokens.key_repository, + self.key_repository, keystone_user_id, keystone_group_id) elif keystone_user_id or keystone_group_id: LOG.warning(_LW( - 'Unable to change the ownership of [fernet_tokens] ' - 'key_repository without a keystone user ID and keystone ' - 'group ID both being provided: %s') % - CONF.fernet_tokens.key_repository) + 'Unable to change the ownership of key_repository without ' + 'a keystone user ID and keystone group ID both being ' + 'provided: %s') % self.key_repository) def _create_new_key(self, keystone_user_id, keystone_group_id): """Securely create a new encryption key. @@ -118,9 +121,9 @@ class FernetUtils(object): 'Unable to change the ownership of the new key without a ' 'keystone user ID and keystone group ID both being provided: ' '%s') % - CONF.fernet_tokens.key_repository) + self.key_repository) # Determine the file name of the new key - key_file = os.path.join(CONF.fernet_tokens.key_repository, '0') + key_file = os.path.join(self.key_repository, '0') try: with open(key_file, 'w') as f: # convert key to str for the file. @@ -145,7 +148,7 @@ class FernetUtils(object): """ # make sure we have work to do before proceeding - if os.access(os.path.join(CONF.fernet_tokens.key_repository, '0'), + if os.access(os.path.join(self.key_repository, '0'), os.F_OK): LOG.info(_LI('Key repository is already initialized; aborting.')) return @@ -178,9 +181,8 @@ class FernetUtils(object): """ # read the list of key files key_files = dict() - for filename in os.listdir(CONF.fernet_tokens.key_repository): - path = os.path.join(CONF.fernet_tokens.key_repository, - str(filename)) + for filename in os.listdir(self.key_repository): + path = os.path.join(self.key_repository, str(filename)) if os.path.isfile(path): try: key_id = int(filename) @@ -202,19 +204,19 @@ class FernetUtils(object): # promote the next primary key to be the primary os.rename( - os.path.join(CONF.fernet_tokens.key_repository, '0'), - os.path.join(CONF.fernet_tokens.key_repository, - str(new_primary_key))) + os.path.join(self.key_repository, '0'), + os.path.join(self.key_repository, str(new_primary_key)) + ) key_files.pop(0) key_files[new_primary_key] = os.path.join( - CONF.fernet_tokens.key_repository, + self.key_repository, str(new_primary_key)) LOG.info(_LI('Promoted key 0 to be the primary: %s'), new_primary_key) # add a new key to the rotation, which will be the *next* primary self._create_new_key(keystone_user_id, keystone_group_id) - max_active_keys = CONF.fernet_tokens.max_active_keys + max_active_keys = self.max_active_keys # purge excess keys @@ -240,9 +242,8 @@ class FernetUtils(object): # build a dictionary of key_number:encryption_key pairs keys = dict() - for filename in os.listdir(CONF.fernet_tokens.key_repository): - path = os.path.join(CONF.fernet_tokens.key_repository, - str(filename)) + for filename in os.listdir(self.key_repository): + path = os.path.join(self.key_repository, str(filename)) if os.path.isfile(path): with open(path, 'r') as key_file: try: @@ -253,7 +254,7 @@ class FernetUtils(object): else: keys[key_id] = key_file.read() - if len(keys) != CONF.fernet_tokens.max_active_keys: + if len(keys) != self.max_active_keys: # If there haven't been enough key rotations to reach # max_active_keys, or if the configured value of max_active_keys # has changed since the last rotation, then reporting the @@ -263,8 +264,8 @@ class FernetUtils(object): 'Loaded %(count)d encryption keys (max_active_keys=%(max)d) ' 'from: %(dir)s'), { 'count': len(keys), - 'max': CONF.fernet_tokens.max_active_keys, - 'dir': CONF.fernet_tokens.key_repository}) + 'max': self.max_active_keys, + 'dir': self.key_repository}) # return the encryption_keys, sorted by key number, descending return [keys[x] for x in sorted(keys.keys(), reverse=True)] diff --git a/keystone/tests/unit/ksfixtures/key_repository.py b/keystone/tests/unit/ksfixtures/key_repository.py index 2726d0daf8..e5657ddbb2 100644 --- a/keystone/tests/unit/ksfixtures/key_repository.py +++ b/keystone/tests/unit/ksfixtures/key_repository.py @@ -13,6 +13,9 @@ import fixtures from keystone.common import fernet_utils as utils +import keystone.conf + +CONF = keystone.conf.CONF class KeyRepository(fixtures.Fixture): @@ -26,6 +29,9 @@ class KeyRepository(fixtures.Fixture): self.config_fixture.config(group='fernet_tokens', key_repository=directory) - fernet_utils = utils.FernetUtils() + fernet_utils = utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) fernet_utils.create_key_directory() fernet_utils.initialize_key_repository() diff --git a/keystone/tests/unit/token/test_fernet_provider.py b/keystone/tests/unit/token/test_fernet_provider.py index a7089f5722..7389a04252 100644 --- a/keystone/tests/unit/token/test_fernet_provider.py +++ b/keystone/tests/unit/token/test_fernet_provider.py @@ -507,8 +507,11 @@ class TestFernetKeyRotation(unit.TestCase): """ # Load the keys into a list, keys is list of six.text_type. - utils = fernet_utils.FernetUtils() - keys = utils.load_keys() + key_utils = fernet_utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) + keys = key_utils.load_keys() # Sort the list of keys by the keys themselves (they were previously # sorted by filename). @@ -544,7 +547,6 @@ class TestFernetKeyRotation(unit.TestCase): # support max_active_keys being set any lower. min_active_keys = 2 - utils = fernet_utils.FernetUtils() # Simulate every rotation strategy up to "rotating once a week while # maintaining a year's worth of keys." for max_active_keys in range(min_active_keys, 52 + 1): @@ -566,8 +568,12 @@ class TestFernetKeyRotation(unit.TestCase): # Rotate the keys just enough times to fully populate the key # repository. + key_utils = fernet_utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) for rotation in range(max_active_keys - min_active_keys): - utils.rotate_keys() + key_utils.rotate_keys() self.assertRepositoryState(expected_size=rotation + 3) exp_keys.append(next_key_number) @@ -579,8 +585,12 @@ class TestFernetKeyRotation(unit.TestCase): # Rotate an additional number of times to ensure that we maintain # the desired number of active keys. + key_utils = fernet_utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) for rotation in range(10): - utils.rotate_keys() + key_utils.rotate_keys() self.assertRepositoryState(expected_size=max_active_keys) exp_keys.pop(1) @@ -593,8 +603,11 @@ class TestFernetKeyRotation(unit.TestCase): evil_file = os.path.join(CONF.fernet_tokens.key_repository, '99.bak') with open(evil_file, 'w'): pass - utils = fernet_utils.FernetUtils() - utils.rotate_keys() + key_utils = fernet_utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) + key_utils.rotate_keys() self.assertTrue(os.path.isfile(evil_file)) keys = 0 for x in os.listdir(CONF.fernet_tokens.key_repository): @@ -610,7 +623,10 @@ class TestLoadKeys(unit.TestCase): evil_file = os.path.join(CONF.fernet_tokens.key_repository, '~1') with open(evil_file, 'w'): pass - utils = fernet_utils.FernetUtils() - keys = utils.load_keys() + key_utils = fernet_utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) + keys = key_utils.load_keys() self.assertEqual(2, len(keys)) self.assertTrue(len(keys[0])) diff --git a/keystone/token/providers/fernet/token_formatters.py b/keystone/token/providers/fernet/token_formatters.py index 7353c42127..acc1714801 100644 --- a/keystone/token/providers/fernet/token_formatters.py +++ b/keystone/token/providers/fernet/token_formatters.py @@ -57,7 +57,10 @@ class TokenFormatter(object): ``encrypt(plaintext)`` and ``decrypt(ciphertext)``. """ - fernet_utils = utils.FernetUtils() + fernet_utils = utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) keys = fernet_utils.load_keys() if not keys: