Synchronize the key manager interface with Cinder
This change synchronizes the key manager interface with code that has been accepted by Cinder. The default key manager (i.e., NotImplementedKeyManager) raises NotImplementedError for all operations. A copy_key method has also been added to the key manager interface so that keys may be deleted when the objects that they encrypt (e.g., a volume) are deleted. Implements blueprint encrypt-cinder-volumes Change-Id: Ie9ab9578402e87338b6a4bd413bb9f875d3b3eb6
This commit is contained in:
parent
b5181d73dc
commit
9469565689
|
@ -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
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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, <encryption key UUID>))
|
||||
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.
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue