Create key manager interface
This interface provides a thin wrapper around an underlying key management implementation such as Barbican or a KMIP server. The key manager interface is used by the volume encryption code to retrieve keys for volumes. Implements: blueprint encrypt-cinder-volumes Change-Id: I9b0dcb7d648ee6809185c71ba457c8a8a6c90d50 SecurityImpact
This commit is contained in:
parent
9b64dcc725
commit
fc8cb355db
37
nova/keymgr/__init__.py
Normal file
37
nova/keymgr/__init__.py
Normal file
@ -0,0 +1,37 @@
|
||||
# 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.
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova.openstack.common import importutils
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
keymgr_opts = [
|
||||
cfg.StrOpt('keymgr_api_class',
|
||||
default='nova.keymgr.key_mgr.KeyManager',
|
||||
help='The full class name of the key manager API class'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(keymgr_opts)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def API():
|
||||
keymgr_api_class = CONF.keymgr_api_class
|
||||
cls = importutils.import_class(keymgr_api_class)
|
||||
return cls()
|
76
nova/keymgr/key.py
Normal file
76
nova/keymgr/key.py
Normal file
@ -0,0 +1,76 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Base Key and SymmetricKey Classes
|
||||
|
||||
This module defines the Key and SymmetricKey classes. The Key class is the base
|
||||
class to represent all encryption keys. The basis for this class was copied
|
||||
from Java.
|
||||
"""
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class Key(object):
|
||||
"""Base class to represent all keys."""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_algorithm(self):
|
||||
"""Returns this key's algorithm. For example, "DSA" would indicate
|
||||
that this key is a DSA key.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_format(self):
|
||||
"""Returns the encoding format of this key or None if this key is not
|
||||
encoded.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_encoded(self):
|
||||
"""Returns the key in the format specified by its encoding."""
|
||||
pass
|
||||
|
||||
|
||||
class SymmetricKey(Key):
|
||||
"""
|
||||
This class represents symmetric keys
|
||||
"""
|
||||
|
||||
def __init__(self, alg, key):
|
||||
"""Create a new SymmetricKey object. This specifies the algorithm for
|
||||
the symmetric encryption and the bytes for the key.
|
||||
"""
|
||||
self.alg = alg
|
||||
self.key = key
|
||||
|
||||
def get_algorithm(self):
|
||||
"""Returns the algorithm for symmetric encryption."""
|
||||
return self.alg
|
||||
|
||||
def get_format(self):
|
||||
"""This returns 'RAW'."""
|
||||
return "RAW"
|
||||
|
||||
def get_encoded(self):
|
||||
"""Returns the key in its encoded format."""
|
||||
return self.key
|
85
nova/keymgr/key_mgr.py
Normal file
85
nova/keymgr/key_mgr.py
Normal file
@ -0,0 +1,85 @@
|
||||
# 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 API
|
||||
"""
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class KeyManager(object):
|
||||
"""Base Key Manager Interface
|
||||
|
||||
A Key Manager is responsible for managing encryption keys for volumes. A
|
||||
Key Manager is responsible for creating, reading, and deleting keys.
|
||||
"""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_key(self, ctxt, algorithm='AES', length=256, expiration=None,
|
||||
**kwargs):
|
||||
"""Creates a key.
|
||||
|
||||
This method creates a key and returns the key's UUID. If the specified
|
||||
context does not permit the creation of keys, then a NotAuthorized
|
||||
exception should be raised.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def store_key(self, ctxt, key, expiration=None, **kwargs):
|
||||
"""Stores (i.e., registers) a key with the key manager.
|
||||
|
||||
This method stores the specified key and returns its UUID that
|
||||
identifies it within the key manager. If the specified context does
|
||||
not permit the creation of keys, then a NotAuthorized exception should
|
||||
be raised.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_key(self, ctxt, key_id, **kwargs):
|
||||
"""Retrieves the specified key.
|
||||
|
||||
Implementations should verify that the caller has permissions to
|
||||
retrieve the key by checking the context object passed in as ctxt. If
|
||||
the user lacks permission then a NotAuthorized exception is raised.
|
||||
|
||||
If the specified key does not exist, then a KeyError should be raised.
|
||||
Implementations should preclude users from discerning the UUIDs of
|
||||
keys that belong to other users by repeatedly calling this method.
|
||||
That is, keys that belong to other users should be considered "non-
|
||||
existent" and completely invisible.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_key(self, ctxt, key_id, **kwargs):
|
||||
"""Deletes the specified key.
|
||||
|
||||
Implementations should verify that the caller has permission to delete
|
||||
the key by checking the context object (ctxt). A NotAuthorized
|
||||
exception should be raised if the caller lacks permission.
|
||||
|
||||
If the specified key does not exist, then a KeyError should be raised.
|
||||
Implementations should preclude users from discerning the UUIDs of
|
||||
keys that belong to other users by repeatedly calling this method.
|
||||
That is, keys that belong to other users should be considered "non-
|
||||
existent" and completely invisible.
|
||||
"""
|
||||
pass
|
15
nova/tests/keymgr/__init__.py
Normal file
15
nova/tests/keymgr/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
# 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.
|
111
nova/tests/keymgr/mock_key_mgr.py
Normal file
111
nova/tests/keymgr/mock_key_mgr.py
Normal file
@ -0,0 +1,111 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A mock implementation of a key manager. This module should NOT be used for
|
||||
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 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 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.
|
||||
|
||||
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.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.keys = {}
|
||||
|
||||
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()
|
||||
|
||||
# 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)
|
||||
|
||||
def store_key(self, ctxt, key, **kwargs):
|
||||
"""Stores (i.e., registers) a key with the key manager.
|
||||
|
||||
This implementation does nothing -- i.e., the specified key is
|
||||
discarded.
|
||||
"""
|
||||
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()
|
||||
|
||||
self.keys[key_id] = key
|
||||
|
||||
return 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.
|
||||
"""
|
||||
if ctxt is None:
|
||||
raise exception.NotAuthorized()
|
||||
|
||||
return self.keys[key_id]
|
||||
|
||||
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.
|
||||
"""
|
||||
if ctxt is None:
|
||||
raise exception.NotAuthorized()
|
||||
|
||||
del self.keys[key_id]
|
57
nova/tests/keymgr/test_key.py
Normal file
57
nova/tests/keymgr/test_key.py
Normal file
@ -0,0 +1,57 @@
|
||||
# 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 key classes.
|
||||
"""
|
||||
|
||||
import array
|
||||
|
||||
from nova.keymgr import key
|
||||
from nova import test
|
||||
|
||||
|
||||
class KeyTestCase(test.TestCase):
|
||||
|
||||
def _create_key(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def setUp(self):
|
||||
super(KeyTestCase, self).setUp()
|
||||
|
||||
self.key = self._create_key()
|
||||
|
||||
|
||||
class SymmetricKeyTestCase(KeyTestCase):
|
||||
|
||||
def _create_key(self):
|
||||
return key.SymmetricKey(self.algorithm, self.encoded)
|
||||
|
||||
def setUp(self):
|
||||
self.algorithm = 'AES'
|
||||
self.encoded = array.array('B', ('0' * 64).decode('hex')).tolist()
|
||||
|
||||
super(SymmetricKeyTestCase, self).setUp()
|
||||
|
||||
def test_get_algorithm(self):
|
||||
self.assertEquals(self.key.get_algorithm(), self.algorithm)
|
||||
|
||||
def test_get_format(self):
|
||||
self.assertEquals(self.key.get_format(), 'RAW')
|
||||
|
||||
def test_get_encoded(self):
|
||||
self.assertEquals(self.key.get_encoded(), self.encoded)
|
33
nova/tests/keymgr/test_key_mgr.py
Normal file
33
nova/tests/keymgr/test_key_mgr.py
Normal file
@ -0,0 +1,33 @@
|
||||
# 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 key manager.
|
||||
"""
|
||||
|
||||
from nova import test
|
||||
|
||||
|
||||
class KeyManagerTestCase(test.TestCase):
|
||||
|
||||
def _create_key_manager(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def setUp(self):
|
||||
super(KeyManagerTestCase, self).setUp()
|
||||
|
||||
self.key_mgr = self._create_key_manager()
|
89
nova/tests/keymgr/test_mock_key_mgr.py
Normal file
89
nova/tests/keymgr/test_mock_key_mgr.py
Normal file
@ -0,0 +1,89 @@
|
||||
# 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 mock key manager.
|
||||
"""
|
||||
|
||||
import array
|
||||
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova.keymgr import key
|
||||
from nova.tests.keymgr import mock_key_mgr
|
||||
from nova.tests.keymgr import test_key_mgr
|
||||
|
||||
|
||||
class MockKeyManagerTestCase(test_key_mgr.KeyManagerTestCase):
|
||||
|
||||
def _create_key_manager(self):
|
||||
return mock_key_mgr.MockKeyManager()
|
||||
|
||||
def setUp(self):
|
||||
super(MockKeyManagerTestCase, self).setUp()
|
||||
|
||||
self.ctxt = context.RequestContext('fake', 'fake')
|
||||
|
||||
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 unique
|
||||
self.assertNotEqual(key_id_1, key_id_2)
|
||||
|
||||
def test_create_key_with_length(self):
|
||||
for length in [64, 128, 256]:
|
||||
key_id = self.key_mgr.create_key(self.ctxt, key_length=length)
|
||||
key = self.key_mgr.get_key(self.ctxt, key_id)
|
||||
self.assertEqual(length / 8, len(key.get_encoded()))
|
||||
|
||||
def test_create_null_context(self):
|
||||
self.assertRaises(exception.NotAuthorized,
|
||||
self.key_mgr.create_key, None)
|
||||
|
||||
def test_store_key(self):
|
||||
_key = key.SymmetricKey('AES',
|
||||
array.array('B', ('0' * 64).decode('hex')).tolist())
|
||||
key_id = self.key_mgr.store_key(self.ctxt, _key)
|
||||
|
||||
actual_key = self.key_mgr.get_key(self.ctxt, key_id)
|
||||
self.assertEqual(_key, actual_key)
|
||||
|
||||
def test_store_null_context(self):
|
||||
self.assertRaises(exception.NotAuthorized,
|
||||
self.key_mgr.store_key, None, None)
|
||||
|
||||
def test_get_key(self):
|
||||
pass
|
||||
|
||||
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_delete_key(self):
|
||||
key_id = self.key_mgr.create_key(self.ctxt)
|
||||
self.key_mgr.delete_key(self.ctxt, key_id)
|
||||
|
||||
self.assertRaises(KeyError, self.key_mgr.get_key, self.ctxt, 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(KeyError, self.key_mgr.delete_key, self.ctxt, None)
|
Loading…
Reference in New Issue
Block a user