diff --git a/.zuul.yaml b/.zuul.yaml index af68976..77dbdfe 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -60,8 +60,7 @@ templates: - check-requirements - openstack-lower-constraints-jobs - - openstack-python-jobs - - openstack-python3-train-jobs + - openstack-python3-ussuri-jobs - periodic-stable-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 diff --git a/README.rst b/README.rst index d9c36c8..a731ecd 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,7 @@ * Source: https://opendev.org/openstack/castellan * Bugs: https://bugs.launchpad.net/castellan * Release notes: https://docs.openstack.org/releasenotes/castellan +* Wiki: https://wiki.openstack.org/wiki/Castellan Team and repository tags ======================== diff --git a/castellan/common/credentials/credential.py b/castellan/common/credentials/credential.py index f878e0c..83192e9 100644 --- a/castellan/common/credentials/credential.py +++ b/castellan/common/credentials/credential.py @@ -23,11 +23,8 @@ import abc -import six - -@six.add_metaclass(abc.ABCMeta) -class Credential(object): +class Credential(object, metaclass=abc.ABCMeta): """Base class to represent all credentials.""" def __init__(self): diff --git a/castellan/common/exception.py b/castellan/common/exception.py index 5f01bea..962a8d2 100644 --- a/castellan/common/exception.py +++ b/castellan/common/exception.py @@ -17,7 +17,7 @@ Castellan exception subclasses """ -import six.moves.urllib.parse as urlparse +import urllib from castellan.i18n import _ @@ -26,7 +26,7 @@ class RedirectException(Exception): def __init__(self, url): - self.url = urlparse.urlparse(url) + self.url = urllib.parse.urlparse(url) class CastellanException(Exception): diff --git a/castellan/common/objects/certificate.py b/castellan/common/objects/certificate.py index a8416f0..bac337a 100644 --- a/castellan/common/objects/certificate.py +++ b/castellan/common/objects/certificate.py @@ -19,13 +19,8 @@ This module defines the Certificate class. """ -import abc - -import six - from castellan.common.objects import managed_object -@six.add_metaclass(abc.ABCMeta) class Certificate(managed_object.ManagedObject): """Base class to represent all certificates.""" diff --git a/castellan/common/objects/key.py b/castellan/common/objects/key.py index 2fe1166..448fd9f 100644 --- a/castellan/common/objects/key.py +++ b/castellan/common/objects/key.py @@ -21,14 +21,11 @@ from Java. """ +import abc + from castellan.common.objects import managed_object -import abc -import six - - -@six.add_metaclass(abc.ABCMeta) class Key(managed_object.ManagedObject): """Base class to represent all keys.""" diff --git a/castellan/common/objects/managed_object.py b/castellan/common/objects/managed_object.py index de3c2e7..a620c1b 100644 --- a/castellan/common/objects/managed_object.py +++ b/castellan/common/objects/managed_object.py @@ -21,11 +21,8 @@ """ import abc -import six - -@six.add_metaclass(abc.ABCMeta) -class ManagedObject(object): +class ManagedObject(object, metaclass=abc.ABCMeta): """Base class to represent all managed objects.""" def __init__(self, name=None, created=None, id=None): diff --git a/castellan/key_manager/barbican_key_manager.py b/castellan/key_manager/barbican_key_manager.py index bc756de..892da26 100644 --- a/castellan/key_manager/barbican_key_manager.py +++ b/castellan/key_manager/barbican_key_manager.py @@ -18,6 +18,7 @@ """ import calendar import time +import urllib from cryptography.hazmat import backends from cryptography.hazmat.primitives import serialization @@ -32,11 +33,6 @@ from castellan.common import exception from castellan.common.objects import key as key_base_class from castellan.common.objects import opaque_data as op_data -from castellan.common.objects import passphrase -from castellan.common.objects import private_key as pri_key -from castellan.common.objects import public_key as pub_key -from castellan.common.objects import symmetric_key as sym_key -from castellan.common.objects import x_509 from castellan.i18n import _ from castellan.key_manager import key_manager @@ -44,10 +40,9 @@ from barbicanclient import client as barbican_client_import from barbicanclient import exceptions as barbican_exceptions from oslo_utils import timeutils -from six.moves import urllib - - -barbican_opts = [ + + +_barbican_opts = [ cfg.StrOpt('barbican_endpoint', help='Use this endpoint to connect to Barbican, for example: ' '"http://localhost:9311/"'), @@ -78,28 +73,20 @@ ] -BARBICAN_OPT_GROUP = 'barbican' +_BARBICAN_OPT_GROUP = 'barbican' LOG = logging.getLogger(__name__) class BarbicanKeyManager(key_manager.KeyManager): """Key Manager Interface that wraps the Barbican client API.""" - - _secret_type_dict = { - op_data.OpaqueData: 'opaque', - passphrase.Passphrase: 'passphrase', - pri_key.PrivateKey: 'private', - pub_key.PublicKey: 'public', - sym_key.SymmetricKey: 'symmetric', - x_509.X509: 'certificate'} def __init__(self, configuration): self._barbican_client = None self._base_url = None self.conf = configuration - self.conf.register_opts(barbican_opts, group=BARBICAN_OPT_GROUP) - loading.register_session_conf_options(self.conf, BARBICAN_OPT_GROUP) + self.conf.register_opts(_barbican_opts, group=_BARBICAN_OPT_GROUP) + loading.register_session_conf_options(self.conf, _BARBICAN_OPT_GROUP) def _get_barbican_client(self, context): """Creates a client to connect to the Barbican service. @@ -144,7 +131,7 @@ return self._barbican_client def _get_keystone_auth(self, context): - if context.__class__.__name__ is 'KeystonePassword': + if context.__class__.__name__ == 'KeystonePassword': return identity.Password( auth_url=context.auth_url, username=context.username, @@ -160,7 +147,7 @@ project_domain_id=context.project_domain_id, project_domain_name=context.project_domain_name, reauthenticate=context.reauthenticate) - elif context.__class__.__name__ is 'KeystoneToken': + elif context.__class__.__name__ == 'KeystoneToken': return identity.Token( auth_url=context.auth_url, token=context.token, @@ -174,14 +161,17 @@ reauthenticate=context.reauthenticate) # this will be kept for oslo.context compatibility until # projects begin to use utils.credential_factory - elif context.__class__.__name__ is 'RequestContext': - return identity.Token( - auth_url=self.conf.barbican.auth_endpoint, - token=context.auth_token, - project_id=context.project_id, - project_name=context.project_name, - project_domain_id=context.project_domain_id, - project_domain_name=context.project_domain_name) + elif context.__class__.__name__ == 'RequestContext': + if getattr(context, 'get_auth_plugin', None): + return context.get_auth_plugin() + else: + return identity.Token( + auth_url=self.conf.barbican.auth_endpoint, + token=context.auth_token, + project_id=context.project_id, + project_name=context.project_name, + project_domain_id=context.project_domain_id, + project_domain_name=context.project_domain_name) else: msg = _("context must be of type KeystonePassword, " "KeystoneToken, or RequestContext.") @@ -192,6 +182,10 @@ barbican = self.conf.barbican if barbican.barbican_endpoint: return barbican.barbican_endpoint + elif getattr(auth, 'service_catalog', None): + endpoint_data = auth.service_catalog.endpoint_data_for( + service_type='key-manager') + return endpoint_data.url else: service_parameters = {'service_type': 'key-manager', 'service_name': 'barbican', @@ -199,9 +193,14 @@ return auth.get_endpoint(sess, **service_parameters) def _create_base_url(self, auth, sess, endpoint): + api_version = None if self.conf.barbican.barbican_api_version: api_version = self.conf.barbican.barbican_api_version - else: + elif getattr(auth, 'service_catalog', None): + endpoint_data = auth.service_catalog.endpoint_data_for( + service_type='key-manager') + api_version = endpoint_data.api_version + elif getattr(auth, 'get_discovery', None): discovery = auth.get_discovery(sess, url=endpoint) raw_data = discovery.raw_version_data() if len(raw_data) == 0: @@ -622,7 +621,7 @@ except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: - LOG.error(_("Error listing objects: %s"), e) + LOG.error("Error listing objects: %s", e) raise exception.KeyManagerError(reason=e) for secret in secrets: @@ -632,7 +631,10 @@ except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: - LOG.warning(_("Error occurred while retrieving object " - "metadata, not adding it to the list: %s"), e) + LOG.warning("Error occurred while retrieving object " + "metadata, not adding it to the list: %s", e) return objects + + def list_options_for_discovery(self): + return [(_BARBICAN_OPT_GROUP, _barbican_opts)] diff --git a/castellan/key_manager/key_manager.py b/castellan/key_manager/key_manager.py index 07b8829..028f58b 100644 --- a/castellan/key_manager/key_manager.py +++ b/castellan/key_manager/key_manager.py @@ -19,16 +19,28 @@ import abc -import six +from castellan.common.objects import opaque_data as op_data +from castellan.common.objects import passphrase +from castellan.common.objects import private_key as pri_key +from castellan.common.objects import public_key as pub_key +from castellan.common.objects import symmetric_key as sym_key +from castellan.common.objects import x_509 -@six.add_metaclass(abc.ABCMeta) -class KeyManager(object): +class KeyManager(object, metaclass=abc.ABCMeta): """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. """ + + _secret_type_dict = { + op_data.OpaqueData: "opaque", + passphrase.Passphrase: "passphrase", + pri_key.PrivateKey: "private", + pub_key.PublicKey: "public", + sym_key.SymmetricKey: "symmetric", + x_509.X509: "certificate"} @abc.abstractmethod def __init__(self, configuration): @@ -123,3 +135,16 @@ found, an empty list should be returned instead. """ return [] + + def list_options_for_discovery(self): + """Lists the KeyManager's configure options. + + KeyManagers should advertise all supported options through this + method for the purpose of sample generation by oslo-config-generator. + Each item in the advertised list should be tuple composed by the group + name and a list of options belonging to that group. None should be used + as the group name for the DEFAULT group. + + :returns: the list of supported options of a KeyManager. + """ + return [] diff --git a/castellan/key_manager/vault_key_manager.py b/castellan/key_manager/vault_key_manager.py index ec19825..ad1424f 100644 --- a/castellan/key_manager/vault_key_manager.py +++ b/castellan/key_manager/vault_key_manager.py @@ -31,22 +31,18 @@ from oslo_log import log as logging from oslo_utils import timeutils import requests -import six from castellan.common import exception -from castellan.common.objects import opaque_data as op_data -from castellan.common.objects import passphrase from castellan.common.objects import private_key as pri_key from castellan.common.objects import public_key as pub_key from castellan.common.objects import symmetric_key as sym_key -from castellan.common.objects import x_509 from castellan.i18n import _ from castellan.key_manager import key_manager -DEFAULT_VAULT_URL = "http://127.0.0.1:8200" -DEFAULT_MOUNTPOINT = "secret" - -vault_opts = [ +_DEFAULT_VAULT_URL = "http://127.0.0.1:8200" +_DEFAULT_MOUNTPOINT = "secret" + +_vault_opts = [ cfg.StrOpt('root_token_id', help='root token for vault'), cfg.StrOpt('approle_role_id', @@ -54,13 +50,13 @@ cfg.StrOpt('approle_secret_id', help='AppRole secret_id for authentication with vault'), cfg.StrOpt('kv_mountpoint', - default=DEFAULT_MOUNTPOINT, + default=_DEFAULT_MOUNTPOINT, help='Mountpoint of KV store in Vault to use, for example: ' - '{}'.format(DEFAULT_MOUNTPOINT)), + '{}'.format(_DEFAULT_MOUNTPOINT)), cfg.StrOpt('vault_url', - default=DEFAULT_VAULT_URL, + default=_DEFAULT_VAULT_URL, help='Use this endpoint to connect to Vault, for example: ' - '"%s"' % DEFAULT_VAULT_URL), + '"%s"' % _DEFAULT_VAULT_URL), cfg.StrOpt('ssl_ca_crt_file', help='Absolute path to ca cert file'), cfg.BoolOpt('use_ssl', @@ -68,7 +64,7 @@ help=_('SSL Enabled/Disabled')), ] -VAULT_OPT_GROUP = 'vault' +_VAULT_OPT_GROUP = 'vault' _EXCEPTIONS_BY_CODE = [ requests.codes['internal_server_error'], @@ -84,18 +80,10 @@ class VaultKeyManager(key_manager.KeyManager): """Key Manager Interface that wraps the Vault REST API.""" - _secret_type_dict = { - op_data.OpaqueData: 'opaque', - passphrase.Passphrase: 'passphrase', - pri_key.PrivateKey: 'private', - pub_key.PublicKey: 'public', - sym_key.SymmetricKey: 'symmetric', - x_509.X509: 'certificate'} - def __init__(self, configuration): self._conf = configuration - self._conf.register_opts(vault_opts, group=VAULT_OPT_GROUP) - loading.register_session_conf_options(self._conf, VAULT_OPT_GROUP) + self._conf.register_opts(_vault_opts, group=_VAULT_OPT_GROUP) + loading.register_session_conf_options(self._conf, _VAULT_OPT_GROUP) self._session = requests.Session() self._root_token_id = self._conf.vault.root_token_id self._approle_role_id = self._conf.vault.approle_role_id @@ -174,11 +162,11 @@ json=params, verify=self._verify_server) except requests.exceptions.Timeout as ex: - raise exception.KeyManagerError(six.text_type(ex)) + raise exception.KeyManagerError(str(ex)) except requests.exceptions.ConnectionError as ex: - raise exception.KeyManagerError(six.text_type(ex)) + raise exception.KeyManagerError(str(ex)) except Exception as ex: - raise exception.KeyManagerError(six.text_type(ex)) + raise exception.KeyManagerError(str(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) @@ -200,11 +188,11 @@ try: resp = method(resource, headers=headers, json=json, verify=verify) except requests.exceptions.Timeout as ex: - raise exception.KeyManagerError(six.text_type(ex)) + raise exception.KeyManagerError(str(ex)) except requests.exceptions.ConnectionError as ex: - raise exception.KeyManagerError(six.text_type(ex)) + raise exception.KeyManagerError(str(ex)) except Exception as ex: - raise exception.KeyManagerError(six.text_type(ex)) + raise exception.KeyManagerError(str(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) @@ -216,11 +204,6 @@ def create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair.""" - - # Confirm context is provided, if not raise forbidden - if not context: - msg = _("User is not authorized to use key manager.") - raise exception.Forbidden(msg) if algorithm.lower() != 'rsa': raise NotImplementedError( @@ -293,11 +276,6 @@ def create_key(self, context, algorithm, length, name=None, **kwargs): """Creates a symmetric key.""" - # Confirm context is provided, if not raise forbidden - if not context: - msg = _("User is not authorized to use key manager.") - raise exception.Forbidden(msg) - if length % 8: msg = _("Length must be multiple of 8.") raise ValueError(msg) @@ -315,21 +293,11 @@ def store(self, context, key_value, **kwargs): """Stores (i.e., registers) a key with the key manager.""" - # Confirm context is provided, if not raise forbidden - if not context: - msg = _("User is not authorized to use key manager.") - raise exception.Forbidden(msg) - key_id = uuid.uuid4().hex return self._store_key_value(key_id, key_value) def get(self, context, key_id, metadata_only=False): """Retrieves the key identified by the specified id.""" - - # Confirm context is provided, if not raise forbidden - if not context: - msg = _("User is not authorized to use key manager.") - raise exception.Forbidden(msg) if not key_id: raise exception.KeyManagerError('key identifier not provided') @@ -371,11 +339,6 @@ def delete(self, context, key_id): """Represents deleting the key.""" - # Confirm context is provided, if not raise forbidden - if not context: - msg = _("User is not authorized to use key manager.") - raise exception.Forbidden(msg) - if not key_id: raise exception.KeyManagerError('key identifier not provided') @@ -387,11 +350,6 @@ def list(self, context, object_type=None, metadata_only=False): """Lists the managed objects given the criteria.""" - - # Confirm context is provided, if not raise forbidden - if not context: - msg = _("User is not authorized to use key manager.") - raise exception.Forbidden(msg) if object_type and object_type not in self._secret_type_dict: msg = _("Invalid secret type: %s") % object_type @@ -412,7 +370,10 @@ if object_type is None or isinstance(obj, object_type): objects.append(obj) except exception.ManagedObjectNotFoundError as e: - LOG.warning(_("Error occurred while retrieving object " - "metadata, not adding it to the list: %s"), e) + LOG.warning("Error occurred while retrieving object " + "metadata, not adding it to the list: %s", e) pass return objects + + def list_options_for_discovery(self): + return [(_VAULT_OPT_GROUP, _vault_opts)] diff --git a/castellan/options.py b/castellan/options.py index 2caed9a..64dd106 100644 --- a/castellan/options.py +++ b/castellan/options.py @@ -12,10 +12,12 @@ # 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 stevedore import ExtensionManager + from oslo_config import cfg from oslo_log import log -from castellan import key_manager as km +from castellan import key_manager try: from castellan.key_manager import barbican_key_manager as bkm except ImportError: @@ -66,11 +68,16 @@ :param barbican_endpoint_type: Use this to specify the type of URL. : Valid values are: public, internal or admin. """ - conf.register_opts(km.key_manager_opts, group='key_manager') - if bkm: - conf.register_opts(bkm.barbican_opts, group=bkm.BARBICAN_OPT_GROUP) - if vkm: - conf.register_opts(vkm.vault_opts, group=vkm.VAULT_OPT_GROUP) + conf.register_opts(key_manager.key_manager_opts, group='key_manager') + + ext_mgr = ExtensionManager( + "castellan.drivers", + invoke_on_load=True, + invoke_args=[cfg.CONF]) + + for km in ext_mgr.names(): + for group, opts in ext_mgr[km].obj.list_options_for_discovery(): + conf.register_opts(opts, group=group) # Use the new backend option if set or fall back to the older api_class default_backend = backend or api_class @@ -80,25 +87,25 @@ if bkm is not None: if barbican_endpoint is not None: conf.set_default('barbican_endpoint', barbican_endpoint, - group=bkm.BARBICAN_OPT_GROUP) + group=bkm._BARBICAN_OPT_GROUP) if barbican_api_version is not None: conf.set_default('barbican_api_version', barbican_api_version, - group=bkm.BARBICAN_OPT_GROUP) + group=bkm._BARBICAN_OPT_GROUP) if auth_endpoint is not None: conf.set_default('auth_endpoint', auth_endpoint, - group=bkm.BARBICAN_OPT_GROUP) + group=bkm._BARBICAN_OPT_GROUP) if retry_delay is not None: conf.set_default('retry_delay', retry_delay, - group=bkm.BARBICAN_OPT_GROUP) + group=bkm._BARBICAN_OPT_GROUP) if number_of_retries is not None: conf.set_default('number_of_retries', number_of_retries, - group=bkm.BARBICAN_OPT_GROUP) + group=bkm._BARBICAN_OPT_GROUP) if verify_ssl is not None: conf.set_default('verify_ssl', verify_ssl, - group=bkm.BARBICAN_OPT_GROUP) + group=bkm._BARBICAN_OPT_GROUP) if barbican_endpoint_type is not None: conf.set_default('barbican_endpoint_type', barbican_endpoint_type, - group=bkm.BARBICAN_OPT_GROUP) + group=bkm._BARBICAN_OPT_GROUP) if vkm is not None: if vault_root_token_id is not None: @@ -151,12 +158,16 @@ :returns: a list of (group_name, opts) tuples """ key_manager_opts = [] - key_manager_opts.extend(km.key_manager_opts) + key_manager_opts.extend(key_manager.key_manager_opts) key_manager_opts.extend(utils.credential_opts) opts = [('key_manager', key_manager_opts)] - if bkm is not None: - opts.append((bkm.BARBICAN_OPT_GROUP, bkm.barbican_opts)) - if vkm is not None: - opts.append((vkm.VAULT_OPT_GROUP, vkm.vault_opts)) + ext_mgr = ExtensionManager( + "castellan.drivers", + invoke_on_load=True, + invoke_args=[cfg.CONF]) + + for driver in ext_mgr.names(): + opts.extend(ext_mgr[driver].obj.list_options_for_discovery()) + return opts diff --git a/castellan/tests/functional/key_manager/test_key_manager.py b/castellan/tests/functional/key_manager/test_key_manager.py index 1f8eaf6..021cb0f 100644 --- a/castellan/tests/functional/key_manager/test_key_manager.py +++ b/castellan/tests/functional/key_manager/test_key_manager.py @@ -77,6 +77,7 @@ def setUp(self): super(KeyManagerTestCase, self).setUp() self.key_mgr = self._create_key_manager() + self.ctxt = None def _get_valid_object_uuid(self, managed_object): object_uuid = self.key_mgr.store(self.ctxt, managed_object) diff --git a/castellan/tests/functional/key_manager/test_vault_key_manager.py b/castellan/tests/functional/key_manager/test_vault_key_manager.py index f72cf6a..180189e 100644 --- a/castellan/tests/functional/key_manager/test_vault_key_manager.py +++ b/castellan/tests/functional/key_manager/test_vault_key_manager.py @@ -15,12 +15,10 @@ Note: This requires local running instance of Vault. """ -import abc import os import uuid from oslo_config import cfg -from oslo_context import context from oslo_utils import uuidutils from oslotest import base import requests @@ -34,7 +32,8 @@ CONF = config.get_config() -class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase): +class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase, + base.BaseTestCase): def _create_key_manager(self): key_mgr = vault_key_manager.VaultKeyManager(cfg.CONF) @@ -46,26 +45,6 @@ key_mgr._vault_url = os.environ['VAULT_TEST_URL'] return key_mgr - @abc.abstractmethod - def get_context(self): - """Retrieves Context for Authentication""" - return - - def setUp(self): - super(VaultKeyManagerTestCase, self).setUp() - self.ctxt = self.get_context() - - def tearDown(self): - super(VaultKeyManagerTestCase, self).tearDown() - - def test_create_null_context(self): - self.assertRaises(exception.Forbidden, - self.key_mgr.create_key, None, 'AES', 256) - - def test_create_key_pair_null_context(self): - self.assertRaises(exception.Forbidden, - self.key_mgr.create_key_pair, None, 'RSA', 2048) - def test_create_key_pair_bad_algorithm(self): self.assertRaises( NotImplementedError, @@ -73,23 +52,9 @@ self.ctxt, 'DSA', 2048 ) - def test_delete_null_context(self): - key_uuid = self._get_valid_object_uuid( - test_key_manager._get_test_symmetric_key()) - self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) - self.assertRaises(exception.Forbidden, - self.key_mgr.delete, None, key_uuid) - def test_delete_null_object(self): self.assertRaises(exception.KeyManagerError, self.key_mgr.delete, self.ctxt, None) - - def test_get_null_context(self): - key_uuid = self._get_valid_object_uuid( - test_key_manager._get_test_symmetric_key()) - self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) - self.assertRaises(exception.Forbidden, - self.key_mgr.get, None, key_uuid) def test_get_null_object(self): self.assertRaises(exception.KeyManagerError, @@ -99,18 +64,6 @@ bad_key_uuid = uuidutils.generate_uuid() self.assertRaises(exception.ManagedObjectNotFoundError, self.key_mgr.get, self.ctxt, bad_key_uuid) - - def test_store_null_context(self): - key = test_key_manager._get_test_symmetric_key() - - self.assertRaises(exception.Forbidden, - self.key_mgr.store, None, key) - - -class VaultKeyManagerOSLOContextTestCase(VaultKeyManagerTestCase, - base.BaseTestCase): - def get_context(self): - return context.get_admin_context() TEST_POLICY = ''' @@ -128,7 +81,7 @@ APPROLE_ENDPOINT = 'v1/auth/approle/role/{role_name}' -class VaultKeyManagerAppRoleTestCase(VaultKeyManagerOSLOContextTestCase): +class VaultKeyManagerAppRoleTestCase(VaultKeyManagerTestCase): mountpoint = 'secret' diff --git a/castellan/tests/unit/key_manager/test_barbican_key_manager.py b/castellan/tests/unit/key_manager/test_barbican_key_manager.py index d4fe682..36e842d 100644 --- a/castellan/tests/unit/key_manager/test_barbican_key_manager.py +++ b/castellan/tests/unit/key_manager/test_barbican_key_manager.py @@ -94,6 +94,54 @@ endpoint) self.assertEqual(endpoint + "/" + version, base_url) + def test_base_url_service_catalog(self): + endpoint_data = mock.Mock() + endpoint_data.api_version = 'v321' + + auth = mock.Mock(spec=['service_catalog']) + auth.service_catalog.endpoint_data_for.return_value = endpoint_data + + endpoint = "http://localhost/key_manager" + + base_url = self.key_mgr._create_base_url(auth, + mock.Mock(), + endpoint) + self.assertEqual(endpoint + "/" + endpoint_data.api_version, base_url) + auth.service_catalog.endpoint_data_for.assert_called_once_with( + service_type='key-manager') + + def test_base_url_raise_exception(self): + auth = mock.Mock(spec=['get_discovery']) + sess = mock.Mock() + discovery = mock.Mock() + discovery.raw_version_data = mock.Mock(return_value=[]) + auth.get_discovery = mock.Mock(return_value=discovery) + + endpoint = "http://localhost/key_manager" + + self.assertRaises(exception.KeyManagerError, + self.key_mgr._create_base_url, + auth, sess, endpoint) + auth.get_discovery.asser_called_once_with(sess, url=endpoint) + self.assertEqual(1, discovery.raw_version_data.call_count) + + def test_base_url_get_discovery(self): + version = 'v100500' + auth = mock.Mock(spec=['get_discovery']) + sess = mock.Mock() + discovery = mock.Mock() + auth.get_discovery = mock.Mock(return_value=discovery) + discovery.raw_version_data = mock.Mock(return_value=[{'id': version}]) + + endpoint = "http://localhost/key_manager" + + base_url = self.key_mgr._create_base_url(auth, + mock.Mock(), + endpoint) + self.assertEqual(endpoint + "/" + version, base_url) + auth.get_discovery.asser_called_once_with(sess, url=endpoint) + self.assertEqual(1, discovery.raw_version_data.call_count) + def test_create_key(self): # Create order_ref_url and assign return value order_ref_url = ("http://localhost:9311/v1/orders/" diff --git a/castellan/tests/unit/test_options.py b/castellan/tests/unit/test_options.py index bd8c3ff..3b82c49 100644 --- a/castellan/tests/unit/test_options.py +++ b/castellan/tests/unit/test_options.py @@ -40,34 +40,34 @@ barbican_endpoint = 'http://test-server.org:9311/' options.set_defaults(conf, barbican_endpoint=barbican_endpoint) self.assertEqual(barbican_endpoint, - conf.get(bkm.BARBICAN_OPT_GROUP).barbican_endpoint) + conf.barbican.barbican_endpoint) barbican_api_version = 'vSomething' options.set_defaults(conf, barbican_api_version=barbican_api_version) self.assertEqual(barbican_api_version, - conf.get(bkm.BARBICAN_OPT_GROUP).barbican_api_version) + conf.barbican.barbican_api_version) auth_endpoint = 'http://test-server.org/identity' options.set_defaults(conf, auth_endpoint=auth_endpoint) self.assertEqual(auth_endpoint, - conf.get(bkm.BARBICAN_OPT_GROUP).auth_endpoint) + conf.barbican.auth_endpoint) retry_delay = 3 options.set_defaults(conf, retry_delay=retry_delay) self.assertEqual(retry_delay, - conf.get(bkm.BARBICAN_OPT_GROUP).retry_delay) + conf.barbican.retry_delay) number_of_retries = 10 options.set_defaults(conf, number_of_retries=number_of_retries) self.assertEqual(number_of_retries, - conf.get(bkm.BARBICAN_OPT_GROUP).number_of_retries) + conf.barbican.number_of_retries) verify_ssl = True options.set_defaults(conf, verify_ssl=True) self.assertEqual(verify_ssl, - conf.get(bkm.BARBICAN_OPT_GROUP).verify_ssl) + conf.barbican.verify_ssl) barbican_endpoint_type = 'internal' options.set_defaults(conf, barbican_endpoint_type='internal') - result_type = conf.get(bkm.BARBICAN_OPT_GROUP).barbican_endpoint_type + result_type = conf.barbican.barbican_endpoint_type self.assertEqual(barbican_endpoint_type, result_type) diff --git a/castellan/tests/utils.py b/castellan/tests/utils.py index 91d66b7..a1d3629 100644 --- a/castellan/tests/utils.py +++ b/castellan/tests/utils.py @@ -19,8 +19,6 @@ import functools import types -import six - def construct_new_test_function(original_func, name, build_params): """Builds a new test function based on parameterized data. @@ -32,10 +30,10 @@ :return: A new function object """ new_func = types.FunctionType( - six.get_function_code(original_func), - six.get_function_globals(original_func), + original_func.__code__, + original_func.__globals__, name=name, - argdefs=six.get_function_defaults(original_func) + argdefs=original_func.__defaults__, ) for key, val in original_func.__dict__.items(): diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..6f8e380 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,4 @@ +sphinx>=1.8.0,!=2.1.0 # BSD +sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD +reno>=2.5.0 # Apache-2.0 +openstackdocstheme>=1.18.1 # Apache-2.0 diff --git a/doc/source/conf.py b/doc/source/conf.py index 9867895..6e6d89f 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -23,6 +23,7 @@ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'openstackdocstheme', + 'sphinxcontrib.rsvgconverter', ] # autodoc generation is a bit aggressive and a nuisance when doing heavy @@ -71,12 +72,19 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). + latex_documents = [ ('index', - '%s.tex' % project, + 'doc-castellan.tex', u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] + +latex_elements = { + 'extraclassoptions': 'openany,oneside', +} + +latex_use_xindy = False # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'https://docs.python.org/3/': None} diff --git a/doc/source/index.rst b/doc/source/index.rst index 7b9de8e..bc115c7 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -15,10 +15,11 @@ user/index contributor/index -Indices and tables -================== +.. only:: html -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + Indices and tables + ================== + * :ref:`genindex` + * :ref:`modindex` + * :ref:`search` diff --git a/lower-constraints.txt b/lower-constraints.txt index 3432ac9..20fbd11 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -17,7 +17,7 @@ gitdb==0.6.4 GitPython==1.0.1 hacking==0.12.0 -idna==2.6 +idna==2.5 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 @@ -31,7 +31,6 @@ msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 -openstackdocstheme==1.18.1 os-client-config==1.28.0 oslo.config==6.4.0 oslo.context==2.19.2 @@ -56,15 +55,11 @@ python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 -reno==2.5.0 -requests==2.14.2 +requests==2.18.0 requestsexceptions==1.2.0 rfc3986==0.3.1 -six==1.10.0 smmap==0.9.0 snowballstemmer==1.2.1 -Sphinx==1.6.2 -sphinxcontrib-websupport==1.0.1 stevedore==1.20.0 stestr==2.0.0 testscenarios==0.4 diff --git a/releasenotes/notes/drop-python-2-7-73d3113c69d724d6.yaml b/releasenotes/notes/drop-python-2-7-73d3113c69d724d6.yaml new file mode 100644 index 0000000..3030b3a --- /dev/null +++ b/releasenotes/notes/drop-python-2-7-73d3113c69d724d6.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Python 2.7 support has been dropped. The minimum version of Python now + supported by castellan is Python 3.6. diff --git a/releasenotes/notes/implements-keymanager-option-discovery-13a46c1dfc036a3f.yaml b/releasenotes/notes/implements-keymanager-option-discovery-13a46c1dfc036a3f.yaml new file mode 100644 index 0000000..6d5632b --- /dev/null +++ b/releasenotes/notes/implements-keymanager-option-discovery-13a46c1dfc036a3f.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Enhance the global option listing to discover available key managers and + their options. The purpose of this feature is to have a correct listing of + the supported key managers, now each key manager is responsible for + advertising the oslo.config groups/options they consume. +other: + - | + The visibility of module variables and constants related to oslo.config + options changed to private in both barbican and vault key managers. The + key managers are only responsible for overloading the method + list_options_for_discovery() in order to advertise their own options. + This way, the global options doesn't need to know which variables to look + for. diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 0bfefcc..f44a132 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + train stein rocky queens diff --git a/releasenotes/source/train.rst b/releasenotes/source/train.rst new file mode 100644 index 0000000..5839003 --- /dev/null +++ b/releasenotes/source/train.rst @@ -0,0 +1,6 @@ +========================== +Train Series Release Notes +========================== + +.. release-notes:: + :branch: stable/train diff --git a/requirements.txt b/requirements.txt index 0d89d61..5bec0f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,4 @@ oslo.utils>=3.33.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 -requests>=2.14.2,!=2.20.0 # Apache-2.0 +requests>=2.18.0,!=2.20.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 5dd2eba..84a3348 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,6 +6,7 @@ author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/castellan/latest/ +python-requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -13,11 +14,11 @@ License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: Implementation :: CPython [files] packages = @@ -35,15 +36,6 @@ barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager vault = castellan.key_manager.vault_key_manager:VaultKeyManager -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 -warning-is-error = 1 - -[upload_sphinx] -upload-dir = doc/build/html - [compile_catalog] directory = castellan/locale domain = castellan @@ -57,6 +49,3 @@ keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = castellan/locale/castellan.pot - -[wheel] -universal = 1 diff --git a/test-requirements.txt b/test-requirements.txt index 09534b4..b7cd592 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,14 +6,10 @@ coverage!=4.4,>=4.0 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD -sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD -sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD -openstackdocstheme>=1.18.1 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT bandit>=1.1.0,<1.6.0 # Apache-2.0 -reno>=2.5.0 # Apache-2.0 pifpaf>=0.10.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 56a9a7f..acb76fe 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] -minversion = 2.0 -envlist = py27,py37,pep8 +minversion = 3.1.1 +envlist = py37,pep8 +ignore_basepython_conflict = True skipsdist = True [testenv] usedevelop = True -install_command = pip install {opts} {packages} +basepython = python3 setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./castellan/tests/unit @@ -15,17 +16,12 @@ -r{toxinidir}/test-requirements.txt commands = stestr run --slowest {posargs} -[testenv:py27] -basepython = python2.7 - [testenv:pep8] -basepython = python3 commands = flake8 bandit -r castellan -x tests -s B105,B106,B107,B607 [testenv:bandit] -basepython = python3 # This command runs the bandit security linter against the castellan # codebase minus the tests directory. Some tests are being excluded to # reduce the number of positives before a team inspection, and to ensure a @@ -38,30 +34,46 @@ bandit -r castellan -x tests -s B105,B106,B107,B607 [testenv:venv] -basepython = python3 commands = {posargs} [testenv:debug] -basepython = python3 commands = oslo_debug_helper {posargs} [testenv:cover] -basepython = python3 setenv = - PYTHON=coverage run --source $project --parallel-mode + PYTHON=coverage run --source castellan --parallel-mode commands = - stestr run {posargs} + coverage erase + {[testenv]commands} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml - coverage report + coverage report --show-missing [testenv:docs] -basepython = python3 -commands = python setup.py build_sphinx +# This environment is called from CI scripts to test and publish +# the main docs to https://docs.openstack.org/castellan +description = Build main documentation +deps = -r{toxinidir}/doc/requirements.txt +commands= + rm -rf doc/build doc/build/doctrees + sphinx-build -W -b html -d doc/build/doctrees doc/source doc/build/html +whitelist_externals = rm + +[testenv:pdf-docs] +deps = {[testenv:docs]deps} +envdir = {toxworkdir}/docs +whitelist_externals = + rm + make +commands = + rm -rf doc/build/pdf + sphinx-build -W -b latex doc/source doc/build/pdf + make -C doc/build/pdf [testenv:releasenotes] -basepython = python3 +deps = {[testenv:docs]deps} +envdir = {toxworkdir}/docs commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:functional] @@ -83,7 +95,6 @@ {toxinidir}/tools/setup-vault-env.sh pifpaf -e VAULT_TEST run vault -- stestr run --slowest {posargs} [testenv:genconfig] -basepython = python3 commands = oslo-config-generator --config-file=etc/castellan/functional-config-generator.conf oslo-config-generator --config-file=etc/castellan/sample-config-generator.conf @@ -99,14 +110,12 @@ import_exceptions = castellan.i18n [testenv:lower-constraints] -basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt [testenv:bindep] -basepython = python3 # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed