diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample index 3b855f16daf1..a7edd458d39b 100644 --- a/etc/nova/nova.conf.sample +++ b/etc/nova/nova.conf.sample @@ -977,7 +977,7 @@ # The full class name of the key manager API class (string # value) -#keymgr_api_class=nova.keymgr.key_mgr.KeyManager +#keymgr_api_class=nova.keymgr.not_implemented_key_mgr.NotImplementedKeyManager # diff --git a/nova/keymgr/__init__.py b/nova/keymgr/__init__.py index c6e1b5011403..101eb57cfd26 100644 --- a/nova/keymgr/__init__.py +++ b/nova/keymgr/__init__.py @@ -21,7 +21,8 @@ from nova.openstack.common import log as logging keymgr_opts = [ cfg.StrOpt('keymgr_api_class', - default='nova.keymgr.key_mgr.KeyManager', + default='nova.keymgr.' + 'not_implemented_key_mgr.NotImplementedKeyManager', help='The full class name of the key manager API class'), ] diff --git a/nova/keymgr/key.py b/nova/keymgr/key.py index fc7255e252cc..3e3f1394122c 100644 --- a/nova/keymgr/key.py +++ b/nova/keymgr/key.py @@ -33,15 +33,18 @@ class Key(object): @abc.abstractmethod def get_algorithm(self): - """Returns this key's algorithm. For example, "DSA" would indicate - that this key is a DSA key. + """Returns the key's algorithm. + + Returns the key's algorithm. For example, "DSA" indicates that this key + is a DSA key and "AES" indicates that this key is an AES key. """ pass @abc.abstractmethod def get_format(self): - """Returns the encoding format of this key or None if this key is not - encoded. + """Returns the encoding format. + + Returns the key's encoding format or None if this key is not encoded. """ pass @@ -57,8 +60,10 @@ class SymmetricKey(Key): """ def __init__(self, alg, key): - """Create a new SymmetricKey object. This specifies the algorithm for - the symmetric encryption and the bytes for the key. + """Create a new SymmetricKey object. + + The arguments specify the algorithm for the symmetric encryption and + the bytes for the key. """ self.alg = alg self.key = key @@ -68,7 +73,7 @@ class SymmetricKey(Key): return self.alg def get_format(self): - """This returns 'RAW'.""" + """This method returns 'RAW'.""" return "RAW" def get_encoded(self): diff --git a/nova/keymgr/key_mgr.py b/nova/keymgr/key_mgr.py index 4d48eee196e5..bd5669fa56e1 100644 --- a/nova/keymgr/key_mgr.py +++ b/nova/keymgr/key_mgr.py @@ -52,6 +52,21 @@ class KeyManager(object): """ pass + @abc.abstractmethod + def copy_key(self, ctxt, key_id, **kwargs): + """Copies (i.e., clones) a key stored by the key manager. + + This method copies the specified key and returns the copy's UUID. If + the specified context does not permit copying keys, then a + NotAuthorized error should be raised. + + Implementation note: This method should behave identically to + store_key(context, get_key(context, )) + although it is preferable to perform this operation within the key + manager to avoid unnecessary handling of the key material. + """ + pass + @abc.abstractmethod def get_key(self, ctxt, key_id, **kwargs): """Retrieves the specified key. diff --git a/nova/keymgr/not_implemented_key_mgr.py b/nova/keymgr/not_implemented_key_mgr.py new file mode 100644 index 000000000000..827a3ca9807d --- /dev/null +++ b/nova/keymgr/not_implemented_key_mgr.py @@ -0,0 +1,42 @@ +# 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. + +""" +Key manager implementation that raises NotImplementedError +""" + +from nova.keymgr import key_mgr + + +class NotImplementedKeyManager(key_mgr.KeyManager): + """Key Manager Interface that raises NotImplementedError for all operations + """ + + def create_key(self, ctxt, algorithm='AES', length=256, expiration=None, + **kwargs): + raise NotImplementedError() + + def store_key(self, ctxt, key, expiration=None, **kwargs): + raise NotImplementedError() + + def copy_key(self, ctxt, key_id, **kwargs): + raise NotImplementedError() + + def get_key(self, ctxt, key_id, **kwargs): + raise NotImplementedError() + + def delete_key(self, ctxt, key_id, **kwargs): + raise NotImplementedError() diff --git a/nova/tests/keymgr/fake.py b/nova/tests/keymgr/fake.py new file mode 100644 index 000000000000..4c99bf649f73 --- /dev/null +++ b/nova/tests/keymgr/fake.py @@ -0,0 +1,26 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# Copyright 2012 OpenStack LLC +# 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. + +"""Implementation of a fake key manager.""" + + +from nova.tests.keymgr import mock_key_mgr + + +def fake_api(): + return mock_key_mgr.MockKeyManager() diff --git a/nova/tests/keymgr/mock_key_mgr.py b/nova/tests/keymgr/mock_key_mgr.py index 04eb3e9fdc51..4489fe567a34 100644 --- a/nova/tests/keymgr/mock_key_mgr.py +++ b/nova/tests/keymgr/mock_key_mgr.py @@ -20,12 +20,12 @@ anything but integration testing. """ import array -import uuid from nova import exception from nova.keymgr import key from nova.keymgr import key_mgr from nova.openstack.common import log as logging +from nova.openstack.common import uuidutils from nova import utils @@ -35,15 +35,15 @@ 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 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. + by the key manager interface. This implementation stores keys within a + dictionary, and as a result, it is not acceptable for use across different + 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 - the same key is created for all invocations of create_key and keys are not - stored persistently. + keys are not stored persistently. """ + def __init__(self): self.keys = {} @@ -67,6 +67,13 @@ class MockKeyManager(key_mgr.KeyManager): return self.store_key(ctxt, _key) + def _generate_key_id(self): + key_id = uuidutils.generate_uuid() + while key_id in self.keys: + 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. @@ -76,22 +83,26 @@ class MockKeyManager(key_mgr.KeyManager): if ctxt is None: raise exception.NotAuthorized() - # generate UUID and ensure that it isn't in use - key_id = uuid.uuid4() - while key_id in self.keys: - key_id = uuid.uuid4() - + key_id = self._generate_key_id() self.keys[key_id] = key return key_id + def copy_key(self, ctxt, key_id, **kwargs): + if ctxt is None: + raise exception.NotAuthorized() + + copied_key_id = self._generate_key_id() + self.keys[copied_key_id] = self.keys[key_id] + + return copied_key_id + def get_key(self, ctxt, key_id, **kwargs): """Retrieves the key identified by the specified id. - This implementation returns a fixed key that is associated with the - UUID returned by the create_key method. A NotAuthorized exception is - raised if the specified context is None; a KeyError is raised if the - UUID is invalid. + 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() @@ -101,9 +112,8 @@ class MockKeyManager(key_mgr.KeyManager): def delete_key(self, ctxt, key_id, **kwargs): """Deletes the key identified by the specified id. - This implementation intentionally does nothing except raise a - NotAuthorized exception is the context is None or a KeyError if the - UUID is invalid. + A NotAuthorized exception is raised if the context is None and a + KeyError is raised if the UUID is invalid. """ if ctxt is None: raise exception.NotAuthorized() diff --git a/nova/tests/keymgr/test_mock_key_mgr.py b/nova/tests/keymgr/test_mock_key_mgr.py index de965644c380..ec8f3c168b66 100644 --- a/nova/tests/keymgr/test_mock_key_mgr.py +++ b/nova/tests/keymgr/test_mock_key_mgr.py @@ -22,7 +22,7 @@ import array from nova import context from nova import exception -from nova.keymgr import key +from nova.keymgr import key as keymgr_key from nova.tests.keymgr import mock_key_mgr from nova.tests.keymgr import test_key_mgr @@ -54,8 +54,8 @@ class MockKeyManagerTestCase(test_key_mgr.KeyManagerTestCase): self.key_mgr.create_key, None) def test_store_key(self): - _key = key.SymmetricKey('AES', - array.array('B', ('0' * 64).decode('hex')).tolist()) + secret_key = array.array('B', ('0' * 64).decode('hex')).tolist() + _key = keymgr_key.SymmetricKey('AES', secret_key) key_id = self.key_mgr.store_key(self.ctxt, _key) actual_key = self.key_mgr.get_key(self.ctxt, key_id) @@ -65,6 +65,20 @@ class MockKeyManagerTestCase(test_key_mgr.KeyManagerTestCase): self.assertRaises(exception.NotAuthorized, self.key_mgr.store_key, None, None) + 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.assertNotEqual(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_get_key(self): pass diff --git a/nova/tests/keymgr/test_not_implemented_key_mgr.py b/nova/tests/keymgr/test_not_implemented_key_mgr.py new file mode 100644 index 000000000000..85bc9f22050c --- /dev/null +++ b/nova/tests/keymgr/test_not_implemented_key_mgr.py @@ -0,0 +1,51 @@ +# 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 not implemented key manager. +""" + +from nova.keymgr import not_implemented_key_mgr +from nova.tests.keymgr import test_key_mgr + + +class NotImplementedKeyManagerTestCase(test_key_mgr.KeyManagerTestCase): + + def _create_key_manager(self): + return not_implemented_key_mgr.NotImplementedKeyManager() + + def setUp(self): + super(NotImplementedKeyManagerTestCase, self).setUp() + + def test_create_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.create_key, None) + + def test_store_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.store_key, None, None) + + def test_copy_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.copy_key, None, None) + + def test_get_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.get_key, None, None) + + def test_delete_key(self): + self.assertRaises(NotImplementedError, + self.key_mgr.delete_key, None, None)