Merge "Pass key_repository and max_active_keys to FernetUtils"

This commit is contained in:
Jenkins 2016-08-16 19:20:31 +00:00 committed by Gerrit Code Review
commit d236f9c9ed
6 changed files with 89 additions and 51 deletions

View File

@ -525,7 +525,10 @@ class FernetSetup(BasePermissionsSetup):
@classmethod @classmethod
def main(cls): def main(cls):
from keystone.common import fernet_utils as utils 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() keystone_user_id, keystone_group_id = cls.get_user_group()
fernet_utils.create_key_directory(keystone_user_id, keystone_group_id) fernet_utils.create_key_directory(keystone_user_id, keystone_group_id)
@ -557,7 +560,10 @@ class FernetRotate(BasePermissionsSetup):
@classmethod @classmethod
def main(cls): def main(cls):
from keystone.common import fernet_utils as utils 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() keystone_user_id, keystone_group_id = cls.get_user_group()
if fernet_utils.validate_key_repository(requires_write=True): if fernet_utils.validate_key_repository(requires_write=True):

View File

@ -25,7 +25,10 @@ def symptom_usability_of_Fernet_key_repository():
keystone, but not world-readable, because it contains security-sensitive keystone, but not world-readable, because it contains security-sensitive
secrets. secrets.
""" """
fernet_utils = utils.FernetUtils() fernet_utils = utils.FernetUtils(
CONF.fernet_tokens.key_repository,
CONF.fernet_tokens.max_active_keys
)
return ( return (
'fernet' in CONF.token.provider 'fernet' in CONF.token.provider
and not fernet_utils.validate_key_repository()) 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 with keys, and periodically rotate your keys with `keystone-manage
fernet_rotate`. fernet_rotate`.
""" """
fernet_utils = utils.FernetUtils() fernet_utils = utils.FernetUtils(
CONF.fernet_tokens.key_repository,
CONF.fernet_tokens.max_active_keys
)
return ( return (
'fernet' in CONF.token.provider 'fernet' in CONF.token.provider
and not fernet_utils.load_keys()) and not fernet_utils.load_keys())

View File

@ -27,31 +27,35 @@ CONF = keystone.conf.CONF
class FernetUtils(object): 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): def validate_key_repository(self, requires_write=False):
"""Validate permissions on the key repository directory.""" """Validate permissions on the key repository directory."""
# NOTE(lbragstad): We shouldn't need to check if the directory was # 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. # passed in as None because we don't set allow_no_values to True.
# ensure current user has sufficient access to the key repository # ensure current user has sufficient access to the key repository
is_valid = (os.access(CONF.fernet_tokens.key_repository, os.R_OK) and is_valid = (os.access(self.key_repository, os.R_OK) and
os.access(CONF.fernet_tokens.key_repository, os.X_OK)) os.access(self.key_repository, os.X_OK))
if requires_write: if requires_write:
is_valid = (is_valid and 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: if not is_valid:
LOG.error( LOG.error(
_LE('Either [fernet_tokens] key_repository does not exist or ' _LE('Either [fernet_tokens] key_repository does not exist or '
'Keystone does not have sufficient permission to access ' 'Keystone does not have sufficient permission to access '
'it: %s'), CONF.fernet_tokens.key_repository) 'it: %s'), self.key_repository)
else: else:
# ensure the key repository isn't world-readable # 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 if(stat_info.st_mode & stat.S_IROTH or
stat_info.st_mode & stat.S_IXOTH): stat_info.st_mode & stat.S_IXOTH):
LOG.warning(_LW( LOG.warning(_LW(
'[fernet_tokens] key_repository is world readable: %s'), 'key_repository is world readable: %s'),
CONF.fernet_tokens.key_repository) self.key_repository)
return is_valid return is_valid
@ -73,30 +77,29 @@ class FernetUtils(object):
def create_key_directory(self, keystone_user_id=None, def create_key_directory(self, keystone_user_id=None,
keystone_group_id=None): keystone_group_id=None):
"""Attempt to create the key directory if it doesn't exist.""" """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( LOG.info(_LI(
'[fernet_tokens] key_repository does not appear to exist; ' 'key_repository does not appear to exist; attempting to '
'attempting to create it')) 'create it'))
try: try:
os.makedirs(CONF.fernet_tokens.key_repository, 0o700) os.makedirs(self.key_repository, 0o700)
except OSError: except OSError:
LOG.error(_LE( LOG.error(_LE(
'Failed to create [fernet_tokens] key_repository: either' 'Failed to create key_repository: either it already '
'it already exists or you don\'t have sufficient ' 'exists or you don\'t have sufficient permissions to '
'permissions to create it')) 'create it'))
if keystone_user_id and keystone_group_id: if keystone_user_id and keystone_group_id:
os.chown( os.chown(
CONF.fernet_tokens.key_repository, self.key_repository,
keystone_user_id, keystone_user_id,
keystone_group_id) keystone_group_id)
elif keystone_user_id or keystone_group_id: elif keystone_user_id or keystone_group_id:
LOG.warning(_LW( LOG.warning(_LW(
'Unable to change the ownership of [fernet_tokens] ' 'Unable to change the ownership of key_repository without '
'key_repository without a keystone user ID and keystone ' 'a keystone user ID and keystone group ID both being '
'group ID both being provided: %s') % 'provided: %s') % self.key_repository)
CONF.fernet_tokens.key_repository)
def _create_new_key(self, keystone_user_id, keystone_group_id): def _create_new_key(self, keystone_user_id, keystone_group_id):
"""Securely create a new encryption key. """Securely create a new encryption key.
@ -118,9 +121,9 @@ class FernetUtils(object):
'Unable to change the ownership of the new key without a ' 'Unable to change the ownership of the new key without a '
'keystone user ID and keystone group ID both being provided: ' 'keystone user ID and keystone group ID both being provided: '
'%s') % '%s') %
CONF.fernet_tokens.key_repository) self.key_repository)
# Determine the file name of the new key # 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: try:
with open(key_file, 'w') as f: with open(key_file, 'w') as f:
# convert key to str for the file. # convert key to str for the file.
@ -145,7 +148,7 @@ class FernetUtils(object):
""" """
# make sure we have work to do before proceeding # 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): os.F_OK):
LOG.info(_LI('Key repository is already initialized; aborting.')) LOG.info(_LI('Key repository is already initialized; aborting.'))
return return
@ -178,9 +181,8 @@ class FernetUtils(object):
""" """
# read the list of key files # read the list of key files
key_files = dict() key_files = dict()
for filename in os.listdir(CONF.fernet_tokens.key_repository): for filename in os.listdir(self.key_repository):
path = os.path.join(CONF.fernet_tokens.key_repository, path = os.path.join(self.key_repository, str(filename))
str(filename))
if os.path.isfile(path): if os.path.isfile(path):
try: try:
key_id = int(filename) key_id = int(filename)
@ -202,19 +204,19 @@ class FernetUtils(object):
# promote the next primary key to be the primary # promote the next primary key to be the primary
os.rename( os.rename(
os.path.join(CONF.fernet_tokens.key_repository, '0'), os.path.join(self.key_repository, '0'),
os.path.join(CONF.fernet_tokens.key_repository, os.path.join(self.key_repository, str(new_primary_key))
str(new_primary_key))) )
key_files.pop(0) key_files.pop(0)
key_files[new_primary_key] = os.path.join( key_files[new_primary_key] = os.path.join(
CONF.fernet_tokens.key_repository, self.key_repository,
str(new_primary_key)) str(new_primary_key))
LOG.info(_LI('Promoted key 0 to be the primary: %s'), 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 # add a new key to the rotation, which will be the *next* primary
self._create_new_key(keystone_user_id, keystone_group_id) 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 # purge excess keys
@ -240,9 +242,8 @@ class FernetUtils(object):
# build a dictionary of key_number:encryption_key pairs # build a dictionary of key_number:encryption_key pairs
keys = dict() keys = dict()
for filename in os.listdir(CONF.fernet_tokens.key_repository): for filename in os.listdir(self.key_repository):
path = os.path.join(CONF.fernet_tokens.key_repository, path = os.path.join(self.key_repository, str(filename))
str(filename))
if os.path.isfile(path): if os.path.isfile(path):
with open(path, 'r') as key_file: with open(path, 'r') as key_file:
try: try:
@ -253,7 +254,7 @@ class FernetUtils(object):
else: else:
keys[key_id] = key_file.read() 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 # If there haven't been enough key rotations to reach
# max_active_keys, or if the configured value of max_active_keys # max_active_keys, or if the configured value of max_active_keys
# has changed since the last rotation, then reporting the # 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) ' 'Loaded %(count)d encryption keys (max_active_keys=%(max)d) '
'from: %(dir)s'), { 'from: %(dir)s'), {
'count': len(keys), 'count': len(keys),
'max': CONF.fernet_tokens.max_active_keys, 'max': self.max_active_keys,
'dir': CONF.fernet_tokens.key_repository}) 'dir': self.key_repository})
# return the encryption_keys, sorted by key number, descending # return the encryption_keys, sorted by key number, descending
return [keys[x] for x in sorted(keys.keys(), reverse=True)] return [keys[x] for x in sorted(keys.keys(), reverse=True)]

View File

@ -13,6 +13,9 @@
import fixtures import fixtures
from keystone.common import fernet_utils as utils from keystone.common import fernet_utils as utils
import keystone.conf
CONF = keystone.conf.CONF
class KeyRepository(fixtures.Fixture): class KeyRepository(fixtures.Fixture):
@ -26,6 +29,9 @@ class KeyRepository(fixtures.Fixture):
self.config_fixture.config(group='fernet_tokens', self.config_fixture.config(group='fernet_tokens',
key_repository=directory) 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.create_key_directory()
fernet_utils.initialize_key_repository() fernet_utils.initialize_key_repository()

View File

@ -507,8 +507,11 @@ class TestFernetKeyRotation(unit.TestCase):
""" """
# Load the keys into a list, keys is list of six.text_type. # Load the keys into a list, keys is list of six.text_type.
utils = fernet_utils.FernetUtils() key_utils = fernet_utils.FernetUtils(
keys = utils.load_keys() 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 # Sort the list of keys by the keys themselves (they were previously
# sorted by filename). # sorted by filename).
@ -544,7 +547,6 @@ class TestFernetKeyRotation(unit.TestCase):
# support max_active_keys being set any lower. # support max_active_keys being set any lower.
min_active_keys = 2 min_active_keys = 2
utils = fernet_utils.FernetUtils()
# Simulate every rotation strategy up to "rotating once a week while # Simulate every rotation strategy up to "rotating once a week while
# maintaining a year's worth of keys." # maintaining a year's worth of keys."
for max_active_keys in range(min_active_keys, 52 + 1): 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 # Rotate the keys just enough times to fully populate the key
# repository. # 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): for rotation in range(max_active_keys - min_active_keys):
utils.rotate_keys() key_utils.rotate_keys()
self.assertRepositoryState(expected_size=rotation + 3) self.assertRepositoryState(expected_size=rotation + 3)
exp_keys.append(next_key_number) 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 # Rotate an additional number of times to ensure that we maintain
# the desired number of active keys. # 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): for rotation in range(10):
utils.rotate_keys() key_utils.rotate_keys()
self.assertRepositoryState(expected_size=max_active_keys) self.assertRepositoryState(expected_size=max_active_keys)
exp_keys.pop(1) exp_keys.pop(1)
@ -593,8 +603,11 @@ class TestFernetKeyRotation(unit.TestCase):
evil_file = os.path.join(CONF.fernet_tokens.key_repository, '99.bak') evil_file = os.path.join(CONF.fernet_tokens.key_repository, '99.bak')
with open(evil_file, 'w'): with open(evil_file, 'w'):
pass pass
utils = fernet_utils.FernetUtils() key_utils = fernet_utils.FernetUtils(
utils.rotate_keys() CONF.fernet_tokens.key_repository,
CONF.fernet_tokens.max_active_keys
)
key_utils.rotate_keys()
self.assertTrue(os.path.isfile(evil_file)) self.assertTrue(os.path.isfile(evil_file))
keys = 0 keys = 0
for x in os.listdir(CONF.fernet_tokens.key_repository): 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') evil_file = os.path.join(CONF.fernet_tokens.key_repository, '~1')
with open(evil_file, 'w'): with open(evil_file, 'w'):
pass pass
utils = fernet_utils.FernetUtils() key_utils = fernet_utils.FernetUtils(
keys = utils.load_keys() CONF.fernet_tokens.key_repository,
CONF.fernet_tokens.max_active_keys
)
keys = key_utils.load_keys()
self.assertEqual(2, len(keys)) self.assertEqual(2, len(keys))
self.assertTrue(len(keys[0])) self.assertTrue(len(keys[0]))

View File

@ -57,7 +57,10 @@ class TokenFormatter(object):
``encrypt(plaintext)`` and ``decrypt(ciphertext)``. ``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() keys = fernet_utils.load_keys()
if not keys: if not keys: