class QoSSpecsInUse(CinderException):
message = _("QoS Specs %(specs_id)s is still associated with entities.")
+
+
+class KeyManagerError(CinderException):
+ msg_fmt = _("key manager error: %(reason)s")
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()
--- /dev/null
+# 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)
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
'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')
# 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
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
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.
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()
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()
--- /dev/null
+# 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)
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)
class KeyManagerTestCase(test.TestCase):
+ def __init__(self, *args, **kwargs):
+ super(KeyManagerTestCase, self).__init__(*args, **kwargs)
def _create_key_manager(self):
raise NotImplementedError()
# 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>
#
#volume_dd_blocksize=1M
-# Total option count: 381
+# Total option count: 382