Merge "Add key manager implementation with static key"
This commit is contained in:
commit
19b7d12e0e
@ -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")
|
||||
|
@ -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()
|
||||
|
137
cinder/keymgr/conf_key_mgr.py
Normal file
137
cinder/keymgr/conf_key_mgr.py
Normal 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)
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
||||
|
124
cinder/tests/keymgr/test_conf_key_mgr.py
Normal file
124
cinder/tests/keymgr/test_conf_key_mgr.py
Normal 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)
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user