Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
Cloud:OpenStack:Newton:Staging
openstack-keystone
0001-Handle-disk-write-failure-when-doing-Ferne...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 0001-Handle-disk-write-failure-when-doing-Fernet-key-rota.patch of Package openstack-keystone
From 5b9d754d50b2b723884005b7111ad51d9d8755b7 Mon Sep 17 00:00:00 2001 From: johnlinp <johnlinp@gmail.com> Date: Wed, 21 Dec 2016 15:17:01 +0800 Subject: [PATCH] Handle disk write failure when doing Fernet key rotation _create_new_key() is broke down into 2 parts: 1. _create_tmp_new_key() 2. _become_valid_new_key() This can avoid empty Fernet keys when the write to the staged key fails. The _become_valid_new_key() is called only after a successful call to _create_tmp_new_key(). Change-Id: Iaf33e2b291f13b9eb9464ef345a8664a634121ff Closes-Bug: #1642457 Signed-off-by: John Lin <johnlinp@gmail.com> (cherry picked from commit 5b7c9a66f0aed860ea0776d4c5b42710d88fcb5f) --- keystone/common/fernet_utils.py | 43 +++++++++++++++++-- .../tests/unit/token/test_fernet_provider.py | 30 +++++++++++++ .../notes/bug-1642457-4533f9810a8cd927.yaml | 7 +++ 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1642457-4533f9810a8cd927.yaml diff --git a/keystone/common/fernet_utils.py b/keystone/common/fernet_utils.py index cb7a69863..d567a85e6 100644 --- a/keystone/common/fernet_utils.py +++ b/keystone/common/fernet_utils.py @@ -100,6 +100,17 @@ class FernetUtils(object): Create a new key that is readable by the Keystone group and Keystone user. + + To avoid disk write failure, this function will create a tmp key file + first, and then rename it as the valid new key. + """ + self._create_tmp_new_key(keystone_user_id, keystone_group_id) + self._become_valid_new_key() + + def _create_tmp_new_key(self, keystone_user_id, keystone_group_id): + """Securely create a new tmp encryption key. + + This created key is not effective until _become_valid_new_key(). """ key = fernet.Fernet.generate_key() # key is bytes @@ -117,11 +128,17 @@ class FernetUtils(object): '%s') % self.key_repository) # Determine the file name of the new key - key_file = os.path.join(self.key_repository, '0') + key_file = os.path.join(self.key_repository, '0.tmp') + create_success = False try: with open(key_file, 'w') as f: # convert key to str for the file. f.write(key.decode('utf-8')) + f.flush() + create_success = True + except IOError: + LOG.error(_LE('Failed to create new temporary key: %s'), key_file) + raise finally: # After writing the key, set the umask back to it's original value. # Do the same with group and user identifiers if a Keystone group @@ -130,8 +147,23 @@ class FernetUtils(object): if keystone_user_id and keystone_group_id: os.seteuid(old_euid) os.setegid(old_egid) + # Deal with the tmp key file + if not create_success and os.access(key_file, os.F_OK): + os.remove(key_file) + + LOG.info(_LI('Created a new temporary key: %s'), key_file) - LOG.info(_LI('Created a new key: %s'), key_file) + def _become_valid_new_key(self): + """Make the tmp new key a valid new key. + + The tmp new key must be created by _create_tmp_new_key(). + """ + tmp_key_file = os.path.join(self.key_repository, '0.tmp') + valid_key_file = os.path.join(self.key_repository, '0') + + os.rename(tmp_key_file, valid_key_file) + + LOG.info(_LI('Become a valid new key: %s'), valid_key_file) def initialize_key_repository(self, keystone_user_id=None, keystone_group_id=None): @@ -190,6 +222,9 @@ class FernetUtils(object): 'count': len(key_files), 'list': list(key_files.values())}) + # add a tmp new key to the rotation, which will be the *next* primary + self._create_tmp_new_key(keystone_user_id, keystone_group_id) + # determine the number of the new primary key current_primary_key = max(key_files.keys()) LOG.info(_LI('Current primary key is: %s'), current_primary_key) @@ -207,8 +242,8 @@ class FernetUtils(object): 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) + # rename the tmp key to the real staged key + self._become_valid_new_key() max_active_keys = self.max_active_keys diff --git a/keystone/tests/unit/token/test_fernet_provider.py b/keystone/tests/unit/token/test_fernet_provider.py index d99330e45..117a372ca 100644 --- a/keystone/tests/unit/token/test_fernet_provider.py +++ b/keystone/tests/unit/token/test_fernet_provider.py @@ -13,6 +13,7 @@ import base64 import datetime import hashlib +import mock import os import uuid @@ -622,6 +623,35 @@ class TestFernetKeyRotation(unit.TestCase): next_key_number += 1 self.assertEqual(exp_keys, self.keys) + def test_rotation_disk_write_fail(self): + # Init the key repository + self.useFixture( + ksfixtures.KeyRepository( + self.config_fixture, + 'fernet_tokens', + CONF.fernet_tokens.max_active_keys + ) + ) + + # Make sure that the init key repository contains 2 keys + self.assertRepositoryState(expected_size=2) + + key_utils = fernet_utils.FernetUtils( + CONF.fernet_tokens.key_repository, + CONF.fernet_tokens.max_active_keys + ) + + # Simulate the disk full situation + mock_open = mock.mock_open() + file_handle = mock_open() + file_handle.flush.side_effect = IOError('disk full') + + with mock.patch('keystone.common.fernet_utils.open', mock_open): + self.assertRaises(IOError, key_utils.rotate_keys) + + # Assert that the key repository is unchanged + self.assertEqual(self.key_repository_size, 2) + def test_non_numeric_files(self): self.useFixture( ksfixtures.KeyRepository( diff --git a/releasenotes/notes/bug-1642457-4533f9810a8cd927.yaml b/releasenotes/notes/bug-1642457-4533f9810a8cd927.yaml new file mode 100644 index 000000000..4d1d5ff95 --- /dev/null +++ b/releasenotes/notes/bug-1642457-4533f9810a8cd927.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + [`bug 1642457 <https://bugs.launchpad.net/keystone/+bug/1642457>`_] + Handle disk write and IO failures when rotating keys for Fernet tokens. + Rather than creating empty keys, properly catch and log errors when + unable to write to disk. -- 2.18.0
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor