Copy cinder.keymgr to castellan
This patch adds the code found in cinder.keymgr to castellan, except for the barbican wrapper and the barbican test case. The ConfKeyManager is also not included, since it is insecure and not suitable for production, and the MockKeyManager is suitable for testing. Change-Id: I1139262581720be47a09b46f01f4bfb85a764d9a
This commit is contained in:
parent
bcf648e9ba
commit
0460fbf34f
0
castellan/common/__init__.py
Normal file
0
castellan/common/__init__.py
Normal file
56
castellan/common/exception.py
Normal file
56
castellan/common/exception.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Copyright (c) 2015 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Castellan exception subclasses
|
||||||
|
"""
|
||||||
|
|
||||||
|
import six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
|
from castellan.openstack.common import _i18n as u
|
||||||
|
|
||||||
|
_FATAL_EXCEPTION_FORMAT_ERRORS = False
|
||||||
|
|
||||||
|
|
||||||
|
class RedirectException(Exception):
|
||||||
|
def __init__(self, url):
|
||||||
|
self.url = urlparse.urlparse(url)
|
||||||
|
|
||||||
|
|
||||||
|
class CastellanException(Exception):
|
||||||
|
"""Base Castellan Exception
|
||||||
|
|
||||||
|
To correctly use this class, inherit from it and define
|
||||||
|
a 'message' property. That message will get printf'd
|
||||||
|
with the keyword arguments provided to the constructor.
|
||||||
|
"""
|
||||||
|
message = u._("An unknown exception occurred")
|
||||||
|
|
||||||
|
def __init__(self, message_arg=None, *args, **kwargs):
|
||||||
|
if not message_arg:
|
||||||
|
message_arg = self.message
|
||||||
|
try:
|
||||||
|
self.message = message_arg % kwargs
|
||||||
|
except Exception as e:
|
||||||
|
if _FATAL_EXCEPTION_FORMAT_ERRORS:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
# at least get the core message out if something happened
|
||||||
|
pass
|
||||||
|
super(CastellanException, self).__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
class Forbidden(CastellanException):
|
||||||
|
message = u._("You are not authorized to complete this action.")
|
71
castellan/context.py
Normal file
71
castellan/context.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Copyright 2011-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.
|
||||||
|
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from castellan.openstack.common import local
|
||||||
|
from castellan.openstack.common import policy
|
||||||
|
|
||||||
|
|
||||||
|
class RequestContext(object):
|
||||||
|
"""User security context object
|
||||||
|
|
||||||
|
Stores information about the security context under which the user
|
||||||
|
accesses the system, as well as additional request information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, auth_tok=None, user=None, project=None, roles=None,
|
||||||
|
is_admin=False, read_only=False, show_deleted=False,
|
||||||
|
owner_is_project=True, service_catalog=None,
|
||||||
|
policy_enforcer=None):
|
||||||
|
self.auth_tok = auth_tok
|
||||||
|
self.user = user
|
||||||
|
self.project = project
|
||||||
|
self.roles = roles or []
|
||||||
|
self.read_only = read_only
|
||||||
|
self.owner_is_project = owner_is_project
|
||||||
|
self.request_id = uuidutils.generate_uuid()
|
||||||
|
self.service_catalog = service_catalog
|
||||||
|
self.policy_enforcer = policy_enforcer or policy.Enforcer()
|
||||||
|
self.is_admin = is_admin
|
||||||
|
|
||||||
|
if not hasattr(local.store, 'context'):
|
||||||
|
self.update_store()
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'request_id': self.request_id,
|
||||||
|
'user': self.user,
|
||||||
|
'user_id': self.user,
|
||||||
|
'project': self.project,
|
||||||
|
'project_id': self.project,
|
||||||
|
'roles': self.roles,
|
||||||
|
'auth_token': self.auth_tok,
|
||||||
|
'service_catalog': self.service_catalog,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, values):
|
||||||
|
return cls(**values)
|
||||||
|
|
||||||
|
def update_store(self):
|
||||||
|
local.store.context = self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def owner(self):
|
||||||
|
"""Return the owner to correlate with key."""
|
||||||
|
if self.owner_is_project:
|
||||||
|
return self.project
|
||||||
|
return self.user
|
30
castellan/keymgr/__init__.py
Normal file
30
castellan/keymgr/__init__.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2015 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.utils import importutils
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
keymgr_opts = [
|
||||||
|
cfg.StrOpt('api_class',
|
||||||
|
help='The full class name of the key manager API class'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(keymgr_opts, group='keymgr')
|
||||||
|
|
||||||
|
|
||||||
|
def API():
|
||||||
|
cls = importutils.import_class(CONF.keymgr.api_class)
|
||||||
|
return cls()
|
53
castellan/keymgr/key.py
Normal file
53
castellan/keymgr/key.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright (c) 2015 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
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Key(object):
|
||||||
|
"""Base class to represent all keys."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_algorithm(self):
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
Returns the key's encoding format 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
|
100
castellan/keymgr/key_mgr.py
Normal file
100
castellan/keymgr/key_mgr.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Copyright (c) 2015 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
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@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 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.
|
||||||
|
|
||||||
|
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
|
42
castellan/keymgr/not_implemented_key_mgr.py
Normal file
42
castellan/keymgr/not_implemented_key_mgr.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Copyright (c) 2015 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 castellan.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()
|
59
castellan/keymgr/symmetric_key.py
Normal file
59
castellan/keymgr/symmetric_key.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Copyright (c) 2015 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 SymmetricKey Class
|
||||||
|
|
||||||
|
This module defines the SymmetricKey class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from castellan.keymgr import key
|
||||||
|
|
||||||
|
|
||||||
|
class SymmetricKey(key.Key):
|
||||||
|
"""This class represents symmetric keys."""
|
||||||
|
|
||||||
|
def __init__(self, alg, 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
|
||||||
|
|
||||||
|
def get_algorithm(self):
|
||||||
|
"""Returns the algorithm for symmetric encryption."""
|
||||||
|
return self.alg
|
||||||
|
|
||||||
|
def get_format(self):
|
||||||
|
"""This method returns 'RAW'."""
|
||||||
|
return "RAW"
|
||||||
|
|
||||||
|
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
|
0
castellan/tests/keymgr/__init__.py
Normal file
0
castellan/tests/keymgr/__init__.py
Normal file
24
castellan/tests/keymgr/fake.py
Normal file
24
castellan/tests/keymgr/fake.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2011 Justin Santa Barbara
|
||||||
|
# Copyright 2012 OpenStack Foundation
|
||||||
|
# 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 castellan.tests.keymgr import mock_key_mgr
|
||||||
|
|
||||||
|
|
||||||
|
def fake_api():
|
||||||
|
return mock_key_mgr.MockKeyManager()
|
166
castellan/tests/keymgr/mock_key_mgr.py
Normal file
166
castellan/tests/keymgr/mock_key_mgr.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# Copyright (c) 2015 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 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
|
||||||
|
import binascii
|
||||||
|
import random
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from castellan.common import exception
|
||||||
|
from castellan.keymgr import key_mgr
|
||||||
|
from castellan.keymgr import symmetric_key as sym_key
|
||||||
|
|
||||||
|
|
||||||
|
class MockKeyManager(key_mgr.KeyManager):
|
||||||
|
|
||||||
|
"""Mocking manager for integration tests.
|
||||||
|
|
||||||
|
This mock key manager implementation supports all the methods specified
|
||||||
|
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 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
|
||||||
|
length = int(key_length / 4)
|
||||||
|
hex_encoded = self._generate_password(length=length,
|
||||||
|
symbolgroups='0123456789ABCDEF')
|
||||||
|
return hex_encoded
|
||||||
|
|
||||||
|
def _generate_key(self, **kwargs):
|
||||||
|
_hex = self._generate_hex_key(**kwargs)
|
||||||
|
return sym_key.SymmetricKey('AES',
|
||||||
|
array.array('B',
|
||||||
|
binascii.unhexlify(_hex))
|
||||||
|
.tolist())
|
||||||
|
|
||||||
|
def create_key(self, ctxt, **kwargs):
|
||||||
|
"""Creates a key.
|
||||||
|
|
||||||
|
This implementation returns a UUID for the created key. A
|
||||||
|
Forbidden exception is raised if the specified context is None.
|
||||||
|
"""
|
||||||
|
if ctxt is None:
|
||||||
|
raise exception.Forbidden()
|
||||||
|
|
||||||
|
key = self._generate_key(**kwargs)
|
||||||
|
return self.store_key(ctxt, key)
|
||||||
|
|
||||||
|
def _generate_key_id(self):
|
||||||
|
key_id = str(uuid.uuid4())
|
||||||
|
while key_id in self.keys:
|
||||||
|
key_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
return 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.Forbidden()
|
||||||
|
|
||||||
|
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.Forbidden()
|
||||||
|
|
||||||
|
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 the key that is associated with the
|
||||||
|
specified UUID. A Forbidden exception is raised if the specified
|
||||||
|
context is None; a KeyError is raised if the UUID is invalid.
|
||||||
|
"""
|
||||||
|
if ctxt is None:
|
||||||
|
raise exception.Forbidden()
|
||||||
|
|
||||||
|
return self.keys[key_id]
|
||||||
|
|
||||||
|
def delete_key(self, ctxt, key_id, **kwargs):
|
||||||
|
"""Deletes the key identified by the specified id.
|
||||||
|
|
||||||
|
A Forbidden exception is raised if the context is None and a
|
||||||
|
KeyError is raised if the UUID is invalid.
|
||||||
|
"""
|
||||||
|
if ctxt is None:
|
||||||
|
raise exception.Forbidden()
|
||||||
|
|
||||||
|
del self.keys[key_id]
|
||||||
|
|
||||||
|
# Default symbols to use for passwords. Avoids visually confusing
|
||||||
|
# characters. ~6 bits per symbol.
|
||||||
|
DEFAULT_PASSWORD_SYMBOLS = ('23456789', # Removed: 0,1
|
||||||
|
'ABCDEFGHJKLMNPQRSTUVWXYZ', # Removed: I, O
|
||||||
|
'abcdefghijkmnopqrstuvwxyz') # Removed: l
|
||||||
|
|
||||||
|
def _generate_password(self, length=16,
|
||||||
|
symbolgroups=DEFAULT_PASSWORD_SYMBOLS):
|
||||||
|
"""Generate a random password from the supplied symbol groups.
|
||||||
|
|
||||||
|
At least one symbol from each group will be included. Unpredictable
|
||||||
|
results if length is less than the number of symbol groups.
|
||||||
|
|
||||||
|
Believed to be reasonably secure (with a reasonable password length!)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# NOTE(jerdfelt): Some password policies require at least one character
|
||||||
|
# from each group of symbols, so start off with one random character
|
||||||
|
# from each symbol group
|
||||||
|
password = [random.choice(s) for s in symbolgroups]
|
||||||
|
# If length < len(symbolgroups), the leading characters will only
|
||||||
|
# be from the first length groups. Try our best to not be predictable
|
||||||
|
# by shuffling and then truncating.
|
||||||
|
random.shuffle(password)
|
||||||
|
password = password[:length]
|
||||||
|
length -= len(password)
|
||||||
|
|
||||||
|
# then fill with random characters from all symbol groups
|
||||||
|
symbols = ''.join(symbolgroups)
|
||||||
|
password.extend([random.choice(symbols) for _i in range(length)])
|
||||||
|
|
||||||
|
# finally shuffle to ensure first x characters aren't from a
|
||||||
|
# predictable group
|
||||||
|
random.shuffle(password)
|
||||||
|
|
||||||
|
return ''.join(password)
|
68
castellan/tests/keymgr/test_key.py
Normal file
68
castellan/tests/keymgr/test_key.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Copyright (c) 2015 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
|
||||||
|
import binascii
|
||||||
|
|
||||||
|
from castellan.keymgr import symmetric_key as sym_key
|
||||||
|
from castellan.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class KeyTestCase(base.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 sym_key.SymmetricKey(self.algorithm, self.encoded)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.algorithm = 'AES'
|
||||||
|
self.encoded = array.array('B', binascii.unhexlify('0' * 64)).tolist()
|
||||||
|
|
||||||
|
super(SymmetricKeyTestCase, self).setUp()
|
||||||
|
|
||||||
|
def test_get_algorithm(self):
|
||||||
|
self.assertEqual(self.key.get_algorithm(), self.algorithm)
|
||||||
|
|
||||||
|
def test_get_format(self):
|
||||||
|
self.assertEqual(self.key.get_format(), 'RAW')
|
||||||
|
|
||||||
|
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 is None)
|
||||||
|
self.assertFalse(None == self.key)
|
||||||
|
|
||||||
|
def test___ne__(self):
|
||||||
|
self.assertFalse(self.key != self.key)
|
||||||
|
|
||||||
|
self.assertTrue(self.key is not None)
|
||||||
|
self.assertTrue(None != self.key)
|
33
castellan/tests/keymgr/test_key_mgr.py
Normal file
33
castellan/tests/keymgr/test_key_mgr.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (c) 2015 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 castellan.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class KeyManagerTestCase(base.TestCase):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(KeyManagerTestCase, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _create_key_manager(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(KeyManagerTestCase, self).setUp()
|
||||||
|
|
||||||
|
self.key_mgr = self._create_key_manager()
|
103
castellan/tests/keymgr/test_mock_key_mgr.py
Normal file
103
castellan/tests/keymgr/test_mock_key_mgr.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# Copyright (c) 2015 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
|
||||||
|
import binascii
|
||||||
|
|
||||||
|
from castellan.common import exception
|
||||||
|
from castellan import context
|
||||||
|
from castellan.keymgr import symmetric_key as sym_key
|
||||||
|
from castellan.tests.keymgr import mock_key_mgr
|
||||||
|
from castellan.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.Forbidden,
|
||||||
|
self.key_mgr.create_key, None)
|
||||||
|
|
||||||
|
def test_store_key(self):
|
||||||
|
secret_key = array.array('B', binascii.unhexlify('0' * 64)).tolist()
|
||||||
|
_key = sym_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)
|
||||||
|
self.assertEqual(_key, actual_key)
|
||||||
|
|
||||||
|
def test_store_null_context(self):
|
||||||
|
self.assertRaises(exception.Forbidden,
|
||||||
|
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.Forbidden,
|
||||||
|
self.key_mgr.copy_key, None, None)
|
||||||
|
|
||||||
|
def test_get_key(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_get_null_context(self):
|
||||||
|
self.assertRaises(exception.Forbidden,
|
||||||
|
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.Forbidden,
|
||||||
|
self.key_mgr.delete_key, None, None)
|
||||||
|
|
||||||
|
def test_delete_unknown_key(self):
|
||||||
|
self.assertRaises(KeyError, self.key_mgr.delete_key, self.ctxt, None)
|
50
castellan/tests/keymgr/test_not_implemented_key_mgr.py
Normal file
50
castellan/tests/keymgr/test_not_implemented_key_mgr.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright (c) 2015 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 castellan.keymgr import not_implemented_key_mgr
|
||||||
|
from castellan.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)
|
@ -4,3 +4,6 @@
|
|||||||
|
|
||||||
pbr>=0.6,!=0.7,<1.0
|
pbr>=0.6,!=0.7,<1.0
|
||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
|
oslo.config>=1.6.0 # Apache-2.0
|
||||||
|
oslo.serialization>=1.2.0 # Apache-2.0
|
||||||
|
oslo.utils>=1.2.0 # Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user