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
|
@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):
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue