Merge "Add key manager implementation with static key"

This commit is contained in:
Jenkins 2013-10-03 04:22:42 +00:00 committed by Gerrit Code Review
commit 19b7d12e0e
10 changed files with 335 additions and 32 deletions

View File

@ -637,3 +637,7 @@ class InvalidQoSSpecs(Invalid):
class QoSSpecsInUse(CinderException):
message = _("QoS Specs %(specs_id)s is still associated with entities.")
class KeyManagerError(CinderException):
msg_fmt = _("key manager error: %(reason)s")

View File

@ -17,22 +17,17 @@
from oslo.config import cfg
from cinder.openstack.common import importutils
from cinder.openstack.common import log as logging
keymgr_opts = [
cfg.StrOpt('keymgr_api_class',
default='cinder.keymgr.'
'not_implemented_key_mgr.NotImplementedKeyManager',
cfg.StrOpt('api_class',
default='cinder.keymgr.conf_key_mgr.ConfKeyManager',
help='The full class name of the key manager API class'),
]
CONF = cfg.CONF
CONF.register_opts(keymgr_opts)
LOG = logging.getLogger(__name__)
CONF.register_opts(keymgr_opts, group='keymgr')
def API():
keymgr_api_class = CONF.keymgr_api_class
cls = importutils.import_class(keymgr_api_class)
cls = importutils.import_class(CONF.keymgr.api_class)
return cls()

View File

@ -0,0 +1,137 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
An implementation of a key manager that reads its key from the project's
configuration options.
This key manager implementation provides limited security, assuming that the
key remains secret. Using the volume encryption feature as an example,
encryption provides protection against a lost or stolen disk, assuming that
the configuration file that contains the key is not stored on the disk.
Encryption also protects the confidentiality of data as it is transmitted via
iSCSI from the compute host to the storage host (again assuming that an
attacker who intercepts the data does not know the secret key).
Because this implementation uses a single, fixed key, it proffers no
protection once that key is compromised. In particular, different volumes
encrypted with a key provided by this key manager actually share the same
encryption key so *any* volume can be decrypted once the fixed key is known.
"""
import array
from oslo.config import cfg
from cinder import exception
from cinder.keymgr import key
from cinder.keymgr import key_mgr
from cinder.openstack.common.gettextutils import _
from cinder.openstack.common import log as logging
key_mgr_opts = [
cfg.StrOpt('fixed_key',
help='Fixed key returned by key manager, specified in hex'),
]
CONF = cfg.CONF
CONF.register_opts(key_mgr_opts, group='keymgr')
LOG = logging.getLogger(__name__)
class ConfKeyManager(key_mgr.KeyManager):
"""
This key manager implementation supports all the methods specified by the
key manager interface. This implementation creates a single key in response
to all invocations of create_key. Side effects (e.g., raising exceptions)
for each method are handled as specified by the key manager interface.
"""
def __init__(self):
LOG.warn(_('This key manager is insecure and is not recommended for '
'production deployments'))
super(ConfKeyManager, self).__init__()
self.key_id = '00000000-0000-0000-0000-000000000000'
if CONF.keymgr.fixed_key is None:
LOG.warn(_('config option keymgr.fixed_key has not been defined: '
'some operations may fail unexpectedly'))
def _generate_key(self, **kwargs):
_hex = self._generate_hex_key(**kwargs)
return key.SymmetricKey('AES',
array.array('B', _hex.decode('hex')).tolist())
def _generate_hex_key(self, **kwargs):
if CONF.keymgr.fixed_key is None:
raise ValueError(_('keymgr.fixed_key not defined'))
return CONF.keymgr.fixed_key
def create_key(self, ctxt, **kwargs):
"""Creates a key.
This implementation returns a UUID for the created key. A
NotAuthorized exception is raised if the specified context is None.
"""
if ctxt is None:
raise exception.NotAuthorized()
return self.key_id
def store_key(self, ctxt, key, **kwargs):
"""Stores (i.e., registers) a key with the key manager."""
if ctxt is None:
raise exception.NotAuthorized()
if key != self._generate_key():
raise exception.KeyManagerError(
reason="cannot store arbitrary keys")
return self.key_id
def copy_key(self, ctxt, key_id, **kwargs):
if ctxt is None:
raise exception.NotAuthorized()
return self.key_id
def get_key(self, ctxt, key_id, **kwargs):
"""Retrieves the key identified by the specified id.
This implementation returns the key that is associated with the
specified UUID. A NotAuthorized exception is raised if the specified
context is None; a KeyError is raised if the UUID is invalid.
"""
if ctxt is None:
raise exception.NotAuthorized()
if key_id != self.key_id:
raise KeyError(key_id)
return self._generate_key()
def delete_key(self, ctxt, key_id, **kwargs):
if ctxt is None:
raise exception.NotAuthorized()
if key_id != self.key_id:
raise exception.KeyManagerError(
reason="cannot delete non-existent key")
LOG.warn(_("Not deleting key %s"), key_id)

View File

@ -79,3 +79,15 @@ class SymmetricKey(Key):
def get_encoded(self):
"""Returns the key in its encoded format."""
return self.key
def __eq__(self, other):
if isinstance(other, SymmetricKey):
return (self.alg == other.alg and
self.key == other.key)
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return result
return not result

View File

@ -48,3 +48,6 @@ def set_defaults(conf):
'xiv_ds8k_proxy',
'cinder.tests.test_xiv_ds8k.XIVDS8KFakeProxyDriver')
conf.set_default('backup_driver', 'cinder.tests.backup.fake_service')
# NOTE(joel-coffman): This option for the ConfKeyManager must be set or
# else the ConfKeyManager cannot be instantiated.
conf.set_default('fixed_key', default='0' * 64, group='keymgr')

View File

@ -15,8 +15,16 @@
# under the License.
"""
A mock implementation of a key manager. This module should NOT be used for
anything but integration testing.
A mock implementation of a key manager that stores keys in a dictionary.
This key manager implementation is primarily intended for testing. In
particular, it does not store keys persistently. Lack of a centralized key
store also makes this implementation unsuitable for use among different
services.
Note: Instantiating this class multiple times will create separate key stores.
Keys created in one instance will not be accessible from other instances of
this class.
"""
import array
@ -24,16 +32,11 @@ import array
from cinder import exception
from cinder.keymgr import key
from cinder.keymgr import key_mgr
from cinder.openstack.common import log as logging
from cinder.openstack.common import uuidutils
from cinder import utils
LOG = logging.getLogger(__name__)
class MockKeyManager(key_mgr.KeyManager):
"""
This mock key manager implementation supports all the methods specified
by the key manager interface. This implementation stores keys within a
@ -41,13 +44,24 @@ class MockKeyManager(key_mgr.KeyManager):
services. Side effects (e.g., raising exceptions) for each method are
handled as specified by the key manager interface.
This class should NOT be used for anything but integration testing because
keys are not stored persistently.
This key manager is not suitable for use in production deployments.
"""
def __init__(self):
self.keys = {}
def _generate_hex_key(self, **kwargs):
key_length = kwargs.get('key_length', 256)
# hex digit => 4 bits
hex_encoded = utils.generate_password(length=key_length / 4,
symbolgroups='0123456789ABCDEF')
return hex_encoded
def _generate_key(self, **kwargs):
_hex = self._generate_hex_key(**kwargs)
return key.SymmetricKey('AES',
array.array('B', _hex.decode('hex')).tolist())
def create_key(self, ctxt, **kwargs):
"""Creates a key.
@ -57,16 +71,8 @@ class MockKeyManager(key_mgr.KeyManager):
if ctxt is None:
raise exception.NotAuthorized()
# generate the key
key_length = kwargs.get('key_length', 256)
# hex digit => 4 bits
hex_string = utils.generate_password(length=key_length / 4,
symbolgroups='0123456789ABCDEF')
_bytes = array.array('B', hex_string.decode('hex')).tolist()
_key = key.SymmetricKey('AES', _bytes)
return self.store_key(ctxt, _key)
key = self._generate_key(**kwargs)
return self.store_key(ctxt, key)
def _generate_key_id(self):
key_id = uuidutils.generate_uuid()
@ -76,8 +82,7 @@ class MockKeyManager(key_mgr.KeyManager):
return key_id
def store_key(self, ctxt, key, **kwargs):
"""Stores (i.e., registers) a key with the key manager.
"""
"""Stores (i.e., registers) a key with the key manager."""
if ctxt is None:
raise exception.NotAuthorized()

View File

@ -0,0 +1,124 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Test cases for the conf key manager.
"""
import array
from oslo.config import cfg
from cinder import context
from cinder import exception
from cinder.keymgr import conf_key_mgr
from cinder.keymgr import key
from cinder.tests.keymgr import test_key_mgr
CONF = cfg.CONF
CONF.import_opt('fixed_key', 'cinder.keymgr.conf_key_mgr', group='keymgr')
class ConfKeyManagerTestCase(test_key_mgr.KeyManagerTestCase):
def __init__(self, *args, **kwargs):
super(ConfKeyManagerTestCase, self).__init__(*args, **kwargs)
self._hex_key = '1' * 64
def _create_key_manager(self):
CONF.set_default('fixed_key', default=self._hex_key, group='keymgr')
return conf_key_mgr.ConfKeyManager()
def setUp(self):
super(ConfKeyManagerTestCase, self).setUp()
self.ctxt = context.RequestContext('fake', 'fake')
self.key_id = '00000000-0000-0000-0000-000000000000'
encoded = array.array('B', self._hex_key.decode('hex')).tolist()
self.key = key.SymmetricKey('AES', encoded)
def test___init__(self):
self.assertEqual(self.key_id, self.key_mgr.key_id)
def test_create_key(self):
key_id_1 = self.key_mgr.create_key(self.ctxt)
key_id_2 = self.key_mgr.create_key(self.ctxt)
# ensure that the UUIDs are the same
self.assertEqual(key_id_1, key_id_2)
def test_create_null_context(self):
self.assertRaises(exception.NotAuthorized,
self.key_mgr.create_key, None)
def test_store_key(self):
key_id = self.key_mgr.store_key(self.ctxt, self.key)
actual_key = self.key_mgr.get_key(self.ctxt, key_id)
self.assertEqual(self.key, actual_key)
def test_store_null_context(self):
self.assertRaises(exception.NotAuthorized,
self.key_mgr.store_key, None, self.key)
def test_store_key_invalid(self):
encoded = self.key.get_encoded()
inverse_key = key.SymmetricKey('AES', [~b for b in encoded])
self.assertRaises(exception.KeyManagerError,
self.key_mgr.store_key, self.ctxt, inverse_key)
def test_copy_key(self):
key_id = self.key_mgr.create_key(self.ctxt)
key = self.key_mgr.get_key(self.ctxt, key_id)
copied_key_id = self.key_mgr.copy_key(self.ctxt, key_id)
copied_key = self.key_mgr.get_key(self.ctxt, copied_key_id)
self.assertEqual(key_id, copied_key_id)
self.assertEqual(key, copied_key)
def test_copy_null_context(self):
self.assertRaises(exception.NotAuthorized,
self.key_mgr.copy_key, None, None)
def test_delete_key(self):
key_id = self.key_mgr.create_key(self.ctxt)
self.key_mgr.delete_key(self.ctxt, key_id)
# cannot delete key -- might have lingering references
self.assertEqual(self.key,
self.key_mgr.get_key(self.ctxt, self.key_id))
def test_delete_null_context(self):
self.assertRaises(exception.NotAuthorized,
self.key_mgr.delete_key, None, None)
def test_delete_unknown_key(self):
self.assertRaises(exception.KeyManagerError,
self.key_mgr.delete_key, self.ctxt, None)
def test_get_key(self):
self.assertEqual(self.key,
self.key_mgr.get_key(self.ctxt, self.key_id))
def test_get_null_context(self):
self.assertRaises(exception.NotAuthorized,
self.key_mgr.get_key, None, None)
def test_get_unknown_key(self):
self.assertRaises(KeyError, self.key_mgr.get_key, self.ctxt, None)

View File

@ -55,3 +55,15 @@ class SymmetricKeyTestCase(KeyTestCase):
def test_get_encoded(self):
self.assertEqual(self.key.get_encoded(), self.encoded)
def test___eq__(self):
self.assertTrue(self.key == self.key)
self.assertFalse(self.key == None)
self.assertFalse(None == self.key)
def test___ne__(self):
self.assertFalse(self.key != self.key)
self.assertTrue(self.key != None)
self.assertTrue(None != self.key)

View File

@ -23,6 +23,8 @@ from cinder import test
class KeyManagerTestCase(test.TestCase):
def __init__(self, *args, **kwargs):
super(KeyManagerTestCase, self).__init__(*args, **kwargs)
def _create_key_manager(self):
raise NotImplementedError()

View File

@ -509,7 +509,16 @@
# The full class name of the key manager API class (string
# value)
#keymgr_api_class=cinder.keymgr.not_implemented_key_mgr.NotImplementedKeyManager
#api_class=cinder.keymgr.conf_key_mgr.ConfKeyManager
#
# Options defined in cinder.keymgr.conf_key_mgr
#
# Fixed key returned by key manager, specified in hex (string
# value)
#fixed_key=<None>
#
@ -1769,4 +1778,4 @@
#volume_dd_blocksize=1M
# Total option count: 381
# Total option count: 382