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:
Joel Coffman 2013-09-05 09:12:26 -04:00
parent b5181d73dc
commit 9469565689
9 changed files with 195 additions and 31 deletions

View File

@ -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
#

View File

@ -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'),
]

View File

@ -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):

View File

@ -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.

View File

@ -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()

26
nova/tests/keymgr/fake.py Normal file
View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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)