Pass key_repository and max_active_keys to FernetUtils
This makes FernetUtils configurable for different things to user fernet utilities. This should help us use fernet for credential encryption. bp credential-encryption Change-Id: Ib4a7309844a2e03a5ca920b5512cf7efe1c97867
This commit is contained in:
parent
bc95434472
commit
c2d8451a12
|
@ -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):
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue