Handle empty token key files

In some rare cases, an empty key file can get created within the fernet
key repository. When keystone tries to load the keys from disk, it will
fail with an invalid fernet key ValueError.

This change adds a check for empty files with a valid numerical name
within the key repository when rotating keys and loading keys. If an
empty file exists, it will be ignored when loading keys, reported in the
logs, and overwritten with a valid key upon rotation.

Change-Id: Ic19dd02d38e8f6a05c8951ec3dd13659aab98259
Closes-Bug: 1728907
This commit is contained in:
Gage Hugo 2018-02-21 15:45:17 -06:00
parent 7671be8518
commit f2a210e3fe
3 changed files with 64 additions and 22 deletions

View File

@ -167,6 +167,27 @@ class TokenUtils(object):
LOG.info('Become a valid new key: %s', valid_key_file)
def _get_key_files(self, key_repo):
key_files = dict()
keys = dict()
for filename in os.listdir(key_repo):
path = os.path.join(key_repo, str(filename))
if os.path.isfile(path):
with open(path, 'r') as key_file:
try:
key_id = int(filename)
except ValueError: # nosec : name is not a number
pass
else:
key = key_file.read()
if len(key) == 0:
LOG.warning('Ignoring empty key found in key '
'repository: %s', path)
continue
key_files[key_id] = path
keys[key_id] = key
return key_files, keys
def initialize_key_repository(self, keystone_user_id=None,
keystone_group_id=None):
"""Create a key repository and bootstrap it with a key.
@ -208,16 +229,7 @@ class TokenUtils(object):
"""
# read the list of key files
key_files = dict()
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)
except ValueError: # nosec : name isn't a number
pass
else:
key_files[key_id] = path
key_files, _ = self._get_key_files(self.key_repository)
LOG.info('Starting key rotation with %(count)s key files: '
'%(list)s', {
@ -277,18 +289,7 @@ class TokenUtils(object):
return []
# build a dictionary of key_number:encryption_key pairs
keys = dict()
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:
key_id = int(filename)
except ValueError: # nosec : filename isn't a number,
# ignore this file since it's not a key.
pass
else:
keys[key_id] = key_file.read()
_, keys = self._get_key_files(self.key_repository)
if len(keys) != self.max_active_keys:
# Once the number of keys matches max_active_keys, this log entry

View File

@ -634,6 +634,24 @@ class TestFernetKeyRotation(unit.TestCase):
# Assert that the key repository is now expanded.
self.assertEqual(self.key_repository_size, 3)
def test_rotation_empty_file(self):
active_keys = 2
self.assertRepositoryState(expected_size=active_keys)
empty_file = os.path.join(CONF.fernet_tokens.key_repository, '2')
with open(empty_file, 'w'):
pass
key_utils = token_utils.TokenUtils(
CONF.fernet_tokens.key_repository,
CONF.fernet_tokens.max_active_keys,
'fernet_tokens'
)
# Rotate the keys to overwrite the empty file
key_utils.rotate_keys()
self.assertTrue(os.path.isfile(empty_file))
keys = key_utils.load_keys()
self.assertEqual(3, len(keys))
self.assertTrue(os.path.getsize(empty_file) > 0)
def test_non_numeric_files(self):
evil_file = os.path.join(CONF.fernet_tokens.key_repository, '99.bak')
with open(evil_file, 'w'):
@ -673,3 +691,16 @@ class TestLoadKeys(unit.TestCase):
keys = key_utils.load_keys()
self.assertEqual(2, len(keys))
self.assertValidFernetKeys(keys)
def test_empty_files(self):
empty_file = os.path.join(CONF.fernet_tokens.key_repository, '2')
with open(empty_file, 'w'):
pass
key_utils = token_utils.TokenUtils(
CONF.fernet_tokens.key_repository,
CONF.fernet_tokens.max_active_keys,
'fernet_tokens'
)
keys = key_utils.load_keys()
self.assertEqual(2, len(keys))
self.assertValidFernetKeys(keys)

View File

@ -0,0 +1,10 @@
---
fixes:
- |
[`bug 1728907 <https://bugs.launchpad.net/keystone/+bug/1728907>`_]
In some rare cases, an empty key file can get created within the fernet
key repository. When keystone tries to load the keys from disk, it will
fail with an invalid fernet key ValueError. Keystone now handles empty
key files when loading and rotating keys. If an empty file exists, it
will be ignored when loaded, reported as a warning in the log, and
overwritten with a valid key upon rotation.