Tagger: OpenStack Release Bot <infra-root@openstack.org>
Date: Fri Sep 16 16:13:40 2016 +0000 retag 3.0.0_rc1 of barbican 3.0.0.0rc1 release candidate -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCAAGBQJX4vhmAAoJENQWrRWsa0P+7N8P/38DWhBtBXIkw5fCqxmbzw+R tRECVT+L/iU+CiEVagX29teKXq2P8E/znIxh2UTbTI8FIfi7fClxYO7HBFqzvrrf E+IxTOeJctw/Kq027Xy3LsVyM6plI+Ao0QKOgQzU1OjK6sCdz2NV9Sr6gLOF5865 9LwRPwerek3KgadQlbEegrwffKSZddaqt7p2aQA47XojMGK1HtFKccHQpVUVx4tO wIzD0a+EP8XVvf7md55Oct7f2L3hIFSEtPrANeFA3k2gKDDzMXr4EDGq0A8bvMez H1ae7tiTGbeGDftbPcuPBTHEFk+N2TcJ6GKUPsKkqcX5XBbgH7raonLrF0lLEOuX 5Ls1kaqrU53z93+kYB/4mksrc0Jd+cGXNffccsiKEVNVBe61uyI9+zcoCyK+guNq f1Qe0Q73IOByzjzvAkR26uu5CE6Qz41C7vFoubrHFA+azE+DRk3Ya2bAXpEfKz/E 9H1estlh3E1ckibrHeaH0ucmAHUG9HDjXts4VPZfmKVqnZNwCm25U2J2SFaXa1i1 4KrzjrdF0uUjBaT2OcmUQ2vEeatu4YAFFu4XoTq56wILGF1a4QjEB/P8j4r8qiHs h2hBzZ4Q/i0wCzZaa+c9dXVAMynkyv224rmu0fleZLUT7F6p/Efs+2ZUVfpEGMwL vLeHuN2zNcjzkHGMU05C =ZOE7 -----END PGP SIGNATURE----- Merge tag '3.0.0_rc1' into debian/newton Tagger: OpenStack Release Bot <infra-root@openstack.org> Date: Fri Sep 16 16:13:40 2016 +0000 retag 3.0.0_rc1 of barbican 3.0.0.0rc1 release candidate Change-Id: Ie2b4e7b3a94a2fb70cf685dc575e32add26bf9ec
This commit is contained in:
commit
e9995b8a6c
|
@ -33,6 +33,12 @@ def _consumer_not_found():
|
|||
'another castle.'))
|
||||
|
||||
|
||||
def _consumer_ownership_mismatch():
|
||||
"""Throw exception indicating the user does not own this consumer."""
|
||||
pecan.abort(403, u._('Not Allowed. Sorry, only the creator of a consumer '
|
||||
'can delete it.'))
|
||||
|
||||
|
||||
class ContainerConsumerController(controllers.ACLMixin):
|
||||
"""Handles Consumer entity retrieval and deletion requests."""
|
||||
|
||||
|
@ -51,7 +57,6 @@ class ContainerConsumerController(controllers.ACLMixin):
|
|||
def on_get(self, external_project_id):
|
||||
consumer = self.consumer_repo.get(
|
||||
entity_id=self.consumer_id,
|
||||
external_project_id=external_project_id,
|
||||
suppress_exception=True)
|
||||
if not consumer:
|
||||
_consumer_not_found()
|
||||
|
@ -92,11 +97,6 @@ class ContainerConsumersController(controllers.ACLMixin):
|
|||
LOG.debug(u._('Start consumers on_get '
|
||||
'for container-ID %s:'), self.container_id)
|
||||
|
||||
try:
|
||||
self.container_repo.get(self.container_id, external_project_id)
|
||||
except exception.NotFound:
|
||||
controllers.containers.container_not_found()
|
||||
|
||||
result = self.consumer_repo.get_by_container_id(
|
||||
self.container_id,
|
||||
offset_arg=kw.get('offset', 0),
|
||||
|
@ -136,18 +136,13 @@ class ContainerConsumersController(controllers.ACLMixin):
|
|||
data = api.load_body(pecan.request, validator=self.validator)
|
||||
LOG.debug('Start on_post...%s', data)
|
||||
|
||||
try:
|
||||
container = self.container_repo.get(self.container_id,
|
||||
external_project_id)
|
||||
except exception.NotFound:
|
||||
controllers.containers.container_not_found()
|
||||
container = self._get_container(self.container_id)
|
||||
|
||||
self.quota_enforcer.enforce(project)
|
||||
|
||||
new_consumer = models.ContainerConsumerMetadatum(self.container_id,
|
||||
project.id,
|
||||
data)
|
||||
new_consumer.project_id = project.id
|
||||
self.consumer_repo.create_or_update_from(new_consumer, container)
|
||||
|
||||
url = hrefs.convert_consumer_to_href(new_consumer.container_id)
|
||||
|
@ -156,8 +151,7 @@ class ContainerConsumersController(controllers.ACLMixin):
|
|||
LOG.info(u._LI('Created a consumer for project: %s'),
|
||||
external_project_id)
|
||||
|
||||
return self._return_container_data(self.container_id,
|
||||
external_project_id)
|
||||
return self._return_container_data(self.container_id)
|
||||
|
||||
@index.when(method='DELETE', template='json')
|
||||
@controllers.handle_exceptions(u._('ContainerConsumer deletion'))
|
||||
|
@ -176,6 +170,13 @@ class ContainerConsumersController(controllers.ACLMixin):
|
|||
_consumer_not_found()
|
||||
LOG.debug("Found consumer: %s", consumer)
|
||||
|
||||
container = self._get_container(self.container_id)
|
||||
owner_of_consumer = consumer.project_id == external_project_id
|
||||
owner_of_container = container.project.external_id \
|
||||
== external_project_id
|
||||
if not owner_of_consumer and not owner_of_container:
|
||||
_consumer_ownership_mismatch()
|
||||
|
||||
try:
|
||||
self.consumer_repo.delete_entity_by_id(consumer.id,
|
||||
external_project_id)
|
||||
|
@ -183,21 +184,22 @@ class ContainerConsumersController(controllers.ACLMixin):
|
|||
LOG.exception(u._LE('Problem deleting consumer'))
|
||||
_consumer_not_found()
|
||||
|
||||
ret_data = self._return_container_data(
|
||||
self.container_id,
|
||||
external_project_id
|
||||
)
|
||||
ret_data = self._return_container_data(self.container_id)
|
||||
LOG.info(u._LI('Deleted a consumer for project: %s'),
|
||||
external_project_id)
|
||||
return ret_data
|
||||
|
||||
def _return_container_data(self, container_id, external_project_id):
|
||||
try:
|
||||
container = self.container_repo.get(container_id,
|
||||
external_project_id)
|
||||
dict_fields = container.to_dict_fields()
|
||||
except Exception:
|
||||
def _get_container(self, container_id):
|
||||
container = self.container_repo.get_container_by_id(
|
||||
container_id, suppress_exception=True)
|
||||
if not container:
|
||||
controllers.containers.container_not_found()
|
||||
return container
|
||||
|
||||
def _return_container_data(self, container_id):
|
||||
container = self._get_container(container_id)
|
||||
|
||||
dict_fields = container.to_dict_fields()
|
||||
|
||||
for secret_ref in dict_fields['secret_refs']:
|
||||
hrefs.convert_to_hrefs(secret_ref)
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
# (c) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
|
||||
from barbican.api import controllers
|
||||
from barbican.common import hrefs
|
||||
from barbican.common import resources as res
|
||||
from barbican.common import utils
|
||||
from barbican import i18n as u
|
||||
from barbican.model import repositories as repo
|
||||
from barbican.plugin.util import multiple_backends
|
||||
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
|
||||
def _secret_store_not_found():
|
||||
"""Throw exception indicating secret store not found."""
|
||||
pecan.abort(404, u._('Not Found. Secret store not found.'))
|
||||
|
||||
|
||||
def _preferred_secret_store_not_found():
|
||||
"""Throw exception indicating secret store not found."""
|
||||
pecan.abort(404, u._('Not Found. No preferred secret store defined for '
|
||||
'this project.'))
|
||||
|
||||
|
||||
def _multiple_backends_not_enabled():
|
||||
"""Throw exception indicating multiple backends support is not enabled."""
|
||||
pecan.abort(404, u._('Not Found. Multiple backends support is not enabled '
|
||||
'in service configuration.'))
|
||||
|
||||
|
||||
def convert_secret_store_to_response_format(secret_store):
|
||||
data = secret_store.to_dict_fields()
|
||||
data['secret_store_plugin'] = data.pop('store_plugin')
|
||||
data['secret_store_ref'] = hrefs.convert_secret_stores_to_href(
|
||||
data['secret_store_id'])
|
||||
# no need to pass store id as secret_store_ref is returned
|
||||
data.pop('secret_store_id', None)
|
||||
return data
|
||||
|
||||
|
||||
class PreferredSecretStoreController(controllers.ACLMixin):
|
||||
"""Handles preferred secret store set/removal requests."""
|
||||
|
||||
def __init__(self, secret_store):
|
||||
LOG.debug(u._('=== Creating PreferredSecretStoreController ==='))
|
||||
self.secret_store = secret_store
|
||||
self.proj_store_repo = repo.get_project_secret_store_repository()
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self, **kwargs):
|
||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||
|
||||
@index.when(method='DELETE', template='json')
|
||||
@controllers.handle_exceptions(u._('Removing preferred secret store'))
|
||||
@controllers.enforce_rbac('secretstore_preferred:delete')
|
||||
def on_delete(self, external_project_id, **kw):
|
||||
LOG.debug(u._('Start: Remove project preferred secret-store for store'
|
||||
' id %s'), self.secret_store.id)
|
||||
|
||||
project = res.get_or_create_project(external_project_id)
|
||||
|
||||
project_store = self.proj_store_repo.get_secret_store_for_project(
|
||||
project.id, None, suppress_exception=True)
|
||||
if project_store is None:
|
||||
_preferred_secret_store_not_found()
|
||||
|
||||
self.proj_store_repo.delete_entity_by_id(
|
||||
entity_id=project_store.id,
|
||||
external_project_id=external_project_id)
|
||||
pecan.response.status = 204
|
||||
|
||||
@index.when(method='POST', template='json')
|
||||
@controllers.handle_exceptions(u._('Setting preferred secret store'))
|
||||
@controllers.enforce_rbac('secretstore_preferred:post')
|
||||
def on_post(self, external_project_id, **kwargs):
|
||||
LOG.debug(u._('Start: Set project preferred secret-store for store '),
|
||||
'id %s', self.secret_store.id)
|
||||
|
||||
project = res.get_or_create_project(external_project_id)
|
||||
|
||||
self.proj_store_repo.create_or_update_for_project(project.id,
|
||||
self.secret_store.id)
|
||||
|
||||
pecan.response.status = 204
|
||||
|
||||
|
||||
class SecretStoreController(controllers.ACLMixin):
|
||||
"""Handles secret store retrieval requests."""
|
||||
|
||||
def __init__(self, secret_store):
|
||||
LOG.debug(u._('=== Creating SecretStoreController ==='))
|
||||
self.secret_store = secret_store
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, action, *remainder):
|
||||
if (action == 'preferred'):
|
||||
return PreferredSecretStoreController(self.secret_store), remainder
|
||||
else:
|
||||
pecan.abort(405)
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self, **kwargs):
|
||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
@controllers.handle_exceptions(u._('Secret store retrieval'))
|
||||
@controllers.enforce_rbac('secretstore:get')
|
||||
def on_get(self, external_project_id):
|
||||
LOG.debug("== Getting secret store for %s", self.secret_store.id)
|
||||
return convert_secret_store_to_response_format(self.secret_store)
|
||||
|
||||
|
||||
class SecretStoresController(controllers.ACLMixin):
|
||||
"""Handles secret-stores list requests."""
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('Creating SecretStoresController')
|
||||
self.secret_stores_repo = repo.get_secret_stores_repository()
|
||||
self.proj_store_repo = repo.get_project_secret_store_repository()
|
||||
|
||||
def __getattr__(self, name):
|
||||
route_table = {
|
||||
'global-default': self.get_global_default,
|
||||
'preferred': self.get_preferred,
|
||||
}
|
||||
if name in route_table:
|
||||
return route_table[name]
|
||||
raise AttributeError
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, secret_store_id, *remainder):
|
||||
if not utils.is_multiple_backends_enabled():
|
||||
_multiple_backends_not_enabled()
|
||||
|
||||
secret_store = self.secret_stores_repo.get(entity_id=secret_store_id,
|
||||
suppress_exception=True)
|
||||
if not secret_store:
|
||||
_secret_store_not_found()
|
||||
return SecretStoreController(secret_store), remainder
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self, **kwargs):
|
||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
@controllers.handle_exceptions(u._('List available secret stores'))
|
||||
@controllers.enforce_rbac('secretstores:get')
|
||||
def on_get(self, external_project_id, **kw):
|
||||
LOG.debug(u._('Start SecretStoresController on_get: listing secret '
|
||||
'stores'))
|
||||
if not utils.is_multiple_backends_enabled():
|
||||
_multiple_backends_not_enabled()
|
||||
|
||||
res.get_or_create_project(external_project_id)
|
||||
|
||||
secret_stores = self.secret_stores_repo.get_all()
|
||||
|
||||
resp_list = []
|
||||
for store in secret_stores:
|
||||
item = convert_secret_store_to_response_format(store)
|
||||
resp_list.append(item)
|
||||
|
||||
resp = {'secret_stores': resp_list}
|
||||
|
||||
return resp
|
||||
|
||||
@pecan.expose(generic=True, template='json')
|
||||
@controllers.handle_exceptions(u._('Retrieve global default secret store'))
|
||||
@controllers.enforce_rbac('secretstores:get_global_default')
|
||||
def get_global_default(self, external_project_id, **kw):
|
||||
LOG.debug(u._('Start secret-stores get global default secret store'))
|
||||
|
||||
if not utils.is_multiple_backends_enabled():
|
||||
_multiple_backends_not_enabled()
|
||||
|
||||
res.get_or_create_project(external_project_id)
|
||||
|
||||
store = multiple_backends.get_global_default_secret_store()
|
||||
|
||||
return convert_secret_store_to_response_format(store)
|
||||
|
||||
@pecan.expose(generic=True, template='json')
|
||||
@controllers.handle_exceptions(u._('Retrieve project preferred store'))
|
||||
@controllers.enforce_rbac('secretstores:get_preferred')
|
||||
def get_preferred(self, external_project_id, **kw):
|
||||
LOG.debug(u._('Start secret-stores get preferred secret store'))
|
||||
|
||||
if not utils.is_multiple_backends_enabled():
|
||||
_multiple_backends_not_enabled()
|
||||
|
||||
project = res.get_or_create_project(external_project_id)
|
||||
|
||||
project_store = self.proj_store_repo.get_secret_store_for_project(
|
||||
project.id, None, suppress_exception=True)
|
||||
|
||||
if project_store is None:
|
||||
_preferred_secret_store_not_found()
|
||||
|
||||
return convert_secret_store_to_response_format(
|
||||
project_store.secret_store)
|
|
@ -19,6 +19,7 @@ from barbican.api.controllers import containers
|
|||
from barbican.api.controllers import orders
|
||||
from barbican.api.controllers import quotas
|
||||
from barbican.api.controllers import secrets
|
||||
from barbican.api.controllers import secretstores
|
||||
from barbican.api.controllers import transportkeys
|
||||
from barbican.common import config
|
||||
from barbican.common import utils
|
||||
|
@ -97,6 +98,7 @@ class V1Controller(BaseVersionController):
|
|||
self.cas = cas.CertificateAuthoritiesController()
|
||||
self.quotas = quotas.QuotasController()
|
||||
setattr(self, 'project-quotas', quotas.ProjectsQuotasController())
|
||||
setattr(self, 'secret-stores', secretstores.SecretStoresController())
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self):
|
||||
|
|
|
@ -170,7 +170,7 @@ class HSMCommands(object):
|
|||
self.pkcs11.generate_key(int(length), self.session, str(label),
|
||||
encrypt=True, wrap=True, master_key=True)
|
||||
self.pkcs11.return_session(self.session)
|
||||
print ("MKEK successfully generated!")
|
||||
print("MKEK successfully generated!")
|
||||
|
||||
gen_hmac_description = "Generates a new HMAC key"
|
||||
|
||||
|
@ -193,7 +193,7 @@ class HSMCommands(object):
|
|||
self.pkcs11.generate_key(int(length), self.session, str(label),
|
||||
sign=True, master_key=True)
|
||||
self.pkcs11.return_session(self.session)
|
||||
print ("HMAC successfully generated!")
|
||||
print("HMAC successfully generated!")
|
||||
|
||||
rewrap_pkek_description = "Re-wrap project MKEKs"
|
||||
|
||||
|
@ -214,7 +214,7 @@ class HSMCommands(object):
|
|||
def _verify_label_does_not_exist(self, label, session):
|
||||
key_handle = self.pkcs11.get_key_handle(label, session)
|
||||
if key_handle:
|
||||
print (
|
||||
print(
|
||||
"The label {label} already exists! "
|
||||
"Please try again.".format(label=label)
|
||||
)
|
||||
|
|
|
@ -79,16 +79,18 @@ class KekRewrap(object):
|
|||
kek_data = iv + wrapped_key
|
||||
self.pkcs11.verify_hmac(kek_mkhk, hmac, kek_data, session)
|
||||
# Unwrap KEK
|
||||
kek = self.pkcs11.unwrap_key(kek_mkek, iv, wrapped_key, session)
|
||||
current_kek = self.pkcs11.unwrap_key(kek_mkek, iv, wrapped_key,
|
||||
session)
|
||||
|
||||
# Wrap KEK with new master keys
|
||||
new_kek = self.pkcs11.wrap_key(self.new_mkek, kek, session)
|
||||
new_kek = self.pkcs11.wrap_key(self.new_mkek, current_kek,
|
||||
session)
|
||||
# Compute HMAC for rewrapped KEK
|
||||
new_kek_data = new_kek['iv'] + new_kek['wrapped_key']
|
||||
new_hmac = self.pkcs11.compute_hmac(self.new_mkhk, new_kek_data,
|
||||
session)
|
||||
# Destroy unwrapped KEK
|
||||
self.pkcs11.destroy_object(kek, session)
|
||||
self.pkcs11.destroy_object(current_kek, session)
|
||||
|
||||
# Build updated meta dict
|
||||
updated_meta = meta_dict.copy()
|
||||
|
|
|
@ -86,7 +86,7 @@ class KeyGenerator(object):
|
|||
def verify_label_does_not_exist(self, label, session):
|
||||
key_handle = self.pkcs11.get_key_handle(label, session)
|
||||
if key_handle:
|
||||
print (
|
||||
print(
|
||||
"The label {label} already exists! "
|
||||
"Please try again.".format(label=label)
|
||||
)
|
||||
|
@ -97,7 +97,7 @@ class KeyGenerator(object):
|
|||
self.verify_label_does_not_exist(args.label, self.session)
|
||||
self.pkcs11.generate_key(int(args.length), self.session, args.label,
|
||||
encrypt=True, wrap=True, master_key=True)
|
||||
print ("MKEK successfully generated!")
|
||||
print("MKEK successfully generated!")
|
||||
|
||||
def generate_hmac(self, args):
|
||||
"""Process the generate HMAC with given arguments"""
|
||||
|
@ -105,7 +105,7 @@ class KeyGenerator(object):
|
|||
self.pkcs11.generate_key(int(args.length), self.session,
|
||||
args.label, sign=True,
|
||||
master_key=True)
|
||||
print ("HMAC successfully generated!")
|
||||
print("HMAC successfully generated!")
|
||||
|
||||
def execute(self):
|
||||
"""Parse the command line arguments."""
|
||||
|
|
|
@ -264,3 +264,20 @@ def set_middleware_defaults():
|
|||
CONF = new_config()
|
||||
LOG = logging.getLogger(__name__)
|
||||
parse_args(CONF)
|
||||
|
||||
# Adding global scope dict for all different configs created in various
|
||||
# modules. In barbican, each plugin module creates its own *new* config
|
||||
# instance so its error prone to share/access config values across modules
|
||||
# as these module imports introduce a cyclic dependency. To avoid this, each
|
||||
# plugin can set this dict after its own config instance is created and parsed.
|
||||
_CONFIGS = {}
|
||||
|
||||
|
||||
def set_module_config(name, module_conf):
|
||||
"""Each plugin can set its own conf instance with its group name."""
|
||||
_CONFIGS[name] = module_conf
|
||||
|
||||
|
||||
def get_module_config(name):
|
||||
"""Get handle to plugin specific config instance by its group name."""
|
||||
return _CONFIGS[name]
|
||||
|
|
|
@ -279,7 +279,7 @@ class LimitExceeded(BarbicanHTTPException):
|
|||
|
||||
|
||||
class ServiceUnavailable(BarbicanException):
|
||||
message = u._("The request returned 503 Service Unavilable. This "
|
||||
message = u._("The request returned 503 Service Unavailable. This "
|
||||
"generally occurs on service overload or other transient "
|
||||
"outage.")
|
||||
|
||||
|
@ -530,3 +530,60 @@ class P11CryptoKeyHandleException(PKCS11Exception):
|
|||
|
||||
class P11CryptoTokenException(PKCS11Exception):
|
||||
message = u._("No token was found in slot %(slot_id)s")
|
||||
|
||||
|
||||
class MultipleStorePreferredPluginMissing(BarbicanException):
|
||||
"""Raised when a preferred plugin is missing in service configuration."""
|
||||
def __init__(self, store_name):
|
||||
super(MultipleStorePreferredPluginMissing, self).__init__(
|
||||
u._("Preferred Secret Store plugin '{store_name}' is not "
|
||||
"currently set in service configuration. This is probably a "
|
||||
"server misconfiguration.").format(
|
||||
store_name=store_name)
|
||||
)
|
||||
self.store_name = store_name
|
||||
|
||||
|
||||
class MultipleStorePluginStillInUse(BarbicanException):
|
||||
"""Raised when a used plugin is missing in service configuration."""
|
||||
def __init__(self, store_name):
|
||||
super(MultipleStorePluginStillInUse, self).__init__(
|
||||
u._("Secret Store plugin '{store_name}' is still in use and can "
|
||||
"not be removed. Its missing in service configuration. This is"
|
||||
" probably a server misconfiguration.").format(
|
||||
store_name=store_name)
|
||||
)
|
||||
self.store_name = store_name
|
||||
|
||||
|
||||
class MultipleSecretStoreLookupFailed(BarbicanException):
|
||||
"""Raised when a plugin lookup suffix is missing during config read."""
|
||||
def __init__(self):
|
||||
msg = u._("Plugin lookup property 'stores_lookup_suffix' is not "
|
||||
"defined in service configuration")
|
||||
super(MultipleSecretStoreLookupFailed, self).__init__(msg)
|
||||
|
||||
|
||||
class MultipleStoreIncorrectGlobalDefault(BarbicanException):
|
||||
"""Raised when a global default for only one plugin is not set to True."""
|
||||
def __init__(self, occurence):
|
||||
msg = None
|
||||
if occurence > 1:
|
||||
msg = u._("There are {count} plugins with global default as "
|
||||
"True in service configuration. Only one plugin can have"
|
||||
" this as True").format(count=occurence)
|
||||
else:
|
||||
msg = u._("There is no plugin defined with global default as True."
|
||||
" One of plugin must be identified as global default")
|
||||
|
||||
super(MultipleStoreIncorrectGlobalDefault, self).__init__(msg)
|
||||
|
||||
|
||||
class MultipleStorePluginValueMissing(BarbicanException):
|
||||
"""Raised when a store plugin value is missing in service configuration."""
|
||||
def __init__(self, section_name):
|
||||
super(MultipleStorePluginValueMissing, self).__init__(
|
||||
u._("In section '{0}', secret_store_plugin value is missing"
|
||||
).format(section_name)
|
||||
)
|
||||
self.section_name = section_name
|
||||
|
|
|
@ -56,6 +56,11 @@ def convert_certificate_authority_to_href(ca_id):
|
|||
return convert_resource_id_to_href('cas', ca_id)
|
||||
|
||||
|
||||
def convert_secret_stores_to_href(secret_store_id):
|
||||
"""Convert the ca ID to a HATEOAS-style href."""
|
||||
return convert_resource_id_to_href('secret-stores', secret_store_id)
|
||||
|
||||
|
||||
# TODO(hgedikli) handle list of fields in here
|
||||
def convert_to_hrefs(fields):
|
||||
"""Convert id's within a fields dict to HATEOAS-style hrefs."""
|
||||
|
|
|
@ -36,6 +36,13 @@ CONF = config.CONF
|
|||
# Current API version
|
||||
API_VERSION = 'v1'
|
||||
|
||||
# Added here to remove cyclic dependency.
|
||||
# In barbican.model.models module SecretType.OPAQUE was imported from
|
||||
# barbican.plugin.interface.secret_store which introduces a cyclic dependency
|
||||
# if `secret_store` plugin needs to use db model classes. So moving shared
|
||||
# value to another common python module which is already imported in both.
|
||||
SECRET_TYPE_OPAQUE = "opaque"
|
||||
|
||||
|
||||
def _do_allow_certain_content_types(func, content_types_list=[]):
|
||||
# Allows you to bypass pecan's content-type restrictions
|
||||
|
@ -176,3 +183,8 @@ def get_class_for(module_name, class_name):
|
|||
|
||||
def generate_uuid():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def is_multiple_backends_enabled():
|
||||
secretstore_conf = config.get_module_config('secretstore')
|
||||
return secretstore_conf.secretstore.enable_multiple_secret_stores
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import inspect
|
||||
import oslo_context
|
||||
from oslo_policy import policy
|
||||
|
||||
|
@ -29,23 +28,12 @@ class RequestContext(oslo_context.context.RequestContext):
|
|||
accesses the system, as well as additional request information.
|
||||
"""
|
||||
|
||||
def __init__(self, roles=None, policy_enforcer=None, project=None,
|
||||
**kwargs):
|
||||
def __init__(self, policy_enforcer=None, project=None, **kwargs):
|
||||
# prefer usage of 'project' instead of 'tenant'
|
||||
if project:
|
||||
kwargs['tenant'] = project
|
||||
self.project = project
|
||||
self.policy_enforcer = policy_enforcer or policy.Enforcer(CONF)
|
||||
|
||||
# NOTE(edtubill): oslo_context 2.2.0 now has a roles attribute in
|
||||
# the RequestContext. This will make sure of backwards compatibility
|
||||
# with past oslo_context versions.
|
||||
argspec = inspect.getargspec(super(RequestContext, self).__init__)
|
||||
if 'roles' in argspec.args:
|
||||
kwargs['roles'] = roles
|
||||
else:
|
||||
self.roles = roles or []
|
||||
|
||||
super(RequestContext, self).__init__(**kwargs)
|
||||
|
||||
def to_dict(self):
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
# Jiong Liu <liujiong@gohighsec.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: barbican 3.0.0.0b3.dev18\n"
|
||||
"Project-Id-Version: barbican 3.0.0.0b4.dev3\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-08-05 03:25+0000\n"
|
||||
"POT-Creation-Date: 2016-09-07 03:23+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-07-26 09:43+0000\n"
|
||||
"PO-Revision-Date: 2016-09-01 01:38+0000\n"
|
||||
"Last-Translator: Jiong Liu <liujiong@gohighsec.com>\n"
|
||||
"Language: zh-CN\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
@ -778,12 +778,6 @@ msgstr "NSS证书数据库的密码"
|
|||
msgid "Password to login to PKCS11 session"
|
||||
msgstr "登录到PKCS11会话的密码"
|
||||
|
||||
msgid "Path to CA certicate chain file"
|
||||
msgstr "CA证书链文件的路径"
|
||||
|
||||
msgid "Path to CA certicate file"
|
||||
msgstr "CA证书文件的路径"
|
||||
|
||||
msgid "Path to CA certificate key file"
|
||||
msgstr "CA证书密钥文件的路径"
|
||||
|
||||
|
@ -1368,4 +1362,4 @@ msgid "{request} not found for {operation} for order_id {order_id}"
|
|||
msgstr "未找到order_id为{order_id}的{operation}操作的{request}请求"
|
||||
|
||||
msgid "{schema_name}' within '{parent_schema_name}"
|
||||
msgstr "带有'{parent_schema_name}'的'{schema_name}'"
|
||||
msgstr "带有{parent_schema_name}的{schema_name}'"
|
||||
|
|
|
@ -297,7 +297,7 @@ def soft_delete_expired_secrets(threshold_date):
|
|||
update_count += children_count
|
||||
LOG.info(u._LI("Soft deleted %(update_count)s entries due to secret "
|
||||
"expiration and %(acl_total)s secret acl entries "
|
||||
"wereremoved from the database") %
|
||||
"were removed from the database") %
|
||||
{'update_count': update_count,
|
||||
'acl_total': acl_total})
|
||||
return update_count + acl_total
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
"""Model for multiple backend support
|
||||
|
||||
Revision ID: 39cf2e645cba
|
||||
Revises: d2780d5aa510
|
||||
Create Date: 2016-07-29 16:45:22.953811
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '39cf2e645cba'
|
||||
down_revision = 'd2780d5aa510'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
ctx = op.get_context()
|
||||
con = op.get_bind()
|
||||
table_exists = ctx.dialect.has_table(con.engine, 'secret_stores')
|
||||
if not table_exists:
|
||||
op.create_table(
|
||||
'secret_stores',
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', sa.Boolean(), nullable=False),
|
||||
sa.Column('status', sa.String(length=20), nullable=False),
|
||||
sa.Column('store_plugin', sa.String(length=255), nullable=False),
|
||||
sa.Column('crypto_plugin', sa.String(length=255), nullable=True),
|
||||
sa.Column('global_default', sa.Boolean(), nullable=False,
|
||||
default=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('store_plugin', 'crypto_plugin',
|
||||
name='_secret_stores_plugin_names_uc'),
|
||||
sa.UniqueConstraint('name',
|
||||
name='_secret_stores_name_uc')
|
||||
)
|
||||
|
||||
table_exists = ctx.dialect.has_table(con.engine, 'project_secret_store')
|
||||
if not table_exists:
|
||||
op.create_table(
|
||||
'project_secret_store',
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', sa.Boolean(), nullable=False),
|
||||
sa.Column('status', sa.String(length=20), nullable=False),
|
||||
sa.Column('project_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('secret_store_id', sa.String(length=36), nullable=False),
|
||||
sa.ForeignKeyConstraint(['project_id'], ['projects.id'],),
|
||||
sa.ForeignKeyConstraint(
|
||||
['secret_store_id'], ['secret_stores.id'],),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('project_id',
|
||||
name='_project_secret_store_project_uc')
|
||||
)
|
||||
op.create_index(op.f('ix_project_secret_store_project_id'),
|
||||
'project_secret_store', ['project_id'], unique=True)
|
|
@ -31,7 +31,6 @@ from sqlalchemy import types as sql_types
|
|||
from barbican.common import exception
|
||||
from barbican.common import utils
|
||||
from barbican import i18n as u
|
||||
from barbican.plugin.interface import secret_store
|
||||
|
||||
LOG = utils.getLogger(__name__)
|
||||
BASE = declarative.declarative_base()
|
||||
|
@ -277,7 +276,7 @@ class Secret(BASE, SoftDeleteMixIn, ModelBase):
|
|||
|
||||
name = sa.Column(sa.String(255))
|
||||
secret_type = sa.Column(sa.String(255),
|
||||
server_default=secret_store.SecretType.OPAQUE)
|
||||
server_default=utils.SECRET_TYPE_OPAQUE)
|
||||
expiration = sa.Column(sa.DateTime, default=None)
|
||||
algorithm = sa.Column(sa.String(255))
|
||||
bit_length = sa.Column(sa.Integer)
|
||||
|
@ -317,7 +316,7 @@ class Secret(BASE, SoftDeleteMixIn, ModelBase):
|
|||
self.name = parsed_request.get('name')
|
||||
self.secret_type = parsed_request.get(
|
||||
'secret_type',
|
||||
secret_store.SecretType.OPAQUE)
|
||||
utils.SECRET_TYPE_OPAQUE)
|
||||
expiration = self._iso_to_datetime(parsed_request.get
|
||||
('expiration'))
|
||||
self.expiration = expiration
|
||||
|
@ -1379,3 +1378,102 @@ class ProjectQuotas(BASE, ModelBase):
|
|||
if self.cas:
|
||||
ret['cas'] = self.cas
|
||||
return ret
|
||||
|
||||
|
||||
class SecretStores(BASE, ModelBase):
|
||||
"""List of secret stores defined via service configuration.
|
||||
|
||||
This class provides a list of secret stores entities with their respective
|
||||
secret store plugin and crypto plugin names.
|
||||
|
||||
SecretStores deletes are NOT soft-deletes.
|
||||
"""
|
||||
|
||||
__tablename__ = 'secret_stores'
|
||||
|
||||
store_plugin = sa.Column(sa.String(255), nullable=False)
|
||||
crypto_plugin = sa.Column(sa.String(255), nullable=True)
|
||||
global_default = sa.Column(sa.Boolean, nullable=False, default=False)
|
||||
name = sa.Column(sa.String(255), nullable=False)
|
||||
|
||||
__table_args__ = (sa.UniqueConstraint(
|
||||
'store_plugin', 'crypto_plugin',
|
||||
name='_secret_stores_plugin_names_uc'),
|
||||
sa.UniqueConstraint('name', name='_secret_stores_name_uc'),)
|
||||
|
||||
def __init__(self, name, store_plugin, crypto_plugin=None,
|
||||
global_default=None):
|
||||
"""Creates secret store entity."""
|
||||
super(SecretStores, self).__init__()
|
||||
|
||||
msg = u._("Must supply non-Blank {0} argument for SecretStores entry.")
|
||||
|
||||
if not name:
|
||||
raise exception.MissingArgumentError(msg.format("name"))
|
||||
if not store_plugin:
|
||||
raise exception.MissingArgumentError(msg.format("store_plugin"))
|
||||
|
||||
self.store_plugin = store_plugin
|
||||
self.name = name
|
||||
self.crypto_plugin = crypto_plugin
|
||||
if global_default is not None:
|
||||
self.global_default = global_default
|
||||
|
||||
self.status = States.ACTIVE
|
||||
|
||||
def _do_extra_dict_fields(self):
|
||||
"""Sub-class hook method: return dict of fields."""
|
||||
return {'secret_store_id': self.id,
|
||||
'store_plugin': self.store_plugin,
|
||||
'crypto_plugin': self.crypto_plugin,
|
||||
'global_default': self.global_default,
|
||||
'name': self.name}
|
||||
|
||||
|
||||
class ProjectSecretStore(BASE, ModelBase):
|
||||
"""Stores secret store to be used for new project secrets.
|
||||
|
||||
This class maintains secret store and project mapping so that new project
|
||||
secret entries uses it as plugin backend.
|
||||
|
||||
ProjectSecretStores deletes are NOT soft-deletes.
|
||||
"""
|
||||
|
||||
__tablename__ = 'project_secret_store'
|
||||
|
||||
secret_store_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('secret_stores.id'),
|
||||
index=True,
|
||||
nullable=False)
|
||||
project_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('projects.id'),
|
||||
index=True,
|
||||
nullable=False)
|
||||
|
||||
secret_store = orm.relationship("SecretStores", backref="project_store")
|
||||
project = orm.relationship('Project',
|
||||
backref=orm.backref('preferred_secret_store'))
|
||||
|
||||
__table_args__ = (sa.UniqueConstraint(
|
||||
'project_id', name='_project_secret_store_project_uc'),)
|
||||
|
||||
def __init__(self, project_id, secret_store_id):
|
||||
"""Creates project secret store mapping entity."""
|
||||
super(ProjectSecretStore, self).__init__()
|
||||
|
||||
msg = u._("Must supply non-None {0} argument for ProjectSecretStore "
|
||||
" entry.")
|
||||
|
||||
if not project_id:
|
||||
raise exception.MissingArgumentError(msg.format("project_id"))
|
||||
self.project_id = project_id
|
||||
if not secret_store_id:
|
||||
raise exception.MissingArgumentError(msg.format("secret_store_id"))
|
||||
self.secret_store_id = secret_store_id
|
||||
|
||||
self.status = States.ACTIVE
|
||||
|
||||
def _do_extra_dict_fields(self):
|
||||
"""Sub-class hook method: return dict of fields."""
|
||||
return {'secret_store_id': self.secret_store_id,
|
||||
'project_id': self.project_id}
|
||||
|
|
|
@ -68,6 +68,8 @@ _SECRET_META_REPOSITORY = None
|
|||
_SECRET_USER_META_REPOSITORY = None
|
||||
_SECRET_REPOSITORY = None
|
||||
_TRANSPORT_KEY_REPOSITORY = None
|
||||
_SECRET_STORES_REPOSITORY = None
|
||||
_PROJECT_SECRET_STORE_REPOSITORY = None
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
|
@ -104,6 +106,7 @@ def setup_database_engine_and_factory():
|
|||
# session instance per thread.
|
||||
session_maker = sa_orm.sessionmaker(bind=_ENGINE)
|
||||
_SESSION_FACTORY = sqlalchemy.orm.scoped_session(session_maker)
|
||||
_initialize_secret_stores_data()
|
||||
|
||||
|
||||
def start():
|
||||
|
@ -202,6 +205,18 @@ def _get_engine(engine):
|
|||
return engine
|
||||
|
||||
|
||||
def _initialize_secret_stores_data():
|
||||
"""Initializes secret stores data in database.
|
||||
|
||||
This logic is executed only when database engine and factory is built.
|
||||
Secret store get_manager internally reads secret store plugin configuration
|
||||
from service configuration and saves it in secret_stores table in database.
|
||||
"""
|
||||
if utils.is_multiple_backends_enabled():
|
||||
from barbican.plugin.interface import secret_store
|
||||
secret_store.get_manager()
|
||||
|
||||
|
||||
def is_db_connection_error(args):
|
||||
"""Return True if error in connecting to db."""
|
||||
# NOTE(adam_g): This is currently MySQL specific and needs to be extended
|
||||
|
@ -2189,6 +2204,153 @@ class ProjectQuotasRepo(BaseRepo):
|
|||
entity.delete(session=session)
|
||||
|
||||
|
||||
class SecretStoresRepo(BaseRepo):
|
||||
"""Repository for the SecretStores entity.
|
||||
|
||||
SecretStores entries are not soft delete. So there is no
|
||||
need to have deleted=False filter in queries.
|
||||
"""
|
||||
|
||||
def get_all(self, session=None):
|
||||
"""Get list of available secret stores.
|
||||
|
||||
Status value is not used while getting complete list as
|
||||
we will just maintain ACTIVE ones. No other state is used and
|
||||
needed here.
|
||||
:param session: SQLAlchemy session object.
|
||||
:return: None
|
||||
"""
|
||||
session = self.get_session(session)
|
||||
query = session.query(models.SecretStores)
|
||||
query.order_by(models.SecretStores.created_at.asc())
|
||||
return query.all()
|
||||
|
||||
def _do_entity_name(self):
|
||||
"""Sub-class hook: return entity name, such as for debugging."""
|
||||
return "SecretStores"
|
||||
|
||||
def _do_build_get_query(self, entity_id, external_project_id, session):
|
||||
"""Sub-class hook: build a retrieve query."""
|
||||
return session.query(models.SecretStores).filter_by(
|
||||
id=entity_id)
|
||||
|
||||
def _do_validate(self, values):
|
||||
"""Sub-class hook: validate values."""
|
||||
pass
|
||||
|
||||
|
||||
class ProjectSecretStoreRepo(BaseRepo):
|
||||
"""Repository for the ProjectSecretStore entity.
|
||||
|
||||
ProjectSecretStore entries are not soft delete. So there is no
|
||||
need to have deleted=False filter in queries.
|
||||
"""
|
||||
|
||||
def get_secret_store_for_project(self, project_id, external_project_id,
|
||||
suppress_exception=False, session=None):
|
||||
"""Returns preferred secret store for a project if set.
|
||||
|
||||
:param project_id: ID of project whose preferred secret store is set
|
||||
:param external_project_id: external ID of project whose preferred
|
||||
secret store is set
|
||||
:param suppress_exception: when True, NotFound is not raised
|
||||
:param session: SQLAlchemy session object.
|
||||
|
||||
Will return preferred secret store by external project id if provided
|
||||
otherwise uses barbican project identifier to lookup.
|
||||
|
||||
Throws exception in case no preferred secret store is defined and
|
||||
supporess_exception=False. If suppress_exception is True, then returns
|
||||
None for no preferred secret store for a project found.
|
||||
"""
|
||||
session = self.get_session(session)
|
||||
if external_project_id is None:
|
||||
query = session.query(models.ProjectSecretStore).filter_by(
|
||||
project_id=project_id)
|
||||
else:
|
||||
query = session.query(models.ProjectSecretStore)
|
||||
query = query.join(models.Project,
|
||||
models.ProjectSecretStore.project)
|
||||
query = query.filter(models.Project.external_id ==
|
||||
external_project_id)
|
||||
try:
|
||||
entity = query.one()
|
||||
except sa_orm.exc.NoResultFound:
|
||||
LOG.info(u._LE("No preferred secret store found for project = %s"),
|
||||
project_id)
|
||||
entity = None
|
||||
if not suppress_exception:
|
||||
_raise_entity_not_found(self._do_entity_name(), project_id)
|
||||
return entity
|
||||
|
||||
def create_or_update_for_project(self, project_id, secret_store_id,
|
||||
session=None):
|
||||
"""Create or update preferred secret store for a project.
|
||||
|
||||
:param project_id: ID of project whose preferred secret store is set
|
||||
:param secret_store_id: ID of secret store
|
||||
:param session: SQLAlchemy session object.
|
||||
:return: None
|
||||
|
||||
If preferred secret store is not set for given project, then create
|
||||
new preferred secret store setting for that project. If secret store
|
||||
setting for project is already there, then it updates with given secret
|
||||
store id.
|
||||
"""
|
||||
session = self.get_session(session)
|
||||
try:
|
||||
entity = self.get_secret_store_for_project(project_id, None,
|
||||
session=session)
|
||||
except exception.NotFound:
|
||||
entity = self.create_from(
|
||||
models.ProjectSecretStore(project_id, secret_store_id),
|
||||
session=session)
|
||||
else:
|
||||
entity.secret_store_id = secret_store_id
|
||||
entity.save(session)
|
||||
return entity
|
||||
|
||||
def get_count_by_secret_store(self, secret_store_id, session=None):
|
||||
"""Gets count of projects mapped to a given secret store.
|
||||
|
||||
:param secret_store_id: id of secret stores entity
|
||||
:param session: existing db session reference. If None, gets session.
|
||||
:return: an number 0 or greater
|
||||
|
||||
This method is supposed to provide count of projects which are
|
||||
currently set to use input secret store as their preferred store. This
|
||||
is used when existing secret store configuration is removed and
|
||||
validation is done to make sure that there are no projects using it as
|
||||
preferred secret store.
|
||||
"""
|
||||
session = self.get_session(session)
|
||||
query = session.query(models.ProjectSecretStore).filter_by(
|
||||
secret_store_id=secret_store_id)
|
||||
return query.count()
|
||||
|
||||
def _do_entity_name(self):
|
||||
"""Sub-class hook: return entity name, such as for debugging."""
|
||||
return "ProjectSecretStore"
|
||||
|
||||
def _do_build_get_query(self, entity_id, external_project_id, session):
|
||||
"""Sub-class hook: build a retrieve query."""
|
||||
return session.query(models.ProjectSecretStore).filter_by(
|
||||
id=entity_id)
|
||||
|
||||
def _do_validate(self, values):
|
||||
"""Sub-class hook: validate values."""
|
||||
pass
|
||||
|
||||
def _build_get_project_entities_query(self, project_id, session):
|
||||
"""Builds query for getting preferred secret stores list for a project.
|
||||
|
||||
:param project_id: id of barbican project entity
|
||||
:param session: existing db session reference.
|
||||
"""
|
||||
return session.query(models.ProjectSecretStore).filter_by(
|
||||
project_id=project_id)
|
||||
|
||||
|
||||
def get_ca_repository():
|
||||
"""Returns a singleton Secret repository instance."""
|
||||
global _CA_REPOSITORY
|
||||
|
@ -2316,6 +2478,19 @@ def get_transport_key_repository():
|
|||
return _get_repository(_TRANSPORT_KEY_REPOSITORY, TransportKeyRepo)
|
||||
|
||||
|
||||
def get_secret_stores_repository():
|
||||
"""Returns a singleton Secret Stores repository instance."""
|
||||
global _SECRET_STORES_REPOSITORY
|
||||
return _get_repository(_SECRET_STORES_REPOSITORY, SecretStoresRepo)
|
||||
|
||||
|
||||
def get_project_secret_store_repository():
|
||||
"""Returns a singleton Project Secret Store repository instance."""
|
||||
global _PROJECT_SECRET_STORE_REPOSITORY
|
||||
return _get_repository(_PROJECT_SECRET_STORE_REPOSITORY,
|
||||
ProjectSecretStoreRepo)
|
||||
|
||||
|
||||
def _get_repository(global_ref, repo_class):
|
||||
if not global_ref:
|
||||
global_ref = repo_class()
|
||||
|
|
|
@ -240,6 +240,18 @@ class CryptoPluginBase(object):
|
|||
persist the data that is assigned to these DTOs by the plugin.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_plugin_name(self):
|
||||
"""Gets user friendly plugin name.
|
||||
|
||||
This plugin name is expected to be read from config file.
|
||||
There will be a default defined for plugin name which can be customized
|
||||
in specific deployment if needed.
|
||||
|
||||
This name needs to be unique across a deployment.
|
||||
"""
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
@abc.abstractmethod
|
||||
def encrypt(self, encrypt_dto, kek_meta_dto, project_id):
|
||||
"""Encryption handler function
|
||||
|
|
|
@ -20,6 +20,7 @@ from barbican.common import utils
|
|||
from barbican import i18n as u
|
||||
from barbican.plugin.crypto import crypto
|
||||
from barbican.plugin.interface import secret_store
|
||||
from barbican.plugin.util import multiple_backends
|
||||
from barbican.plugin.util import utils as plugin_utils
|
||||
|
||||
|
||||
|
@ -47,6 +48,8 @@ CONF.register_group(crypto_opt_group)
|
|||
CONF.register_opts(crypto_opts, group=crypto_opt_group)
|
||||
config.parse_args(CONF)
|
||||
|
||||
config.set_module_config("crypto", CONF)
|
||||
|
||||
|
||||
class _CryptoPluginManager(named.NamedExtensionManager):
|
||||
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
|
||||
|
@ -57,26 +60,31 @@ class _CryptoPluginManager(named.NamedExtensionManager):
|
|||
initializing a new instance of this class use the PLUGIN_MANAGER
|
||||
at the module level.
|
||||
"""
|
||||
crypto_conf = config.get_module_config('crypto')
|
||||
plugin_names = self._get_internal_plugin_names(crypto_conf)
|
||||
|
||||
super(_CryptoPluginManager, self).__init__(
|
||||
conf.crypto.namespace,
|
||||
conf.crypto.enabled_crypto_plugins,
|
||||
crypto_conf.crypto.namespace,
|
||||
plugin_names,
|
||||
invoke_on_load=False, # Defer creating plugins to utility below.
|
||||
invoke_args=invoke_args,
|
||||
invoke_kwds=invoke_kwargs
|
||||
invoke_kwds=invoke_kwargs,
|
||||
name_order=True # extensions sorted as per order of plugin names
|
||||
)
|
||||
|
||||
plugin_utils.instantiate_plugins(
|
||||
self, invoke_args, invoke_kwargs)
|
||||
|
||||
def get_plugin_store_generate(self, type_needed, algorithm=None,
|
||||
bit_length=None, mode=None):
|
||||
bit_length=None, mode=None, project_id=None):
|
||||
"""Gets a secret store or generate plugin that supports provided type.
|
||||
|
||||
:param type_needed: PluginSupportTypes that contains details on the
|
||||
type of plugin required
|
||||
:returns: CryptoPluginBase plugin implementation
|
||||
"""
|
||||
active_plugins = plugin_utils.get_active_plugins(self)
|
||||
active_plugins = multiple_backends.get_applicable_crypto_plugins(
|
||||
self, project_id=project_id, existing_plugin_name=None)
|
||||
|
||||
if not active_plugins:
|
||||
raise crypto.CryptoPluginNotFound()
|
||||
|
@ -111,6 +119,23 @@ class _CryptoPluginManager(named.NamedExtensionManager):
|
|||
|
||||
return decrypting_plugin
|
||||
|
||||
def _get_internal_plugin_names(self, crypto_conf):
|
||||
"""Gets plugin names used for loading via stevedore.
|
||||
|
||||
When multiple secret store support is enabled, then crypto plugin names
|
||||
are read via updated configuration structure. If not enabled, then it
|
||||
reads MultiStr property in 'crypto' config section.
|
||||
"""
|
||||
# to cache default global secret store value on first use
|
||||
self.global_default_store_dict = None
|
||||
if utils.is_multiple_backends_enabled():
|
||||
parsed_stores = multiple_backends.read_multiple_backends_config()
|
||||
plugin_names = [store.crypto_plugin for store in parsed_stores
|
||||
if store.crypto_plugin]
|
||||
else:
|
||||
plugin_names = crypto_conf.crypto.enabled_crypto_plugins
|
||||
return plugin_names
|
||||
|
||||
|
||||
def get_manager():
|
||||
"""Return a singleton crypto plugin manager."""
|
||||
|
|
|
@ -69,6 +69,9 @@ p11_crypto_plugin_opts = [
|
|||
cfg.IntOpt('seed_length',
|
||||
help=u._('Amount of data to read from file for seed'),
|
||||
default=32),
|
||||
cfg.StrOpt('plugin_name',
|
||||
help=u._('User friendly plugin name'),
|
||||
default='PKCS11 HSM'),
|
||||
]
|
||||
CONF.register_group(p11_crypto_plugin_group)
|
||||
CONF.register_opts(p11_crypto_plugin_opts, group=p11_crypto_plugin_group)
|
||||
|
@ -104,6 +107,9 @@ class P11CryptoPlugin(plugin.CryptoPluginBase):
|
|||
|
||||
self._configure_object_cache()
|
||||
|
||||
def get_plugin_name(self):
|
||||
return self.conf.p11_crypto_plugin.plugin_name
|
||||
|
||||
def encrypt(self, encrypt_dto, kek_meta_dto, project_id):
|
||||
return self._call_pkcs11(self._encrypt, encrypt_dto, kek_meta_dto,
|
||||
project_id)
|
||||
|
|
|
@ -34,7 +34,10 @@ simple_crypto_plugin_opts = [
|
|||
cfg.StrOpt('kek',
|
||||
default='dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=',
|
||||
help=u._('Key encryption key to be used by Simple Crypto '
|
||||
'Plugin'), secret=True)
|
||||
'Plugin'), secret=True),
|
||||
cfg.StrOpt('plugin_name',
|
||||
help=u._('User friendly plugin name'),
|
||||
default='Software Only Crypto'),
|
||||
]
|
||||
CONF.register_group(simple_crypto_plugin_group)
|
||||
CONF.register_opts(simple_crypto_plugin_opts, group=simple_crypto_plugin_group)
|
||||
|
@ -46,11 +49,15 @@ class SimpleCryptoPlugin(c.CryptoPluginBase):
|
|||
|
||||
def __init__(self, conf=CONF):
|
||||
self.master_kek = conf.simple_crypto_plugin.kek
|
||||
self.plugin_name = conf.simple_crypto_plugin.plugin_name
|
||||
LOG.warning(u._LW("This plugin is NOT meant for a production "
|
||||
"environment. This is meant just for development "
|
||||
"and testing purposes. Please use another plugin "
|
||||
"for production."))
|
||||
|
||||
def get_plugin_name(self):
|
||||
return self.plugin_name
|
||||
|
||||
def _get_kek(self, kek_meta_dto):
|
||||
if not kek_meta_dto.plugin_meta:
|
||||
raise ValueError(u._('KEK not yet created.'))
|
||||
|
|
|
@ -74,7 +74,10 @@ dogtag_plugin_opts = [
|
|||
default=cm.CA_INFO_DEFAULT_EXPIRATION_DAYS,
|
||||
help=u._('Time in days for CA entries to expire')),
|
||||
cfg.StrOpt('plugin_working_dir',
|
||||
help=u._('Working directory for Dogtag plugin'))
|
||||
help=u._('Working directory for Dogtag plugin')),
|
||||
cfg.StrOpt('plugin_name',
|
||||
help=u._('User friendly plugin name'),
|
||||
default='Dogtag KRA'),
|
||||
]
|
||||
|
||||
CONF.register_group(dogtag_plugin_group)
|
||||
|
@ -203,9 +206,13 @@ class DogtagKRAPlugin(sstore.SecretStoreBase):
|
|||
self.keyclient = kraclient.keys
|
||||
|
||||
self.keyclient.set_transport_cert(KRA_TRANSPORT_NICK)
|
||||
self.plugin_name = conf.dogtag_plugin.plugin_name
|
||||
|
||||
LOG.debug(u._("completed DogtagKRAPlugin init"))
|
||||
|
||||
def get_plugin_name(self):
|
||||
return self.plugin_name
|
||||
|
||||
def store_secret(self, secret_dto):
|
||||
"""Store a secret in the KRA
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ from barbican.common import config
|
|||
from barbican.common import exception
|
||||
from barbican.common import utils
|
||||
from barbican import i18n as u
|
||||
from barbican.plugin.util import multiple_backends
|
||||
from barbican.plugin.util import utils as plugin_utils
|
||||
|
||||
|
||||
|
@ -42,12 +43,24 @@ store_opts = [
|
|||
cfg.MultiStrOpt('enabled_secretstore_plugins',
|
||||
default=DEFAULT_PLUGINS,
|
||||
help=u._('List of secret store plugins to load.')
|
||||
)
|
||||
),
|
||||
cfg.BoolOpt('enable_multiple_secret_stores',
|
||||
default=False,
|
||||
help=u._('Flag to enable multiple secret store plugin'
|
||||
' backend support. Default is False')
|
||||
),
|
||||
cfg.ListOpt('stores_lookup_suffix',
|
||||
default=None,
|
||||
help=u._('List of suffix to use for looking up plugins which '
|
||||
'are supported with multiple backend support.')
|
||||
)
|
||||
]
|
||||
CONF.register_group(store_opt_group)
|
||||
CONF.register_opts(store_opts, group=store_opt_group)
|
||||
config.parse_args(CONF)
|
||||
|
||||
config.set_module_config("secretstore", CONF)
|
||||
|
||||
|
||||
class SecretStorePluginNotFound(exception.BarbicanHTTPException):
|
||||
"""Raised when no plugins are installed."""
|
||||
|
@ -238,7 +251,7 @@ class SecretType(object):
|
|||
opaque data. Opaque data can be any kind of data. This data type signals to
|
||||
Barbican to just store the information and do not worry about the format or
|
||||
encoding. This is the default type if no type is specified by the user."""
|
||||
OPAQUE = "opaque"
|
||||
OPAQUE = utils.SECRET_TYPE_OPAQUE
|
||||
|
||||
|
||||
class KeyAlgorithm(object):
|
||||
|
@ -345,6 +358,18 @@ class AsymmetricKeyMetadataDTO(object):
|
|||
@six.add_metaclass(abc.ABCMeta)
|
||||
class SecretStoreBase(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_plugin_name(self):
|
||||
"""Gets user friendly plugin name.
|
||||
|
||||
This plugin name is expected to be read from config file.
|
||||
There will be a default defined for plugin name which can be customized
|
||||
in specific deployment if needed.
|
||||
|
||||
This name needs to be unique across a deployment.
|
||||
"""
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
@abc.abstractmethod
|
||||
def generate_symmetric_key(self, key_spec):
|
||||
"""Generate a new symmetric key and store it.
|
||||
|
@ -496,20 +521,25 @@ def _enforce_extensions_configured(plugin_related_function):
|
|||
|
||||
class SecretStorePluginManager(named.NamedExtensionManager):
|
||||
def __init__(self, conf=CONF, invoke_args=(), invoke_kwargs={}):
|
||||
ss_conf = config.get_module_config('secretstore')
|
||||
plugin_names = self._get_internal_plugin_names(ss_conf)
|
||||
|
||||
super(SecretStorePluginManager, self).__init__(
|
||||
conf.secretstore.namespace,
|
||||
conf.secretstore.enabled_secretstore_plugins,
|
||||
ss_conf.secretstore.namespace,
|
||||
plugin_names,
|
||||
invoke_on_load=False, # Defer creating plugins to utility below.
|
||||
invoke_args=invoke_args,
|
||||
invoke_kwds=invoke_kwargs
|
||||
invoke_kwds=invoke_kwargs,
|
||||
name_order=True # extensions sorted as per order of plugin names
|
||||
)
|
||||
|
||||
plugin_utils.instantiate_plugins(
|
||||
self, invoke_args, invoke_kwargs)
|
||||
plugin_utils.instantiate_plugins(self, invoke_args, invoke_kwargs)
|
||||
|
||||
multiple_backends.sync_secret_stores(self)
|
||||
|
||||
@_enforce_extensions_configured
|
||||
def get_plugin_store(self, key_spec, plugin_name=None,
|
||||
transport_key_needed=False):
|
||||
transport_key_needed=False, project_id=None):
|
||||
"""Gets a secret store plugin.
|
||||
|
||||
:param: plugin_name: set to plugin_name to get specific plugin
|
||||
|
@ -518,7 +548,8 @@ class SecretStorePluginManager(named.NamedExtensionManager):
|
|||
key is required.
|
||||
:returns: SecretStoreBase plugin implementation
|
||||
"""
|
||||
active_plugins = plugin_utils.get_active_plugins(self)
|
||||
active_plugins = multiple_backends.get_applicable_store_plugins(
|
||||
self, project_id=project_id, existing_plugin_name=plugin_name)
|
||||
|
||||
if plugin_name is not None:
|
||||
for plugin in active_plugins:
|
||||
|
@ -561,7 +592,7 @@ class SecretStorePluginManager(named.NamedExtensionManager):
|
|||
raise StorePluginNotAvailableOrMisconfigured(plugin_name)
|
||||
|
||||
@_enforce_extensions_configured
|
||||
def get_plugin_generate(self, key_spec):
|
||||
def get_plugin_generate(self, key_spec, project_id=None):
|
||||
"""Gets a secret generate plugin.
|
||||
|
||||
:param key_spec: KeySpec that contains details on the type of key to
|
||||
|
@ -569,11 +600,33 @@ class SecretStorePluginManager(named.NamedExtensionManager):
|
|||
:returns: SecretStoreBase plugin implementation
|
||||
"""
|
||||
|
||||
for plugin in plugin_utils.get_active_plugins(self):
|
||||
active_plugins = multiple_backends.get_applicable_store_plugins(
|
||||
self, project_id=project_id, existing_plugin_name=None)
|
||||
|
||||
for plugin in active_plugins:
|
||||
if plugin.generate_supports(key_spec):
|
||||
return plugin
|
||||
raise SecretStoreSupportedPluginNotFound()
|
||||
|
||||
def _get_internal_plugin_names(self, secretstore_conf):
|
||||
"""Gets plugin names used for loading via stevedore.
|
||||
|
||||
When multiple secret store support is enabled, then secret store plugin
|
||||
names are read via updated configuration structure. If not enabled,
|
||||
then it reads MultiStr property in 'secretstore' config section.
|
||||
"""
|
||||
# to cache default global secret store value on first use
|
||||
self.global_default_store_dict = None
|
||||
if utils.is_multiple_backends_enabled():
|
||||
self.parsed_stores = multiple_backends.\
|
||||
read_multiple_backends_config()
|
||||
plugin_names = [store.store_plugin for store in self.parsed_stores
|
||||
if store.store_plugin]
|
||||
else:
|
||||
plugin_names = secretstore_conf.secretstore.\
|
||||
enabled_secretstore_plugins
|
||||
return plugin_names
|
||||
|
||||
|
||||
def get_manager():
|
||||
global _SECRET_STORE
|
||||
|
|
|
@ -77,7 +77,10 @@ kmip_opts = [
|
|||
cfg.BoolOpt('pkcs1_only',
|
||||
default=False,
|
||||
help=u._('Only support PKCS#1 encoding of asymmetric keys'),
|
||||
)
|
||||
),
|
||||
cfg.StrOpt('plugin_name',
|
||||
help=u._('User friendly plugin name'),
|
||||
default='KMIP HSM'),
|
||||
]
|
||||
CONF.register_group(kmip_opt_group)
|
||||
CONF.register_opts(kmip_opts, group=kmip_opt_group)
|
||||
|
@ -217,6 +220,8 @@ class KMIPSecretStore(ss.SecretStoreBase):
|
|||
enums.CryptographicAlgorithm.RSA: ss.KeyAlgorithm.RSA
|
||||
}
|
||||
|
||||
self.plugin_name = conf.kmip_plugin.plugin_name
|
||||
|
||||
if conf.kmip_plugin.keyfile is not None:
|
||||
self._validate_keyfile_permissions(conf.kmip_plugin.keyfile)
|
||||
|
||||
|
@ -252,6 +257,9 @@ class KMIPSecretStore(ss.SecretStoreBase):
|
|||
username=config.username,
|
||||
password=config.password)
|
||||
|
||||
def get_plugin_name(self):
|
||||
return self.plugin_name
|
||||
|
||||
def generate_symmetric_key(self, key_spec):
|
||||
"""Generate a symmetric key.
|
||||
|
||||
|
|
|
@ -20,14 +20,15 @@ from barbican.plugin import store_crypto
|
|||
from barbican.plugin.util import translations as tr
|
||||
|
||||
|
||||
def _get_transport_key_model(key_spec, transport_key_needed):
|
||||
def _get_transport_key_model(key_spec, transport_key_needed, project_id):
|
||||
key_model = None
|
||||
if transport_key_needed:
|
||||
# get_plugin_store() will throw an exception if no suitable
|
||||
# plugin with transport key is found
|
||||
plugin_manager = secret_store.get_manager()
|
||||
store_plugin = plugin_manager.get_plugin_store(
|
||||
key_spec=key_spec, transport_key_needed=True)
|
||||
key_spec=key_spec, transport_key_needed=True,
|
||||
project_id=project_id)
|
||||
plugin_name = utils.generate_fullname_for(store_plugin)
|
||||
|
||||
key_repo = repos.get_transport_key_repository()
|
||||
|
@ -80,7 +81,8 @@ def store_secret(unencrypted_raw, content_type_raw, content_encoding,
|
|||
# leave. A subsequent call to this method should provide both the Secret
|
||||
# entity created here *and* the secret data to store into it.
|
||||
if not unencrypted_raw:
|
||||
key_model = _get_transport_key_model(key_spec, transport_key_needed)
|
||||
key_model = _get_transport_key_model(key_spec, transport_key_needed,
|
||||
project_id=project_model.id)
|
||||
|
||||
_save_secret_in_repo(secret_model, project_model)
|
||||
return secret_model, key_model
|
||||
|
@ -94,7 +96,8 @@ def store_secret(unencrypted_raw, content_type_raw, content_encoding,
|
|||
|
||||
plugin_manager = secret_store.get_manager()
|
||||
store_plugin = plugin_manager.get_plugin_store(key_spec=key_spec,
|
||||
plugin_name=plugin_name)
|
||||
plugin_name=plugin_name,
|
||||
project_id=project_model.id)
|
||||
|
||||
secret_dto = secret_store.SecretDTO(type=secret_model.secret_type,
|
||||
secret=unencrypted,
|
||||
|
@ -166,7 +169,8 @@ def generate_secret(spec, content_type, project_model):
|
|||
mode=spec.get('mode'))
|
||||
|
||||
plugin_manager = secret_store.get_manager()
|
||||
generate_plugin = plugin_manager.get_plugin_generate(key_spec)
|
||||
generate_plugin = plugin_manager.get_plugin_generate(
|
||||
key_spec, project_id=project_model.id)
|
||||
|
||||
# Create secret model to eventually save metadata to.
|
||||
secret_model = models.Secret(spec)
|
||||
|
@ -192,7 +196,8 @@ def generate_asymmetric_secret(spec, content_type, project_model):
|
|||
passphrase=spec.get('passphrase'))
|
||||
|
||||
plugin_manager = secret_store.get_manager()
|
||||
generate_plugin = plugin_manager.get_plugin_generate(key_spec)
|
||||
generate_plugin = plugin_manager.get_plugin_generate(
|
||||
key_spec, project_id=project_model.id)
|
||||
|
||||
# Create secret models to eventually save metadata to.
|
||||
private_secret_model = models.Secret(spec)
|
||||
|
|
|
@ -74,7 +74,8 @@ class StoreCryptoAdapterPlugin(object):
|
|||
|
||||
# Find HSM-style 'crypto' plugin.
|
||||
encrypting_plugin = manager.get_manager().get_plugin_store_generate(
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT,
|
||||
project_id=context.project_model.id
|
||||
)
|
||||
|
||||
# Find or create a key encryption key metadata.
|
||||
|
@ -163,7 +164,8 @@ class StoreCryptoAdapterPlugin(object):
|
|||
plugin_type,
|
||||
key_spec.alg,
|
||||
key_spec.bit_length,
|
||||
key_spec.mode)
|
||||
key_spec.mode,
|
||||
project_id=context.project_model.id)
|
||||
|
||||
# Find or create a key encryption key metadata.
|
||||
kek_datum_model, kek_meta_dto = _find_or_create_kek_objects(
|
||||
|
@ -197,7 +199,8 @@ class StoreCryptoAdapterPlugin(object):
|
|||
raise sstore.SecretAlgorithmNotSupportedException(key_spec.alg)
|
||||
|
||||
generating_plugin = manager.get_manager().get_plugin_store_generate(
|
||||
plugin_type, key_spec.alg, key_spec.bit_length, None)
|
||||
plugin_type, key_spec.alg, key_spec.bit_length,
|
||||
project_id=context.project_model.id)
|
||||
|
||||
# Find or create a key encryption key metadata.
|
||||
kek_datum_model, kek_meta_dto = _find_or_create_kek_objects(
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
# (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
import collections
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from barbican.common import config
|
||||
from barbican.common import exception
|
||||
from barbican.common import utils
|
||||
from barbican import i18n as u
|
||||
from barbican.model import models as db_models
|
||||
from barbican.model import repositories as db_repos
|
||||
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
LOOKUP_PLUGINS_PREFIX = "secretstore:"
|
||||
|
||||
|
||||
def read_multiple_backends_config():
|
||||
"""Reads and validates multiple backend related configuration.
|
||||
|
||||
Multiple backends configuration is read only when multiple secret store
|
||||
flag is enabled.
|
||||
Configuration is validated to make sure that section specific to
|
||||
provided suffix exists in service configuration. Also validated that only
|
||||
one of section has global_default = True and its not missing.
|
||||
"""
|
||||
conf = config.get_module_config('secretstore')
|
||||
|
||||
parsed_stores = None
|
||||
if utils.is_multiple_backends_enabled():
|
||||
suffix_list = conf.secretstore.stores_lookup_suffix
|
||||
if not suffix_list:
|
||||
raise exception.MultipleSecretStoreLookupFailed()
|
||||
|
||||
def register_options_dynamically(conf, group_name):
|
||||
store_opt_group = cfg.OptGroup(
|
||||
name=group_name, title='Plugins needed for this backend')
|
||||
store_opts = [
|
||||
cfg.StrOpt('secret_store_plugin',
|
||||
default=None,
|
||||
help=u._('Internal name used to identify'
|
||||
'secretstore_plugin')
|
||||
),
|
||||
cfg.StrOpt('crypto_plugin',
|
||||
default=None,
|
||||
help=u._('Internal name used to identify '
|
||||
'crypto_plugin.')
|
||||
),
|
||||
cfg.BoolOpt('global_default',
|
||||
default=False,
|
||||
help=u._('Flag to indicate if this plugin is '
|
||||
'global default plugin for deployment. '
|
||||
'Default is False.')
|
||||
),
|
||||
]
|
||||
conf.register_group(store_opt_group)
|
||||
conf.register_opts(store_opts, group=store_opt_group)
|
||||
|
||||
group_names = []
|
||||
# construct group names using those suffix and dynamically register
|
||||
# oslo config options under that group name
|
||||
for suffix in suffix_list:
|
||||
group_name = LOOKUP_PLUGINS_PREFIX + suffix
|
||||
register_options_dynamically(conf, group_name)
|
||||
group_names.append(group_name)
|
||||
|
||||
store_conf = collections.namedtuple('store_conf', ['store_plugin',
|
||||
'crypto_plugin',
|
||||
'global_default'])
|
||||
parsed_stores = []
|
||||
global_default_count = 0
|
||||
# Section related to group names based of suffix list are always found
|
||||
# as we are dynamically registering group and its options.
|
||||
for group_name in group_names:
|
||||
conf_section = getattr(conf, group_name)
|
||||
if conf_section.global_default:
|
||||
global_default_count += 1
|
||||
|
||||
store_plugin = conf_section.secret_store_plugin
|
||||
if not store_plugin:
|
||||
raise exception.MultipleStorePluginValueMissing(conf_section)
|
||||
|
||||
parsed_stores.append(store_conf(store_plugin,
|
||||
conf_section.crypto_plugin,
|
||||
conf_section.global_default))
|
||||
|
||||
if global_default_count != 1:
|
||||
raise exception.MultipleStoreIncorrectGlobalDefault(
|
||||
global_default_count)
|
||||
|
||||
return parsed_stores
|
||||
|
||||
|
||||
def sync_secret_stores(secretstore_manager, crypto_manager=None):
|
||||
"""Synchronize secret store plugin names between service conf and database
|
||||
|
||||
This method reads secret and crypto store plugin name from service
|
||||
configuration and then synchronizes corresponding data maintained in
|
||||
database SecretStores table.
|
||||
|
||||
Any new plugin name(s) added in service configuration is added as a new
|
||||
entry in SecretStores table. If global_default value is changed for
|
||||
existing plugins, then global_default flag is updated to reflect that
|
||||
change in database. If plugin name is removed from service configuration,
|
||||
then removal is possible as long as respective plugin names are NOT set as
|
||||
preferred secret store for a project. If it is used and plugin name is
|
||||
removed, then error is raised. This logic is intended to be invoked at
|
||||
server startup so any error raised here will result in critical failure.
|
||||
"""
|
||||
if not utils.is_multiple_backends_enabled():
|
||||
return
|
||||
|
||||
# doing local import to avoid circular dependency between manager and
|
||||
# current utils module
|
||||
from barbican.plugin.crypto import manager as cm
|
||||
|
||||
secret_stores_repo = db_repos.get_secret_stores_repository()
|
||||
proj_store_repo = db_repos.get_project_secret_store_repository()
|
||||
if crypto_manager is None:
|
||||
crypto_manager = cm.get_manager()
|
||||
|
||||
def get_friendly_name_dict(ext_manager):
|
||||
"""Returns dict of plugin internal name and friendly name entries."""
|
||||
names_dict = {}
|
||||
for ext in ext_manager.extensions:
|
||||
if ext.obj and hasattr(ext.obj, 'get_plugin_name'):
|
||||
names_dict[ext.name] = ext.obj.get_plugin_name()
|
||||
return names_dict
|
||||
|
||||
ss_friendly_names = get_friendly_name_dict(secretstore_manager)
|
||||
crypto_friendly_names = get_friendly_name_dict(crypto_manager)
|
||||
# get existing secret stores data from database
|
||||
db_stores = secret_stores_repo.get_all()
|
||||
|
||||
# read secret store data from service configuration
|
||||
conf_stores = []
|
||||
for parsed_store in secretstore_manager.parsed_stores:
|
||||
crypto_plugin = parsed_store.crypto_plugin
|
||||
if not crypto_plugin:
|
||||
crypto_plugin = None
|
||||
|
||||
if crypto_plugin:
|
||||
friendly_name = crypto_friendly_names.get(crypto_plugin)
|
||||
else:
|
||||
friendly_name = ss_friendly_names.get(parsed_store.store_plugin)
|
||||
|
||||
conf_stores.append(db_models.SecretStores(
|
||||
name=friendly_name, store_plugin=parsed_store.store_plugin,
|
||||
crypto_plugin=crypto_plugin,
|
||||
global_default=parsed_store.global_default))
|
||||
|
||||
if db_stores:
|
||||
def fn_match(lh_store, rh_store):
|
||||
return (lh_store.store_plugin == rh_store.store_plugin and
|
||||
lh_store.crypto_plugin == rh_store.crypto_plugin)
|
||||
|
||||
for conf_store in conf_stores:
|
||||
# find existing db entry for plugin using conf based plugin names
|
||||
db_store_match = next((db_store for db_store in db_stores if
|
||||
fn_match(conf_store, db_store)), None)
|
||||
if db_store_match:
|
||||
# update existing db entry if global default is changed now
|
||||
if db_store_match.global_default != conf_store.global_default:
|
||||
db_store_match.global_default = conf_store.global_default
|
||||
# persist flag change.
|
||||
db_store_match.save()
|
||||
# remove matches store from local list after processing
|
||||
db_stores.remove(db_store_match)
|
||||
else: # new conf entry as no match found in existing entries
|
||||
secret_stores_repo.create_from(conf_store)
|
||||
|
||||
# entries still present in db list are no longer configured in service
|
||||
# configuration, so try to remove them provided there is no project
|
||||
# is using it as preferred secret store.
|
||||
for db_store in db_stores:
|
||||
if proj_store_repo.get_count_by_secret_store(db_store.id) == 0:
|
||||
secret_stores_repo.delete_entity_by_id(db_store.id, None)
|
||||
else:
|
||||
raise exception.MultipleStorePluginStillInUse(db_store.name)
|
||||
else: # initial setup case when there is no secret stores data in db
|
||||
for conf_store in conf_stores:
|
||||
secret_stores_repo.create_from(conf_store)
|
||||
|
||||
|
||||
def get_global_default_secret_store():
|
||||
secret_store_repo = db_repos.get_secret_stores_repository()
|
||||
|
||||
default_ss = None
|
||||
for secret_store in secret_store_repo.get_all():
|
||||
if secret_store.global_default:
|
||||
default_ss = secret_store
|
||||
break
|
||||
return default_ss
|
||||
|
||||
|
||||
def get_applicable_crypto_plugins(manager, project_id, existing_plugin_name):
|
||||
"""Get list of crypto plugins available for use.
|
||||
|
||||
:param: manager instance of crypto manager
|
||||
:param: project_id project to identify preferred store if set
|
||||
:param: existing_plugin_name full plugin name. If a secret has an existing
|
||||
plugin defined, then we do not care if any preferred plugins have
|
||||
been defined. We will return all configured plugins as if multiple
|
||||
plugin support was not enabled. Subsequent code in the caller will
|
||||
select the plugin by name.
|
||||
|
||||
When multiple backends support is enabled:
|
||||
It return project preferred plugin as list when it is setup earlier.
|
||||
If project preferred plugin is not set, then it uses plugin from default
|
||||
secret store.
|
||||
Plugin name is 'crypto_plugin' field value on identified secret store data.
|
||||
It returns matched plugin as list to match existing functionality.
|
||||
|
||||
When multiple backends support is NOT enabled:
|
||||
In this case, it just returns list of all active plugins which is
|
||||
existing functionality before support for multiple backends is added.
|
||||
"""
|
||||
return _get_applicable_plugins_for_type(manager, project_id,
|
||||
existing_plugin_name,
|
||||
'crypto_plugin')
|
||||
|
||||
|
||||
def get_applicable_store_plugins(manager, project_id, existing_plugin_name):
|
||||
"""Get list of secret store plugins available for use.
|
||||
|
||||
:param: manager instance of secret store manager
|
||||
:param: project_id project to identify preferred store if set
|
||||
:param: existing_plugin_name full plugin name. If a secret has an existing
|
||||
plugin defined, then we do not care if any preferred plugins have
|
||||
been defined. We will return all configured plugins as if multiple
|
||||
plugin support was not enabled. Subsequent code in the caller will
|
||||
select the plugin by name.
|
||||
|
||||
When multiple backends support is enabled:
|
||||
It return project preferred plugin as list when it is setup earlier.
|
||||
If project preferred plugin is not set, then it uses plugin from default
|
||||
secret store.
|
||||
Plugin name is 'store_plugin' field value on identified secret store data.
|
||||
It returns matched plugin as list to match existing functionality.
|
||||
|
||||
When multiple backends support is NOT enabled:
|
||||
In this case, it just returns list of all active plugins which is
|
||||
existing functionality before support for multiple backends is added.
|
||||
"""
|
||||
return _get_applicable_plugins_for_type(manager, project_id,
|
||||
existing_plugin_name,
|
||||
'store_plugin')
|
||||
|
||||
|
||||
def _get_applicable_plugins_for_type(manager, project_id, existing_plugin_name,
|
||||
plugin_type_field):
|
||||
|
||||
plugins = []
|
||||
plugin_dict = {ext.name: ext.obj for ext in manager.extensions if ext.obj}
|
||||
if utils.is_multiple_backends_enabled() and existing_plugin_name is None:
|
||||
proj_store_repo = db_repos.get_project_secret_store_repository()
|
||||
plugin_store = proj_store_repo.get_secret_store_for_project(
|
||||
project_id, None, suppress_exception=True)
|
||||
|
||||
# If project specific store is not set, then use global default one.
|
||||
if not plugin_store:
|
||||
if manager.global_default_store_dict is None:
|
||||
# Need to cache data as dict instead of db object to be usable
|
||||
# across various request sqlalchemy sessions
|
||||
store_dict = get_global_default_secret_store().to_dict_fields()
|
||||
manager.global_default_store_dict = store_dict
|
||||
secret_store_data = manager.global_default_store_dict
|
||||
else:
|
||||
secret_store_data = plugin_store.secret_store.to_dict_fields()
|
||||
|
||||
applicable_plugin_name = secret_store_data[plugin_type_field]
|
||||
if applicable_plugin_name in plugin_dict:
|
||||
plugins = [plugin_dict.get(applicable_plugin_name)]
|
||||
elif applicable_plugin_name: # applicable_plugin_name has value
|
||||
raise exception.MultipleStorePreferredPluginMissing(
|
||||
applicable_plugin_name)
|
||||
else:
|
||||
plugins = plugin_dict.values()
|
||||
|
||||
return plugins
|
|
@ -0,0 +1,325 @@
|
|||
# (c) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
import uuid
|
||||
|
||||
from barbican.model import models
|
||||
from barbican.model import repositories as repos
|
||||
from barbican.plugin.interface import secret_store
|
||||
from barbican.tests import utils
|
||||
|
||||
|
||||
class SecretStoresMixin(utils.MultipleBackendsTestCase):
|
||||
|
||||
def _create_project(self):
|
||||
session = repos.get_project_repository().get_session()
|
||||
|
||||
project = models.Project()
|
||||
project.external_id = "keystone_project_id" + uuid.uuid4().hex
|
||||
project.save(session=session)
|
||||
return project
|
||||
|
||||
def _create_project_store(self, project_id, secret_store_id):
|
||||
proj_store_repo = repos.get_project_secret_store_repository()
|
||||
session = proj_store_repo.get_session()
|
||||
|
||||
proj_model = models.ProjectSecretStore(project_id, secret_store_id)
|
||||
|
||||
proj_s_store = proj_store_repo.create_from(proj_model, session)
|
||||
proj_s_store.save(session=session)
|
||||
return proj_s_store
|
||||
|
||||
def _init_multiple_backends(self, enabled=True, global_default_index=0):
|
||||
|
||||
store_plugin_names = ['store_crypto', 'kmip_plugin', 'store_crypto']
|
||||
crypto_plugin_names = ['p11_crypto', '', 'simple_crypto']
|
||||
|
||||
self.init_via_conf_file(store_plugin_names,
|
||||
crypto_plugin_names, enabled=enabled,
|
||||
global_default_index=global_default_index)
|
||||
|
||||
with mock.patch('barbican.plugin.crypto.p11_crypto.P11CryptoPlugin.'
|
||||
'_create_pkcs11'), \
|
||||
mock.patch('kmip.pie.client.ProxyKmipClient'):
|
||||
|
||||
secret_store.SecretStorePluginManager()
|
||||
|
||||
|
||||
class WhenTestingSecretStores(utils.BarbicanAPIBaseTestCase,
|
||||
SecretStoresMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingSecretStores, self).setUp()
|
||||
self.secret_store_repo = repos.get_secret_stores_repository()
|
||||
|
||||
def test_should_get_all_secret_stores(self):
|
||||
|
||||
g_index = 2 # global default index in plugins list
|
||||
self._init_multiple_backends(global_default_index=g_index)
|
||||
|
||||
resp = self.app.get('/secret-stores', expect_errors=False)
|
||||
self.assertEqual(200, resp.status_int)
|
||||
secret_stores_data = resp.json.get('secret_stores')
|
||||
|
||||
self.assertEqual(3, len(secret_stores_data))
|
||||
|
||||
for i, secret_data in enumerate(secret_stores_data):
|
||||
self.assertEqual(i == g_index, secret_data['global_default'])
|
||||
self.assertIsNotNone(secret_data['secret_store_ref'])
|
||||
self.assertIsNone(secret_data.get('id'))
|
||||
self.assertIsNone(secret_data.get('secret_store_id'))
|
||||
self.assertIsNotNone(secret_data['name'])
|
||||
self.assertIsNotNone(secret_data['secret_store_plugin'])
|
||||
self.assertIsNotNone(secret_data['created'])
|
||||
self.assertIsNotNone(secret_data['updated'])
|
||||
self.assertEqual(models.States.ACTIVE, secret_data['status'])
|
||||
|
||||
def test_get_all_secret_stores_when_multiple_backends_not_enabled(self):
|
||||
|
||||
self._init_multiple_backends(enabled=False)
|
||||
|
||||
resp = self.app.get('/secret-stores', expect_errors=True)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
resp = self.app.get('/secret-stores/any_valid_id',
|
||||
expect_errors=True)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_get_all_secret_stores_with_unsupported_http_method(self):
|
||||
|
||||
self._init_multiple_backends()
|
||||
|
||||
resp = self.app.put('/secret-stores', expect_errors=True)
|
||||
self.assertEqual(405, resp.status_int)
|
||||
|
||||
resp = self.app.patch('/secret-stores', expect_errors=True)
|
||||
self.assertEqual(405, resp.status_int)
|
||||
|
||||
def test_should_get_global_default(self):
|
||||
|
||||
self._init_multiple_backends(global_default_index=1)
|
||||
|
||||
resp = self.app.get('/secret-stores/global-default',
|
||||
expect_errors=False)
|
||||
self.assertEqual(200, resp.status_int)
|
||||
resp_data = resp.json
|
||||
self.assertEqual(True, resp_data['global_default'])
|
||||
self.assertIn('kmip', resp_data['name'].lower())
|
||||
self.assertIsNotNone(resp_data['secret_store_ref'])
|
||||
self.assertIsNotNone(resp_data['secret_store_plugin'])
|
||||
self.assertIsNone(resp_data['crypto_plugin'])
|
||||
self.assertIsNotNone(resp_data['created'])
|
||||
self.assertIsNotNone(resp_data['updated'])
|
||||
self.assertEqual(models.States.ACTIVE, resp_data['status'])
|
||||
|
||||
def test_get_global_default_when_multiple_backends_not_enabled(self):
|
||||
|
||||
self._init_multiple_backends(enabled=False)
|
||||
|
||||
with mock.patch('barbican.common.resources.'
|
||||
'get_or_create_project') as m1:
|
||||
|
||||
resp = self.app.get('/secret-stores/global-default',
|
||||
expect_errors=True)
|
||||
|
||||
self.assertFalse(m1.called)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_get_preferred_when_preferred_is_set(self):
|
||||
self._init_multiple_backends(global_default_index=1)
|
||||
|
||||
secret_stores = self.secret_store_repo.get_all()
|
||||
project1 = self._create_project()
|
||||
|
||||
self._create_project_store(project1.id, secret_stores[0].id)
|
||||
|
||||
self.app.extra_environ = {
|
||||
'barbican.context': self._build_context(project1.external_id)
|
||||
}
|
||||
resp = self.app.get('/secret-stores/preferred',
|
||||
expect_errors=False)
|
||||
self.assertEqual(200, resp.status_int)
|
||||
resp_data = resp.json
|
||||
self.assertEqual(secret_stores[0].name, resp_data['name'])
|
||||
self.assertEqual(secret_stores[0].global_default,
|
||||
resp_data['global_default'])
|
||||
self.assertIn('/secret-stores/{0}'.format(secret_stores[0].id),
|
||||
resp_data['secret_store_ref'])
|
||||
self.assertIsNotNone(resp_data['created'])
|
||||
self.assertIsNotNone(resp_data['updated'])
|
||||
self.assertEqual(models.States.ACTIVE, resp_data['status'])
|
||||
|
||||
def test_get_preferred_when_preferred_is_not_set(self):
|
||||
self._init_multiple_backends(global_default_index=1)
|
||||
project1 = self._create_project()
|
||||
|
||||
self.app.extra_environ = {
|
||||
'barbican.context': self._build_context(project1.external_id)
|
||||
}
|
||||
resp = self.app.get('/secret-stores/preferred',
|
||||
expect_errors=True)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_get_preferred_when_multiple_backends_not_enabled(self):
|
||||
|
||||
self._init_multiple_backends(enabled=False)
|
||||
|
||||
with mock.patch('barbican.common.resources.'
|
||||
'get_or_create_project') as m1:
|
||||
|
||||
resp = self.app.get('/secret-stores/preferred',
|
||||
expect_errors=True)
|
||||
|
||||
self.assertFalse(m1.called)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
|
||||
class WhenTestingSecretStore(utils.BarbicanAPIBaseTestCase,
|
||||
SecretStoresMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingSecretStore, self).setUp()
|
||||
self.secret_store_repo = repos.get_secret_stores_repository()
|
||||
|
||||
def test_get_a_secret_store_when_no_error(self):
|
||||
|
||||
self._init_multiple_backends()
|
||||
|
||||
secret_stores = self.secret_store_repo.get_all()
|
||||
|
||||
store = secret_stores[0]
|
||||
|
||||
resp = self.app.get('/secret-stores/{0}'.format(store.id),
|
||||
expect_errors=False)
|
||||
self.assertEqual(200, resp.status_int)
|
||||
data = resp.json
|
||||
self.assertEqual(store.global_default, data['global_default'])
|
||||
self.assertEqual(store.name, data['name'])
|
||||
self.assertIn('/secret-stores/{0}'.format(store.id),
|
||||
data['secret_store_ref'])
|
||||
self.assertIsNotNone(data['secret_store_plugin'])
|
||||
self.assertIsNotNone(data['created'])
|
||||
self.assertIsNotNone(data['updated'])
|
||||
self.assertEqual(models.States.ACTIVE, data['status'])
|
||||
|
||||
def test_invalid_uri_for_secret_stores_subresource(self):
|
||||
self._init_multiple_backends()
|
||||
|
||||
resp = self.app.get('/secret-stores/invalid_uri',
|
||||
expect_errors=True)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_get_a_secret_store_with_unsupported_http_method(self):
|
||||
self._init_multiple_backends()
|
||||
|
||||
secret_stores = self.secret_store_repo.get_all()
|
||||
store_id = secret_stores[0].id
|
||||
|
||||
resp = self.app.put('/secret-stores/{0}'.format(store_id),
|
||||
expect_errors=True)
|
||||
self.assertEqual(405, resp.status_int)
|
||||
|
||||
def test_invalid_uri_for_a_secret_store_subresource(self):
|
||||
self._init_multiple_backends()
|
||||
|
||||
secret_stores = self.secret_store_repo.get_all()
|
||||
resp = self.app.get('/secret-stores/{0}/invalid_uri'.
|
||||
format(secret_stores[0].id), expect_errors=True)
|
||||
self.assertEqual(405, resp.status_int)
|
||||
|
||||
|
||||
class WhenTestingProjectSecretStore(utils.BarbicanAPIBaseTestCase,
|
||||
SecretStoresMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingProjectSecretStore, self).setUp()
|
||||
self.secret_store_repo = repos.get_secret_stores_repository()
|
||||
self.proj_store_repo = repos.get_project_secret_store_repository()
|
||||
|
||||
def test_set_a_preferred_secret_store_when_no_error(self):
|
||||
|
||||
self._init_multiple_backends()
|
||||
|
||||
stores = self.secret_store_repo.get_all()
|
||||
|
||||
proj_external_id = uuid.uuid4().hex
|
||||
# get ids as secret store are not bound to session after a rest call.
|
||||
store_ids = [store.id for store in stores]
|
||||
for store_id in store_ids:
|
||||
self.app.extra_environ = {
|
||||
'barbican.context': self._build_context(proj_external_id)
|
||||
}
|
||||
resp = self.app.post('/secret-stores/{0}/preferred'.
|
||||
format(store_id), expect_errors=False)
|
||||
self.assertEqual(204, resp.status_int)
|
||||
|
||||
# Now make sure preferred store is set to store id via get call
|
||||
resp = self.app.get('/secret-stores/preferred')
|
||||
self.assertIn(store_id, resp.json['secret_store_ref'])
|
||||
|
||||
def test_unset_a_preferred_secret_store_when_no_error(self):
|
||||
|
||||
self._init_multiple_backends()
|
||||
|
||||
stores = self.secret_store_repo.get_all()
|
||||
|
||||
proj_external_id = uuid.uuid4().hex
|
||||
# get ids as secret store are not bound to session after a rest call.
|
||||
store_ids = [store.id for store in stores]
|
||||
for store_id in store_ids:
|
||||
self.app.extra_environ = {
|
||||
'barbican.context': self._build_context(proj_external_id)
|
||||
}
|
||||
resp = self.app.post('/secret-stores/{0}/preferred'.
|
||||
format(store_id), expect_errors=False)
|
||||
self.assertEqual(204, resp.status_int)
|
||||
|
||||
# unset preferred store here
|
||||
resp = self.app.delete('/secret-stores/{0}/preferred'.
|
||||
format(store_id), expect_errors=False)
|
||||
self.assertEqual(204, resp.status_int)
|
||||
|
||||
# Now make sure that there is no longer a preferred store set
|
||||
resp = self.app.get('/secret-stores/preferred',
|
||||
expect_errors=True)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_unset_a_preferred_store_when_not_found_error(self):
|
||||
self._init_multiple_backends()
|
||||
|
||||
stores = self.secret_store_repo.get_all()
|
||||
proj_external_id = uuid.uuid4().hex
|
||||
self.app.extra_environ = {
|
||||
'barbican.context': self._build_context(proj_external_id)
|
||||
}
|
||||
resp = self.app.delete('/secret-stores/{0}/preferred'.
|
||||
format(stores[0].id), expect_errors=True)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_preferred_secret_store_call_with_unsupported_http_method(self):
|
||||
self._init_multiple_backends()
|
||||
|
||||
secret_stores = self.secret_store_repo.get_all()
|
||||
store_id = secret_stores[0].id
|
||||
|
||||
proj_external_id = uuid.uuid4().hex
|
||||
|
||||
self.app.extra_environ = {
|
||||
'barbican.context': self._build_context(proj_external_id)
|
||||
}
|
||||
resp = self.app.put('/secret-stores/{0}/preferred'.
|
||||
format(store_id), expect_errors=True)
|
||||
|
||||
self.assertEqual(405, resp.status_int)
|
|
@ -786,7 +786,6 @@ class WhenCreatingConsumersUsingConsumersResource(FunctionalTest):
|
|||
|
||||
# Set up mocked container repo
|
||||
self.container_repo = mock.MagicMock()
|
||||
self.container_repo.get.return_value = self.container
|
||||
self.container_repo.get_container_by_id.return_value = self.container
|
||||
self.setup_container_repository_mock(self.container_repo)
|
||||
|
||||
|
@ -824,22 +823,12 @@ class WhenCreatingConsumersUsingConsumersResource(FunctionalTest):
|
|||
)
|
||||
self.assertEqual(415, resp.status_int)
|
||||
|
||||
def test_should_404_consumer_bad_container_id(self):
|
||||
self.container_repo.get.side_effect = excep.NotFound()
|
||||
def test_should_404_when_container_ref_doesnt_exist(self):
|
||||
self.container_repo.get_container_by_id.return_value = None
|
||||
resp = self.app.post_json(
|
||||
'/containers/{0}/consumers/'.format('bad_id'),
|
||||
self.consumer_ref, expect_errors=True
|
||||
)
|
||||
self.container_repo.get.side_effect = None
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_should_raise_exception_when_container_ref_doesnt_exist(self):
|
||||
self.container_repo.get.return_value = None
|
||||
resp = self.app.post_json(
|
||||
'/containers/{0}/consumers/'.format(self.container.id),
|
||||
self.consumer_ref,
|
||||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
|
||||
|
@ -896,7 +885,6 @@ class WhenGettingOrDeletingConsumersUsingConsumerResource(FunctionalTest):
|
|||
|
||||
# Set up mocked container repo
|
||||
self.container_repo = mock.MagicMock()
|
||||
self.container_repo.get.return_value = self.container
|
||||
self.container_repo.get_container_by_id.return_value = self.container
|
||||
self.setup_container_repository_mock(self.container_repo)
|
||||
|
||||
|
@ -928,12 +916,11 @@ class WhenGettingOrDeletingConsumersUsingConsumerResource(FunctionalTest):
|
|||
self.assertEqual(self.consumer.name, resp.json['consumers'][0]['name'])
|
||||
self.assertEqual(self.consumer.URL, resp.json['consumers'][0]['URL'])
|
||||
|
||||
def test_should_404_with_bad_container_id(self):
|
||||
self.container_repo.get.side_effect = excep.NotFound()
|
||||
def test_should_404_when_container_ref_doesnt_exist(self):
|
||||
self.container_repo.get_container_by_id.return_value = None
|
||||
resp = self.app.get('/containers/{0}/consumers/'.format(
|
||||
'bad_id'
|
||||
), expect_errors=True)
|
||||
self.container_repo.get.side_effect = None
|
||||
self.assertEqual(404, resp.status_int)
|
||||
|
||||
def test_should_get_consumer_by_id(self):
|
||||
|
@ -1105,7 +1092,7 @@ class WhenPerformingUnallowedOperationsOnConsumers(FunctionalTest):
|
|||
|
||||
# Set up container repo
|
||||
self.container_repo = mock.MagicMock()
|
||||
self.container_repo.get.return_value = self.container
|
||||
self.container_repo.get_container_by_id.return_value = self.container
|
||||
self.setup_container_repository_mock(self.container_repo)
|
||||
|
||||
# Set up container consumer repo
|
||||
|
@ -1156,3 +1143,75 @@ class WhenPerformingUnallowedOperationsOnConsumers(FunctionalTest):
|
|||
expect_errors=True
|
||||
)
|
||||
self.assertEqual(405, resp.status_int)
|
||||
|
||||
|
||||
class WhenOwnershipMismatch(FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(
|
||||
WhenOwnershipMismatch, self
|
||||
).setUp()
|
||||
self.app = webtest.TestApp(app.build_wsgi_app(self.root))
|
||||
self.app.extra_environ = get_barbican_env(self.external_project_id)
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
self._init()
|
||||
|
||||
class RootController(object):
|
||||
containers = controllers.containers.ContainersController()
|
||||
return RootController()
|
||||
|
||||
def _init(self):
|
||||
self.external_project_id = 'keystoneid1234'
|
||||
self.project_internal_id = 'projectid1234'
|
||||
|
||||
# Set up mocked project
|
||||
self.project = models.Project()
|
||||
self.project.id = self.project_internal_id
|
||||
self.project.external_id = self.external_project_id
|
||||
|
||||
# Set up mocked project repo
|
||||
self.project_repo = mock.MagicMock()
|
||||
self.project_repo.get.return_value = self.project
|
||||
self.setup_project_repository_mock(self.project_repo)
|
||||
|
||||
# Set up mocked container
|
||||
self.container = create_container(
|
||||
id_ref='id1',
|
||||
project_id=self.project_internal_id,
|
||||
external_project_id='differentProjectId')
|
||||
|
||||
# Set up mocked consumers
|
||||
self.consumer = create_consumer(self.container.id,
|
||||
self.project_internal_id,
|
||||
id_ref='id2')
|
||||
self.consumer2 = create_consumer(self.container.id,
|
||||
self.project_internal_id,
|
||||
id_ref='id3')
|
||||
|
||||
self.consumer_ref = {
|
||||
'name': self.consumer.name,
|
||||
'URL': self.consumer.URL
|
||||
}
|
||||
|
||||
# Set up mocked container repo
|
||||
self.container_repo = mock.MagicMock()
|
||||
self.container_repo.get.return_value = self.container
|
||||
self.container_repo.get_container_by_id.return_value = self.container
|
||||
self.setup_container_repository_mock(self.container_repo)
|
||||
|
||||
# Set up mocked container consumer repo
|
||||
self.consumer_repo = mock.MagicMock()
|
||||
self.consumer_repo.get_by_values.return_value = self.consumer
|
||||
self.consumer_repo.delete_entity_by_id.return_value = None
|
||||
self.setup_container_consumer_repository_mock(self.consumer_repo)
|
||||
|
||||
# Set up mocked secret repo
|
||||
self.setup_secret_repository_mock()
|
||||
|
||||
def test_consumer_check_ownership_mismatch(self):
|
||||
resp = self.app.delete_json(
|
||||
'/containers/{0}/consumers/'.format(self.container.id),
|
||||
self.consumer_ref, expect_errors=True)
|
||||
self.assertEqual(403, resp.status_int)
|
||||
|
|
|
@ -27,6 +27,7 @@ from barbican.api.controllers import consumers
|
|||
from barbican.api.controllers import containers
|
||||
from barbican.api.controllers import orders
|
||||
from barbican.api.controllers import secrets
|
||||
from barbican.api.controllers import secretstores
|
||||
from barbican.api.controllers import versions
|
||||
from barbican.common import config
|
||||
from barbican import context
|
||||
|
@ -101,6 +102,18 @@ class ConsumerResource(TestableResource):
|
|||
controller_cls = consumers.ContainerConsumerController
|
||||
|
||||
|
||||
class SecretStoresResource(TestableResource):
|
||||
controller_cls = secretstores.SecretStoresController
|
||||
|
||||
|
||||
class SecretStoreResource(TestableResource):
|
||||
controller_cls = secretstores.SecretStoreController
|
||||
|
||||
|
||||
class PreferredSecretStoreResource(TestableResource):
|
||||
controller_cls = secretstores.PreferredSecretStoreController
|
||||
|
||||
|
||||
class BaseTestCase(utils.BaseTestCase, utils.MockModelRepositoryMixin):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -1115,3 +1128,151 @@ class WhenTestingConsumerResource(BaseTestCase):
|
|||
|
||||
def _invoke_on_get(self):
|
||||
self.resource.on_get(self.req, self.resp)
|
||||
|
||||
|
||||
class WhenTestingSecretStoresResource(BaseTestCase):
|
||||
"""RBAC tests for the barbican.api.resources.SecretStoresResource class."""
|
||||
def setUp(self):
|
||||
super(WhenTestingSecretStoresResource, self).setUp()
|
||||
|
||||
self.external_project_id = '12345project'
|
||||
|
||||
self.moc_enable_patcher = mock.patch(
|
||||
'barbican.common.utils.is_multiple_backends_enabled')
|
||||
enable_check_method = self.moc_enable_patcher.start()
|
||||
enable_check_method.return_value = True
|
||||
self.addCleanup(self.moc_enable_patcher.stop)
|
||||
|
||||
# Force an error on GET calls that pass RBAC, as we are not testing
|
||||
# such flows in this test module.
|
||||
self.project_repo = mock.MagicMock()
|
||||
fail_method = mock.MagicMock(return_value=None,
|
||||
side_effect=self._generate_get_error())
|
||||
self.project_repo.find_by_external_project_id = fail_method
|
||||
self.setup_project_repository_mock(self.project_repo)
|
||||
|
||||
self.resource = SecretStoresResource()
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
|
||||
def test_should_pass_get_all_secret_stores(self):
|
||||
self._assert_pass_rbac(['admin'],
|
||||
self._invoke_on_get)
|
||||
|
||||
def test_should_raise_get_all_secret_stores(self):
|
||||
self._assert_fail_rbac([None, 'creator', 'observer', 'audit'],
|
||||
self._invoke_on_get)
|
||||
|
||||
def test_should_pass_get_global_default(self):
|
||||
self._assert_pass_rbac(['admin'],
|
||||
self._invoke_get_global_default)
|
||||
|
||||
def test_should_raise_get_global_default(self):
|
||||
self._assert_fail_rbac([None, 'creator', 'observer', 'audit'],
|
||||
self._invoke_get_global_default)
|
||||
|
||||
def test_should_pass_get_preferred(self):
|
||||
self._assert_pass_rbac(['admin'],
|
||||
self._invoke_get_preferred)
|
||||
|
||||
def test_should_raise_get_preferred(self):
|
||||
self._assert_fail_rbac([None, 'creator', 'observer', 'audit'],
|
||||
self._invoke_get_preferred)
|
||||
|
||||
def _invoke_on_get(self):
|
||||
self.resource.on_get(self.req, self.resp)
|
||||
|
||||
def _invoke_get_global_default(self):
|
||||
with mock.patch('pecan.request', self.req):
|
||||
with mock.patch('pecan.response', self.resp):
|
||||
return self.resource.controller.get_global_default()
|
||||
|
||||
def _invoke_get_preferred(self):
|
||||
with mock.patch('pecan.request', self.req):
|
||||
with mock.patch('pecan.response', self.resp):
|
||||
return self.resource.controller.get_preferred()
|
||||
|
||||
|
||||
class WhenTestingSecretStoreResource(BaseTestCase):
|
||||
"""RBAC tests for the barbican.api.resources.SecretStoreResource class."""
|
||||
def setUp(self):
|
||||
super(WhenTestingSecretStoreResource, self).setUp()
|
||||
|
||||
self.external_project_id = '12345project'
|
||||
self.store_id = '123456SecretStoreId'
|
||||
|
||||
self.moc_enable_patcher = mock.patch(
|
||||
'barbican.common.utils.is_multiple_backends_enabled')
|
||||
enable_check_method = self.moc_enable_patcher.start()
|
||||
enable_check_method.return_value = True
|
||||
self.addCleanup(self.moc_enable_patcher.stop)
|
||||
|
||||
# Force an error on GET calls that pass RBAC, as we are not testing
|
||||
# such flows in this test module.
|
||||
|
||||
self.project_repo = mock.MagicMock()
|
||||
fail_method = mock.MagicMock(return_value=None,
|
||||
side_effect=self._generate_get_error())
|
||||
|
||||
self.project_repo.find_by_external_project_id = fail_method
|
||||
self.setup_project_repository_mock(self.project_repo)
|
||||
|
||||
secret_store_res = mock.MagicMock()
|
||||
secret_store_res.to_dict_fields = mock.MagicMock(side_effect=IOError)
|
||||
secret_store_res.id = self.store_id
|
||||
|
||||
self.resource = SecretStoreResource(secret_store_res)
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
|
||||
def test_should_pass_get_a_secret_store(self):
|
||||
self._assert_pass_rbac(['admin'],
|
||||
self._invoke_on_get)
|
||||
|
||||
def test_should_raise_get_a_secret_store(self):
|
||||
self._assert_fail_rbac([None, 'creator', 'observer', 'audit'],
|
||||
self._invoke_on_get)
|
||||
|
||||
def _invoke_on_get(self):
|
||||
self.resource.on_get(self.req, self.resp)
|
||||
|
||||
|
||||
class WhenTestingPreferredSecretStoreResource(BaseTestCase):
|
||||
"""RBAC tests for barbican.api.resources.PreferredSecretStoreResource"""
|
||||
def setUp(self):
|
||||
super(WhenTestingPreferredSecretStoreResource, self).setUp()
|
||||
|
||||
self.external_project_id = '12345project'
|
||||
self.store_id = '123456SecretStoreId'
|
||||
|
||||
self.moc_enable_patcher = mock.patch(
|
||||
'barbican.common.utils.is_multiple_backends_enabled')
|
||||
enable_check_method = self.moc_enable_patcher.start()
|
||||
enable_check_method.return_value = True
|
||||
self.addCleanup(self.moc_enable_patcher.stop)
|
||||
|
||||
# Force an error on POST/DELETE calls that pass RBAC, as we are not
|
||||
# testing such flows in this test module.
|
||||
self.project_repo = mock.MagicMock()
|
||||
fail_method = mock.MagicMock(return_value=None,
|
||||
side_effect=self._generate_get_error())
|
||||
self.project_repo.find_by_external_project_id = fail_method
|
||||
self.setup_project_repository_mock(self.project_repo)
|
||||
|
||||
self.resource = PreferredSecretStoreResource(mock.MagicMock())
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
|
||||
def test_should_pass_set_preferred_secret_store(self):
|
||||
self._assert_pass_rbac(['admin'],
|
||||
self._invoke_on_post)
|
||||
|
||||
def test_should_raise_set_preferred_secret_store(self):
|
||||
self._assert_fail_rbac([None, 'creator', 'observer', 'audit'],
|
||||
self._invoke_on_post)
|
||||
|
||||
def _invoke_on_post(self):
|
||||
self.resource.on_post(self.req, self.resp)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
|
||||
def get_private_key_pem():
|
||||
"""Returns a private key in PCKS#8 format
|
||||
"""Returns a private key in PKCS#8 format
|
||||
|
||||
This key was created by issuing the following openssl commands:
|
||||
|
||||
|
|
|
@ -0,0 +1,426 @@
|
|||
# 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.
|
||||
import uuid
|
||||
|
||||
from barbican.common import exception
|
||||
from barbican.model import models
|
||||
from barbican.model import repositories
|
||||
from barbican.tests import database_utils
|
||||
|
||||
|
||||
class WhenTestingSecretStoresRepo(database_utils.RepositoryTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingSecretStoresRepo, self).setUp()
|
||||
self.s_stores_repo = repositories.get_secret_stores_repository()
|
||||
self.def_name = "PKCS11 HSM"
|
||||
self.def_store_plugin = "store_crypto"
|
||||
self.def_crypto_plugin = "p11_crypto"
|
||||
self.default_secret_store = self._create_secret_store(
|
||||
self.def_name, self.def_store_plugin, self.def_crypto_plugin, True)
|
||||
|
||||
def _create_secret_store(self, name, store_plugin, crypto_plugin=None,
|
||||
global_default=None):
|
||||
session = self.s_stores_repo.get_session()
|
||||
|
||||
s_stores_model = models.SecretStores(name=name,
|
||||
store_plugin=store_plugin,
|
||||
crypto_plugin=crypto_plugin,
|
||||
global_default=global_default)
|
||||
s_stores = self.s_stores_repo.create_from(s_stores_model,
|
||||
session=session)
|
||||
|
||||
s_stores.save(session=session)
|
||||
|
||||
session.commit()
|
||||
return s_stores
|
||||
|
||||
def test_get_by_entity_id(self):
|
||||
|
||||
session = self.s_stores_repo.get_session()
|
||||
s_stores = self.s_stores_repo.get(self.default_secret_store.id,
|
||||
session=session)
|
||||
|
||||
self.assertIsNotNone(s_stores)
|
||||
self.assertEqual(self.def_store_plugin, s_stores.store_plugin)
|
||||
self.assertEqual(self.def_crypto_plugin, s_stores.crypto_plugin)
|
||||
self.assertEqual(True, s_stores.global_default)
|
||||
self.assertEqual(models.States.ACTIVE, s_stores.status)
|
||||
|
||||
def test_should_raise_notfound_exception_get_by_entity_id(self):
|
||||
self.assertRaises(exception.NotFound, self.s_stores_repo.get,
|
||||
"invalid_id", suppress_exception=False)
|
||||
|
||||
def test_delete_entity_by_id(self):
|
||||
|
||||
session = self.s_stores_repo.get_session()
|
||||
s_stores = self.s_stores_repo.get(self.default_secret_store.id,
|
||||
session=session)
|
||||
self.assertIsNotNone(s_stores)
|
||||
|
||||
self.s_stores_repo.delete_entity_by_id(self.default_secret_store.id,
|
||||
None, session=session)
|
||||
s_stores = self.s_stores_repo.get(self.default_secret_store.id,
|
||||
suppress_exception=True,
|
||||
session=session)
|
||||
|
||||
self.assertIsNone(s_stores)
|
||||
|
||||
def test_get_all(self):
|
||||
|
||||
session = self.s_stores_repo.get_session()
|
||||
all_stores = self.s_stores_repo.get_all(session=session)
|
||||
|
||||
self.assertIsNotNone(all_stores)
|
||||
self.assertEqual(1, len(all_stores))
|
||||
|
||||
self._create_secret_store("db backend", "store_crypto",
|
||||
"simple_crypto", False)
|
||||
all_stores = self.s_stores_repo.get_all(session=session)
|
||||
self.assertEqual(2, len(all_stores))
|
||||
|
||||
self.assertEqual("simple_crypto", all_stores[1].crypto_plugin)
|
||||
self.assertEqual("store_crypto", all_stores[1].store_plugin)
|
||||
self.assertEqual("db backend", all_stores[1].name)
|
||||
self.assertEqual(False, all_stores[1].global_default)
|
||||
|
||||
def test_no_data_case_for_get_all(self):
|
||||
|
||||
self.s_stores_repo.delete_entity_by_id(self.default_secret_store.id,
|
||||
None)
|
||||
session = self.s_stores_repo.get_session()
|
||||
all_stores = self.s_stores_repo.get_all(session=session)
|
||||
self.assertEqual([], all_stores)
|
||||
|
||||
def test_get_all_check_sorting_order(self):
|
||||
"""Check that all stores are sorted in ascending creation time
|
||||
|
||||
"""
|
||||
session = self.s_stores_repo.get_session()
|
||||
|
||||
self._create_secret_store("second_name", "second_store",
|
||||
"second_crypto", False)
|
||||
m_stores = self._create_secret_store("middle_name", "middle_store",
|
||||
"middle_crypto", False)
|
||||
self._create_secret_store("last_name", "last_store", "last_crypto",
|
||||
False)
|
||||
|
||||
all_stores = self.s_stores_repo.get_all(session=session)
|
||||
self.assertIsNotNone(all_stores)
|
||||
self.assertEqual(4, len(all_stores))
|
||||
# returned list is sorted by created_at field so check for last entry
|
||||
self.assertEqual("last_crypto", all_stores[3].crypto_plugin)
|
||||
self.assertEqual("last_store", all_stores[3].store_plugin)
|
||||
self.assertEqual("last_name", all_stores[3].name)
|
||||
self.assertEqual(False, all_stores[3].global_default)
|
||||
|
||||
# Now delete in between entry and create as new entry
|
||||
self.s_stores_repo.delete_entity_by_id(m_stores.id, None,
|
||||
session=session)
|
||||
all_stores = self.s_stores_repo.get_all(session=session)
|
||||
|
||||
self._create_secret_store("middle_name", "middle_store",
|
||||
"middle_crypto", False)
|
||||
all_stores = self.s_stores_repo.get_all(session=session)
|
||||
# now newly created entry should be last one.
|
||||
self.assertEqual("middle_crypto", all_stores[3].crypto_plugin)
|
||||
self.assertEqual("middle_store", all_stores[3].store_plugin)
|
||||
self.assertEqual("middle_name", all_stores[3].name)
|
||||
self.assertEqual(False, all_stores[3].global_default)
|
||||
|
||||
def test_should_raise_duplicate_for_same_plugin_names(self):
|
||||
"""Check for store and crypto plugin name combination uniqueness"""
|
||||
|
||||
name = 'second_name'
|
||||
store_plugin = 'second_store'
|
||||
crypto_plugin = 'second_crypto'
|
||||
self._create_secret_store(name, store_plugin, crypto_plugin, False)
|
||||
self.assertRaises(exception.Duplicate, self._create_secret_store,
|
||||
"thrid_name", store_plugin, crypto_plugin, False)
|
||||
|
||||
def test_should_raise_duplicate_for_same_names(self):
|
||||
"""Check for secret store 'name' uniqueness"""
|
||||
|
||||
name = 'Db backend'
|
||||
store_plugin = 'second_store'
|
||||
crypto_plugin = 'second_crypto'
|
||||
self._create_secret_store(name, store_plugin, crypto_plugin, False)
|
||||
self.assertRaises(exception.Duplicate, self._create_secret_store,
|
||||
name, "another_store", "another_crypto", False)
|
||||
|
||||
def test_do_entity_name(self):
|
||||
"""Code coverage for entity_name which is used in case of exception.
|
||||
|
||||
Raising duplicate error for store and crypto plugin combination
|
||||
"""
|
||||
name = "DB backend"
|
||||
store_plugin = 'second_store'
|
||||
crypto_plugin = 'second_crypto'
|
||||
self._create_secret_store(name, store_plugin, crypto_plugin, False)
|
||||
try:
|
||||
self._create_secret_store(name, store_plugin, crypto_plugin, False)
|
||||
self.assertFail()
|
||||
except exception.Duplicate as ex:
|
||||
self.assertIn("SecretStores", ex.message)
|
||||
|
||||
|
||||
class WhenTestingProjectSecretStoreRepo(database_utils.RepositoryTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenTestingProjectSecretStoreRepo, self).setUp()
|
||||
self.proj_store_repo = repositories.\
|
||||
get_project_secret_store_repository()
|
||||
self.def_name = "PKCS11 HSM"
|
||||
self.def_store_plugin = "store_crypto"
|
||||
self.def_crypto_plugin = "p11_crypto"
|
||||
self.default_secret_store = self._create_secret_store(
|
||||
self.def_name, self.def_store_plugin, self.def_crypto_plugin, True)
|
||||
|
||||
def _create_secret_store(self, name, store_plugin, crypto_plugin=None,
|
||||
global_default=None):
|
||||
s_stores_repo = repositories.get_secret_stores_repository()
|
||||
session = s_stores_repo.get_session()
|
||||
|
||||
s_stores_model = models.SecretStores(name=name,
|
||||
store_plugin=store_plugin,
|
||||
crypto_plugin=crypto_plugin,
|
||||
global_default=global_default)
|
||||
s_stores = s_stores_repo.create_from(s_stores_model,
|
||||
session=session)
|
||||
s_stores.save(session=session)
|
||||
|
||||
session.commit()
|
||||
return s_stores
|
||||
|
||||
def _create_project(self):
|
||||
session = self.proj_store_repo.get_session()
|
||||
|
||||
project = models.Project()
|
||||
project.external_id = "keystone_project_id" + uuid.uuid4().hex
|
||||
project.save(session=session)
|
||||
return project
|
||||
|
||||
def _create_project_store(self, project_id, secret_store_id):
|
||||
session = self.proj_store_repo.get_session()
|
||||
|
||||
proj_model = models.ProjectSecretStore(project_id, secret_store_id)
|
||||
|
||||
proj_s_store = self.proj_store_repo.create_from(proj_model, session)
|
||||
proj_s_store.save(session=session)
|
||||
return proj_s_store
|
||||
|
||||
def test_get_by_entity_id(self):
|
||||
"""Tests for 'get' call by project secret store id"""
|
||||
|
||||
project = self._create_project()
|
||||
|
||||
proj_s_store = self._create_project_store(project.id,
|
||||
self.default_secret_store.id)
|
||||
|
||||
session = self.proj_store_repo.get_session()
|
||||
s_stores = self.proj_store_repo.get(proj_s_store.id, session=session)
|
||||
|
||||
self.assertIsNotNone(proj_s_store)
|
||||
self.assertEqual(project.id, proj_s_store.project_id)
|
||||
self.assertEqual(self.default_secret_store.id,
|
||||
proj_s_store.secret_store_id)
|
||||
self.assertEqual(models.States.ACTIVE, s_stores.status)
|
||||
# assert values via relationship
|
||||
self.assertEqual(self.default_secret_store.store_plugin,
|
||||
proj_s_store.secret_store.store_plugin)
|
||||
self.assertEqual(project.external_id, proj_s_store.project.external_id)
|
||||
|
||||
def test_should_raise_notfound_exception_get_by_entity_id(self):
|
||||
self.assertRaises(exception.NotFound, self.proj_store_repo.get,
|
||||
"invalid_id", suppress_exception=False)
|
||||
|
||||
def test_delete_entity_by_id(self):
|
||||
|
||||
project = self._create_project()
|
||||
|
||||
proj_s_store = self._create_project_store(project.id,
|
||||
self.default_secret_store.id)
|
||||
|
||||
session = self.proj_store_repo.get_session()
|
||||
proj_s_store = self.proj_store_repo.get(proj_s_store.id,
|
||||
session=session)
|
||||
|
||||
self.assertIsNotNone(proj_s_store)
|
||||
|
||||
self.proj_store_repo.delete_entity_by_id(proj_s_store.id, None,
|
||||
session=session)
|
||||
proj_s_store = self.proj_store_repo.get(proj_s_store.id,
|
||||
suppress_exception=True,
|
||||
session=session)
|
||||
|
||||
self.assertIsNone(proj_s_store)
|
||||
|
||||
def test_should_raise_duplicate_for_same_project_id(self):
|
||||
"""Check preferred secret store is set only once for project"""
|
||||
|
||||
project1 = self._create_project()
|
||||
name = "first_name"
|
||||
store_plugin = 'first_store'
|
||||
crypto_plugin = 'first_crypto'
|
||||
s_store1 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
|
||||
# set preferred secret store for project1
|
||||
self._create_project_store(project1.id,
|
||||
s_store1.id)
|
||||
|
||||
name = "second_name"
|
||||
store_plugin = 'second_store'
|
||||
crypto_plugin = 'second_crypto'
|
||||
s_store2 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
|
||||
self.assertRaises(exception.Duplicate, self._create_project_store,
|
||||
project1.id, s_store2.id)
|
||||
|
||||
def test_do_entity_name(self):
|
||||
"""Code coverage for entity_name which is used in case of exception.
|
||||
|
||||
Raising duplicate error when try to set another entry for existing
|
||||
project
|
||||
"""
|
||||
project1 = self._create_project()
|
||||
name = "first name"
|
||||
store_plugin = 'first_store'
|
||||
crypto_plugin = 'first_crypto'
|
||||
s_store1 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
|
||||
# set preferred secret store for project1
|
||||
self._create_project_store(project1.id,
|
||||
s_store1.id)
|
||||
try:
|
||||
name = "second_name"
|
||||
store_plugin = 'second_store'
|
||||
crypto_plugin = 'second_crypto'
|
||||
s_store2 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
self._create_project_store(project1.id, s_store2.id)
|
||||
self.assertFail()
|
||||
except exception.Duplicate as ex:
|
||||
self.assertIn("ProjectSecretStore", ex.message)
|
||||
|
||||
def test_get_secret_store_for_project(self):
|
||||
project1 = self._create_project()
|
||||
name = "first_name"
|
||||
store_plugin = 'first_store'
|
||||
crypto_plugin = 'first_crypto'
|
||||
s_store1 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
|
||||
# set preferred secret store for project1
|
||||
proj_s_store = self._create_project_store(project1.id, s_store1.id)
|
||||
|
||||
# get preferred secret store by barbican project id
|
||||
read_project_s_store = self.proj_store_repo.\
|
||||
get_secret_store_for_project(project1.id, None)
|
||||
|
||||
self.assertEqual(proj_s_store.project_id,
|
||||
read_project_s_store.project_id)
|
||||
self.assertEqual(proj_s_store.secret_store_id,
|
||||
read_project_s_store.secret_store_id)
|
||||
|
||||
# get preferred secret store by keystone project id
|
||||
read_project_s_store = self.proj_store_repo.\
|
||||
get_secret_store_for_project(None, project1.external_id)
|
||||
|
||||
self.assertEqual(proj_s_store.project_id,
|
||||
read_project_s_store.project_id)
|
||||
self.assertEqual(project1.external_id,
|
||||
read_project_s_store.project.external_id)
|
||||
|
||||
self.assertEqual(proj_s_store.secret_store_id,
|
||||
read_project_s_store.secret_store_id)
|
||||
|
||||
def test_raise_notfound_exception_get_secret_store_for_project(self):
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.proj_store_repo.get_secret_store_for_project,
|
||||
"invalid_id", None, suppress_exception=False)
|
||||
|
||||
def test_with_exception_suppressed_get_secret_store_for_project(self):
|
||||
returned_value = self.proj_store_repo.\
|
||||
get_secret_store_for_project("invalid_id", None,
|
||||
suppress_exception=True)
|
||||
self.assertIsNone(returned_value)
|
||||
|
||||
def test_get_project_entities(self):
|
||||
entities = self.proj_store_repo.get_project_entities(uuid.uuid4().hex)
|
||||
self.assertEqual([], entities)
|
||||
|
||||
def test_create_or_update_for_project(self):
|
||||
project1 = self._create_project()
|
||||
name = "first_name"
|
||||
store_plugin = 'first_store'
|
||||
crypto_plugin = 'first_crypto'
|
||||
s_store1 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
|
||||
# assert that no preferred secret store is set project.
|
||||
entity = self.proj_store_repo.get_secret_store_for_project(
|
||||
project1.id, None, suppress_exception=True)
|
||||
self.assertIsNone(entity)
|
||||
|
||||
# create/set preferred secret store now
|
||||
created_entity = self.proj_store_repo.create_or_update_for_project(
|
||||
project1.id, s_store1.id)
|
||||
|
||||
entity = self.proj_store_repo.get_secret_store_for_project(
|
||||
project1.id, None, suppress_exception=False)
|
||||
self.assertIsNotNone(entity) # new preferred secret store
|
||||
|
||||
self.assertEqual(project1.id, entity.project_id)
|
||||
self.assertEqual(s_store1.id, entity.secret_store_id)
|
||||
self.assertEqual(store_plugin, entity.secret_store.store_plugin)
|
||||
self.assertEqual(crypto_plugin, entity.secret_store.crypto_plugin)
|
||||
self.assertEqual(name, entity.secret_store.name)
|
||||
|
||||
name = 'second_name'
|
||||
store_plugin = 'second_store'
|
||||
crypto_plugin = 'second_crypto'
|
||||
s_store2 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
|
||||
updated_entity = self.proj_store_repo.create_or_update_for_project(
|
||||
project1.id, s_store2.id)
|
||||
|
||||
self.assertEqual(created_entity.id, updated_entity.id)
|
||||
self.assertEqual(s_store2.id, updated_entity.secret_store_id)
|
||||
|
||||
def test_get_count_by_secret_store(self):
|
||||
project1 = self._create_project()
|
||||
name = "first_name"
|
||||
store_plugin = 'first_store'
|
||||
crypto_plugin = 'first_crypto'
|
||||
s_store1 = self._create_secret_store(name, store_plugin,
|
||||
crypto_plugin, False)
|
||||
|
||||
count = self.proj_store_repo.get_count_by_secret_store(s_store1.id)
|
||||
self.assertEqual(0, count)
|
||||
|
||||
# create/set preferred secret store now
|
||||
self.proj_store_repo.create_or_update_for_project(project1.id,
|
||||
s_store1.id)
|
||||
|
||||
count = self.proj_store_repo.get_count_by_secret_store(s_store1.id)
|
||||
self.assertEqual(1, count)
|
||||
|
||||
project2 = self._create_project()
|
||||
self.proj_store_repo.create_or_update_for_project(project2.id,
|
||||
s_store1.id)
|
||||
|
||||
count = self.proj_store_repo.get_count_by_secret_store(s_store1.id)
|
||||
self.assertEqual(2, count)
|
|
@ -639,5 +639,118 @@ class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
|
|||
self.assertEqual(106,
|
||||
project_quotas.to_dict_fields()['cas'])
|
||||
|
||||
|
||||
class WhenCreatingNewSecretStores(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(WhenCreatingNewSecretStores, self).setUp()
|
||||
|
||||
def test_new_secret_stores_for_all_input(self):
|
||||
name = "db backend"
|
||||
store_plugin = 'store_crypto'
|
||||
crypto_plugin = 'simple_crypto'
|
||||
ss = models.SecretStores(name, store_plugin, crypto_plugin,
|
||||
global_default=True)
|
||||
|
||||
self.assertEqual(store_plugin, ss.store_plugin)
|
||||
self.assertEqual(crypto_plugin, ss.crypto_plugin)
|
||||
self.assertEqual(name, ss.name)
|
||||
self.assertEqual(True, ss.global_default)
|
||||
self.assertEqual(models.States.ACTIVE, ss.status)
|
||||
|
||||
def test_new_secret_stores_required_input_only(self):
|
||||
store_plugin = 'store_crypto'
|
||||
name = "db backend"
|
||||
ss = models.SecretStores(name, store_plugin)
|
||||
|
||||
self.assertEqual(store_plugin, ss.store_plugin)
|
||||
self.assertEqual(name, ss.name)
|
||||
self.assertIsNone(ss.crypto_plugin)
|
||||
self.assertIsNone(ss.global_default) # False default is not used
|
||||
self.assertEqual(models.States.ACTIVE, ss.status)
|
||||
|
||||
def test_should_throw_exception_missing_store_plugin(self):
|
||||
name = "db backend"
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.SecretStores, name, None)
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.SecretStores, name, "")
|
||||
|
||||
def test_should_throw_exception_missing_name(self):
|
||||
store_plugin = 'store_crypto'
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.SecretStores, None, store_plugin)
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.SecretStores, "", store_plugin)
|
||||
|
||||
def test_secret_stores_check_to_dict_fields(self):
|
||||
name = "pkcs11 backend"
|
||||
store_plugin = 'store_crypto'
|
||||
crypto_plugin = 'p11_crypto'
|
||||
ss = models.SecretStores(name, store_plugin, crypto_plugin,
|
||||
global_default=True)
|
||||
self.assertEqual(store_plugin,
|
||||
ss.to_dict_fields()['store_plugin'])
|
||||
self.assertEqual(crypto_plugin,
|
||||
ss.to_dict_fields()['crypto_plugin'])
|
||||
self.assertEqual(True,
|
||||
ss.to_dict_fields()['global_default'])
|
||||
self.assertEqual(models.States.ACTIVE,
|
||||
ss.to_dict_fields()['status'])
|
||||
self.assertEqual(name, ss.to_dict_fields()['name'])
|
||||
|
||||
# check with required input only
|
||||
ss = models.SecretStores(name, store_plugin)
|
||||
self.assertEqual(store_plugin,
|
||||
ss.to_dict_fields()['store_plugin'])
|
||||
self.assertIsNone(ss.to_dict_fields()['crypto_plugin'])
|
||||
self.assertIsNone(ss.to_dict_fields()['global_default'])
|
||||
self.assertEqual(models.States.ACTIVE,
|
||||
ss.to_dict_fields()['status'])
|
||||
self.assertEqual(name, ss.to_dict_fields()['name'])
|
||||
|
||||
|
||||
class WhenCreatingNewProjectSecretStore(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(WhenCreatingNewProjectSecretStore, self).setUp()
|
||||
|
||||
def test_new_project_secret_store(self):
|
||||
|
||||
project_id = 'proj_123456'
|
||||
name = "db backend"
|
||||
store_plugin = 'store_crypto'
|
||||
crypto_plugin = 'simple_crypto'
|
||||
ss = models.SecretStores(name, store_plugin, crypto_plugin,
|
||||
global_default=True)
|
||||
ss.id = "ss_123456"
|
||||
|
||||
project_ss = models.ProjectSecretStore(project_id, ss.id)
|
||||
self.assertEqual(project_id, project_ss.project_id)
|
||||
self.assertEqual(ss.id, project_ss.secret_store_id)
|
||||
self.assertEqual(models.States.ACTIVE, project_ss.status)
|
||||
|
||||
def test_should_throw_exception_missing_project_id(self):
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.ProjectSecretStore, None, "ss_123456")
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.ProjectSecretStore, "", "ss_123456")
|
||||
|
||||
def test_should_throw_exception_missing_secret_store_id(self):
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.ProjectSecretStore, "proj_123456", None)
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.ProjectSecretStore, "proj_123456", "")
|
||||
|
||||
def test_project_secret_store_check_to_dict_fields(self):
|
||||
project_id = 'proj_123456'
|
||||
secret_store_id = 'ss_7689012'
|
||||
project_ss = models.ProjectSecretStore(project_id, secret_store_id)
|
||||
self.assertEqual(project_id,
|
||||
project_ss.to_dict_fields()['project_id'])
|
||||
self.assertEqual(secret_store_id,
|
||||
project_ss.to_dict_fields()['secret_store_id'])
|
||||
self.assertEqual(models.States.ACTIVE,
|
||||
project_ss.to_dict_fields()['status'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -356,3 +356,6 @@ class WhenTestingSimpleCryptoPlugin(utils.BaseTestCase):
|
|||
response_dto.kek_meta_extended,
|
||||
mock.MagicMock())
|
||||
self.assertEqual(16, len(key))
|
||||
|
||||
def test_get_plugin_name(self):
|
||||
self.assertIsNotNone(self.plugin.get_plugin_name())
|
||||
|
|
|
@ -64,6 +64,9 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase):
|
|||
self.cfg_mock.p11_crypto_plugin.seed_file = ''
|
||||
self.cfg_mock.p11_crypto_plugin.seed_length = 32
|
||||
|
||||
self.plugin_name = 'Test PKCS11 plugin'
|
||||
self.cfg_mock.p11_crypto_plugin.plugin_name = self.plugin_name
|
||||
|
||||
self.plugin = p11_crypto.P11CryptoPlugin(
|
||||
conf=self.cfg_mock, pkcs11=self.pkcs11
|
||||
)
|
||||
|
@ -382,3 +385,6 @@ class WhenTestingP11CryptoPlugin(utils.BaseTestCase):
|
|||
self.assertEqual(self.pkcs11.finalize.call_count, 1)
|
||||
self.assertEqual(self.plugin._create_pkcs11.call_count, 1)
|
||||
self.assertEqual(self.plugin._configure_object_cache.call_count, 1)
|
||||
|
||||
def test_get_plugin_name(self):
|
||||
self.assertEqual(self.plugin_name, self.plugin.get_plugin_name())
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
import mock
|
||||
|
||||
from barbican.common import utils as common_utils
|
||||
from barbican.plugin.crypto import crypto
|
||||
from barbican.plugin.crypto import manager as cm
|
||||
from barbican.plugin.crypto import p11_crypto
|
||||
from barbican.plugin.interface import secret_store as str
|
||||
from barbican.plugin import kmip_secret_store as kss
|
||||
from barbican.plugin import store_crypto
|
||||
from barbican.tests import utils
|
||||
|
||||
|
||||
|
@ -27,6 +32,9 @@ class TestSecretStore(str.SecretStoreBase):
|
|||
super(TestSecretStore, self).__init__()
|
||||
self.alg_list = supported_alg_list
|
||||
|
||||
def get_plugin_name(self):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def generate_symmetric_key(self, key_spec):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
|
@ -59,6 +67,9 @@ class TestSecretStoreWithTransportKey(str.SecretStoreBase):
|
|||
super(TestSecretStoreWithTransportKey, self).__init__()
|
||||
self.alg_list = supported_alg_list
|
||||
|
||||
def get_plugin_name(self):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def generate_symmetric_key(self, key_spec):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
|
@ -243,3 +254,63 @@ class WhenTestingSecretStorePluginManager(utils.BaseTestCase):
|
|||
self.manager.get_plugin_store(
|
||||
key_spec=keySpec,
|
||||
transport_key_needed=True))
|
||||
|
||||
|
||||
class TestSecretStorePluginManagerMultipleBackend(
|
||||
utils.MultipleBackendsTestCase):
|
||||
|
||||
def test_plugin_created_as_per_mulitple_backend_conf(self):
|
||||
"""Check plugins are created as per multiple backend conf"""
|
||||
|
||||
store_plugin_names = ['store_crypto', 'kmip_plugin', 'store_crypto']
|
||||
crypto_plugin_names = ['p11_crypto', '', 'simple_crypto']
|
||||
|
||||
self.init_via_conf_file(store_plugin_names,
|
||||
crypto_plugin_names, enabled=True)
|
||||
|
||||
with mock.patch('barbican.plugin.crypto.p11_crypto.P11CryptoPlugin.'
|
||||
'_create_pkcs11') as m_pkcs11, \
|
||||
mock.patch('kmip.pie.client.ProxyKmipClient') as m_kmip:
|
||||
|
||||
manager = str.SecretStorePluginManager()
|
||||
|
||||
# check pkcs11 and kmip plugin instantiation call is invoked
|
||||
m_pkcs11.called_once_with(mock.ANY, mock.ANY)
|
||||
m_kmip.called_once_with(mock.ANY)
|
||||
# check store crypto adapter is matched as its defined first.
|
||||
keySpec = str.KeySpec(str.KeyAlgorithm.AES, 128)
|
||||
plugin_found = manager.get_plugin_store(keySpec)
|
||||
self.assertIsInstance(plugin_found,
|
||||
store_crypto.StoreCryptoAdapterPlugin)
|
||||
|
||||
# check pkcs11 crypto is matched as its defined first.
|
||||
crypto_plugin = cm.get_manager().get_plugin_store_generate(
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT)
|
||||
self.assertIsInstance(crypto_plugin, p11_crypto.P11CryptoPlugin)
|
||||
|
||||
def test_plugin_created_kmip_default_mulitple_backend_conf(self):
|
||||
"""Check plugins are created as per multiple backend conf
|
||||
|
||||
Here KMIP plugin is marked as global default plugin
|
||||
"""
|
||||
|
||||
store_plugin_names = ['store_crypto', 'kmip_plugin', 'store_crypto']
|
||||
crypto_plugin_names = ['p11_crypto', '', 'simple_crypto']
|
||||
|
||||
self.init_via_conf_file(store_plugin_names,
|
||||
crypto_plugin_names, enabled=True,
|
||||
global_default_index=1)
|
||||
|
||||
with mock.patch('barbican.plugin.crypto.p11_crypto.P11CryptoPlugin.'
|
||||
'_create_pkcs11') as m_pkcs11, \
|
||||
mock.patch('kmip.pie.client.ProxyKmipClient') as m_kmip:
|
||||
|
||||
manager = str.SecretStorePluginManager()
|
||||
|
||||
# check pkcs11 and kmip plugin instantiation call is invoked
|
||||
m_pkcs11.called_once_with(mock.ANY, mock.ANY)
|
||||
m_kmip.called_once_with(mock.ANY)
|
||||
# check kmip store is matched as its global default store.
|
||||
keySpec = str.KeySpec(str.KeyAlgorithm.AES, 128)
|
||||
plugin_found = manager.get_plugin_store(keySpec)
|
||||
self.assertIsInstance(plugin_found, kss.KMIPSecretStore)
|
||||
|
|
|
@ -52,9 +52,10 @@ class WhenTestingDogtagKRAPlugin(utils.BaseTestCase):
|
|||
# create nss db for test only
|
||||
self.nss_dir = tempfile.mkdtemp()
|
||||
|
||||
self.plugin_name = "Test Dogtag KRA plugin"
|
||||
self.cfg_mock = mock.MagicMock(name='config mock')
|
||||
self.cfg_mock.dogtag_plugin = mock.MagicMock(
|
||||
nss_db_path=self.nss_dir)
|
||||
nss_db_path=self.nss_dir, plugin_name=self.plugin_name)
|
||||
self.plugin = dogtag_import.DogtagKRAPlugin(self.cfg_mock)
|
||||
self.plugin.keyclient = self.keyclient_mock
|
||||
|
||||
|
@ -63,6 +64,9 @@ class WhenTestingDogtagKRAPlugin(utils.BaseTestCase):
|
|||
self.patcher.stop()
|
||||
os.rmdir(self.nss_dir)
|
||||
|
||||
def test_get_plugin_name(self):
|
||||
self.assertEqual(self.plugin_name, self.plugin.get_plugin_name())
|
||||
|
||||
def test_generate_symmetric_key(self):
|
||||
key_spec = sstore.KeySpec(sstore.KeyAlgorithm.AES, 128)
|
||||
self.plugin.generate_symmetric_key(key_spec)
|
||||
|
|
|
@ -920,3 +920,9 @@ class WhenTestingKMIPSecretStore(utils.BaseTestCase):
|
|||
CONF.kmip_plugin.keyfile = '/some/path'
|
||||
kss.KMIPSecretStore(CONF)
|
||||
self.assertEqual(1, len(m.mock_calls))
|
||||
|
||||
def test_get_plugin_name(self):
|
||||
CONF = kss.CONF
|
||||
CONF.kmip_plugin.plugin_name = "Test KMIP Plugin"
|
||||
secret_store = kss.KMIPSecretStore(CONF)
|
||||
self.assertEqual("Test KMIP Plugin", secret_store.get_plugin_name())
|
||||
|
|
|
@ -0,0 +1,574 @@
|
|||
# (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
import collections
|
||||
import mock
|
||||
import uuid
|
||||
|
||||
from barbican.common import config
|
||||
from barbican.common import exception
|
||||
from barbican.model import models
|
||||
from barbican.model import repositories
|
||||
from barbican.plugin.crypto import crypto
|
||||
from barbican.plugin.crypto import manager as cm
|
||||
from barbican.plugin.crypto import p11_crypto
|
||||
from barbican.plugin.crypto import simple_crypto
|
||||
from barbican.plugin.interface import secret_store
|
||||
from barbican.plugin import kmip_secret_store as kss
|
||||
from barbican.plugin import store_crypto
|
||||
from barbican.plugin.util import multiple_backends
|
||||
from barbican.tests import utils as test_utils
|
||||
|
||||
|
||||
class MockedManager(object):
|
||||
|
||||
NAME_PREFIX = "friendly_"
|
||||
|
||||
def __init__(self, names, enabled=True,
|
||||
plugin_lookup_field='store_plugin'):
|
||||
ExtTuple = collections.namedtuple('ExtTuple', ['name', 'obj'])
|
||||
self.extensions = []
|
||||
for name in names:
|
||||
m = mock.MagicMock()
|
||||
m.get_plugin_name.return_value = self.NAME_PREFIX + name
|
||||
new_extension = ExtTuple(name, m)
|
||||
self.extensions.append(new_extension)
|
||||
self.global_default_store_dict = None
|
||||
self.parsed_stores = multiple_backends.read_multiple_backends_config()
|
||||
|
||||
|
||||
class WhenReadingMultipleBackendsConfig(test_utils.MultipleBackendsTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenReadingMultipleBackendsConfig, self).setUp()
|
||||
|
||||
def test_successful_conf_read(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True,
|
||||
global_default_index=1)
|
||||
|
||||
stores = multiple_backends.read_multiple_backends_config()
|
||||
|
||||
self.assertEqual(len(ss_plugins), len(stores))
|
||||
self.assertEqual('ss_p1', stores[0].store_plugin)
|
||||
self.assertEqual('cr_p1', stores[0].crypto_plugin)
|
||||
self.assertEqual(False, stores[0].global_default)
|
||||
self.assertEqual('ss_p2', stores[1].store_plugin)
|
||||
self.assertEqual('cr_p2', stores[1].crypto_plugin)
|
||||
self.assertEqual(True, stores[1].global_default)
|
||||
self.assertEqual('ss_p3', stores[2].store_plugin)
|
||||
self.assertEqual('cr_p3', stores[2].crypto_plugin)
|
||||
self.assertEqual(False, stores[2].global_default)
|
||||
|
||||
def test_fail_when_store_plugin_name_missing(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
|
||||
self.assertRaises(exception.MultipleStorePluginValueMissing,
|
||||
multiple_backends.read_multiple_backends_config)
|
||||
|
||||
def test_fail_when_store_plugin_name_is_blank(self):
|
||||
ss_plugins = ['ss_p1', '', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
|
||||
self.assertRaises(exception.MultipleStorePluginValueMissing,
|
||||
multiple_backends.read_multiple_backends_config)
|
||||
|
||||
def test_successful_conf_read_when_crypto_plugin_name_is_missing(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
|
||||
stores = multiple_backends.read_multiple_backends_config()
|
||||
self.assertEqual(len(ss_plugins), len(stores))
|
||||
|
||||
def test_conf_read_when_multiple_plugin_disabled(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=False)
|
||||
|
||||
stores = multiple_backends.read_multiple_backends_config()
|
||||
self.assertIsNone(stores)
|
||||
|
||||
def test_successful_conf_read_when_crypto_plugin_name_is_blank(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', '', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
|
||||
stores = multiple_backends.read_multiple_backends_config()
|
||||
self.assertEqual(len(ss_plugins), len(stores))
|
||||
self.assertEqual('', stores[1].crypto_plugin)
|
||||
|
||||
def test_fail_when_global_default_not_specified(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True,
|
||||
global_default_index=-1)
|
||||
|
||||
self.assertRaises(exception.MultipleStoreIncorrectGlobalDefault,
|
||||
multiple_backends.read_multiple_backends_config)
|
||||
|
||||
def test_fail_when_stores_lookup_suffix_missing_when_enabled(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True,
|
||||
global_default_index=0)
|
||||
|
||||
conf = config.get_module_config('secretstore')
|
||||
conf.set_override("stores_lookup_suffix", [], group='secretstore',
|
||||
enforce_type=True)
|
||||
self.assertRaises(exception.MultipleSecretStoreLookupFailed,
|
||||
multiple_backends.read_multiple_backends_config)
|
||||
|
||||
def test_fail_when_secretstore_section_missing(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True,
|
||||
global_default_index=-1)
|
||||
ss_conf = config.get_module_config('secretstore')
|
||||
|
||||
existing_value = ss_conf.secretstore.stores_lookup_suffix
|
||||
existing_value.append('unknown_section')
|
||||
|
||||
ss_conf.set_override('stores_lookup_suffix', existing_value,
|
||||
'secretstore')
|
||||
|
||||
self.assertRaises(exception.MultipleStorePluginValueMissing,
|
||||
multiple_backends.read_multiple_backends_config)
|
||||
|
||||
|
||||
class WhenInvokingSyncSecretStores(test_utils.MultipleBackendsTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WhenInvokingSyncSecretStores, self).setUp()
|
||||
|
||||
def test_successful_syncup_no_existing_secret_stores(self):
|
||||
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3', 'ss_p4', 'ss_p5']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3', 'cr_p4', 'cr_p5']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
multiple_backends.sync_secret_stores(secretstore_manager,
|
||||
crypto_manager)
|
||||
|
||||
default_secret_store = multiple_backends.\
|
||||
get_global_default_secret_store()
|
||||
self.assertEqual('ss_p1', default_secret_store.store_plugin)
|
||||
self.assertEqual('cr_p1', default_secret_store.crypto_plugin)
|
||||
self.assertEqual(MockedManager.NAME_PREFIX + 'cr_p1',
|
||||
default_secret_store.name)
|
||||
|
||||
ss_db_entries = repositories.get_secret_stores_repository().get_all()
|
||||
self.assertEqual(5, len(ss_db_entries))
|
||||
|
||||
def test_syncup_with_existing_secret_stores(self):
|
||||
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3', 'ss_p4', 'ss_p5']
|
||||
cr_plugins = ['cr_p1', '', 'cr_p3', 'cr_p4', 'cr_p5']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
multiple_backends.sync_secret_stores(secretstore_manager,
|
||||
crypto_manager)
|
||||
|
||||
ss_db_entries = repositories.get_secret_stores_repository().get_all()
|
||||
self.assertEqual(5, len(ss_db_entries))
|
||||
|
||||
# check friendly name for the case when crypto plugin is not there
|
||||
ss_db_entry = self._get_secret_store_entry('ss_p2', None)
|
||||
self.assertIsNotNone(ss_db_entry)
|
||||
self.assertEqual(MockedManager.NAME_PREFIX + 'ss_p2',
|
||||
ss_db_entry.name)
|
||||
|
||||
ss_plugins = ['ss_p3', 'ss_p4', 'ss_p5', 'ss_p6']
|
||||
cr_plugins = ['cr_p3', 'cr_p4', 'cr_p5', 'cr_p6']
|
||||
# update conf and re-run sync store
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
|
||||
multiple_backends.sync_secret_stores(secretstore_manager,
|
||||
crypto_manager)
|
||||
|
||||
ss_db_entry = self._get_secret_store_entry('ss_p2', 'cr_p2')
|
||||
self.assertIsNone(ss_db_entry)
|
||||
|
||||
ss_db_entry = self._get_secret_store_entry('ss_p6', 'cr_p6')
|
||||
self.assertIsNotNone(ss_db_entry)
|
||||
|
||||
default_secret_store = multiple_backends.\
|
||||
get_global_default_secret_store()
|
||||
self.assertEqual('ss_p3', default_secret_store.store_plugin)
|
||||
self.assertEqual('cr_p3', default_secret_store.crypto_plugin)
|
||||
self.assertEqual(MockedManager.NAME_PREFIX + 'cr_p3',
|
||||
default_secret_store.name)
|
||||
ss_db_entries = repositories.get_secret_stores_repository().get_all()
|
||||
self.assertEqual(4, len(ss_db_entries))
|
||||
|
||||
def test_syncup_modify_global_default(self):
|
||||
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3', 'ss_p4', 'ss_p5']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3', 'cr_p4', 'cr_p5']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
multiple_backends.sync_secret_stores(secretstore_manager,
|
||||
crypto_manager)
|
||||
|
||||
global_secret_store = multiple_backends.\
|
||||
get_global_default_secret_store()
|
||||
self.assertEqual('ss_p1', global_secret_store.store_plugin)
|
||||
self.assertEqual('cr_p1', global_secret_store.crypto_plugin)
|
||||
self.assertEqual(MockedManager.NAME_PREFIX + 'cr_p1',
|
||||
global_secret_store.name)
|
||||
|
||||
ss_plugins = ['ss_p9', 'ss_p4', 'ss_p5']
|
||||
cr_plugins = ['cr_p9', 'cr_p4', 'cr_p5']
|
||||
# update conf and re-run sync store
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
multiple_backends.sync_secret_stores(secretstore_manager,
|
||||
crypto_manager)
|
||||
|
||||
global_secret_store = multiple_backends.\
|
||||
get_global_default_secret_store()
|
||||
self.assertEqual('ss_p9', global_secret_store.store_plugin)
|
||||
self.assertEqual('cr_p9', global_secret_store.crypto_plugin)
|
||||
self.assertEqual(MockedManager.NAME_PREFIX + 'cr_p9',
|
||||
global_secret_store.name)
|
||||
|
||||
def test_syncup_with_store_and_crypto_plugins_count_mismatch(self):
|
||||
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3', 'ss_p4']
|
||||
cr_plugins = ['cr_p1', '', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
multiple_backends.sync_secret_stores(secretstore_manager,
|
||||
crypto_manager)
|
||||
|
||||
# empty crypto_plugin name maps to None in database entry
|
||||
ss_db_entry = self._get_secret_store_entry('ss_p2', None)
|
||||
self.assertIsNotNone(ss_db_entry)
|
||||
ss_db_entry = self._get_secret_store_entry('ss_p2', '')
|
||||
self.assertIsNone(ss_db_entry)
|
||||
|
||||
# missing crypto plugin name maps to None in database entry
|
||||
ss_db_entry = self._get_secret_store_entry('ss_p4', None)
|
||||
self.assertIsNotNone(ss_db_entry)
|
||||
|
||||
def test_syncup_delete_secret_store_with_preferred_project_using_it(self):
|
||||
"""Removing secret store will fail if its defined as preferred store.
|
||||
|
||||
"""
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3', 'ss_p4']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3', 'cr_p4']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
multiple_backends.sync_secret_stores(secretstore_manager,
|
||||
crypto_manager)
|
||||
|
||||
with mock.patch('barbican.model.repositories.'
|
||||
'get_project_secret_store_repository') as ps_repo:
|
||||
# Mocking with 2 projects as using preferred secret store
|
||||
ps_repo.get_count_by_secret_store.return_value = 2
|
||||
|
||||
ss_plugins = ['ss_p3', 'ss_p4']
|
||||
cr_plugins = ['cr_p3', 'cr_p4']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
secretstore_manager = MockedManager(ss_plugins)
|
||||
crypto_manager = MockedManager(cr_plugins)
|
||||
|
||||
self.assertRaises(exception.MultipleStorePluginStillInUse,
|
||||
multiple_backends.sync_secret_stores,
|
||||
secretstore_manager, crypto_manager)
|
||||
|
||||
def test_get_global_default_store_when_multiple_backends_disabled(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=False)
|
||||
|
||||
default_store = multiple_backends.get_global_default_secret_store()
|
||||
self.assertIsNone(default_store)
|
||||
|
||||
|
||||
class TestGetApplicablePlugins(test_utils.MultipleBackendsTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestGetApplicablePlugins, self).setUp()
|
||||
|
||||
def test_get_when_project_preferred_plugin_is_set(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
ss_manager = MockedManager(ss_plugins)
|
||||
project_id = uuid.uuid4().hex
|
||||
|
||||
with mock.patch('barbican.model.repositories.ProjectSecretStoreRepo.'
|
||||
'get_secret_store_for_project') as pref_func:
|
||||
|
||||
# set preferred secret store to one of value in config
|
||||
m_dict = {'store_plugin': 'ss_p3'}
|
||||
m_rec = mock.MagicMock()
|
||||
m_rec.secret_store.to_dict_fields.return_value = m_dict
|
||||
pref_func.return_value = m_rec
|
||||
|
||||
objs = multiple_backends.get_applicable_store_plugins(
|
||||
ss_manager, project_id, None)
|
||||
self.assertIn(project_id, pref_func.call_args_list[0][0])
|
||||
self.assertIsInstance(objs, list)
|
||||
self.assertEqual(1, len(objs))
|
||||
self.assertIn('ss_p3', objs[0].get_plugin_name())
|
||||
|
||||
def test_get_when_project_preferred_plugin_is_not_found_in_conf(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True)
|
||||
ss_manager = MockedManager(ss_plugins)
|
||||
|
||||
project_id = uuid.uuid4().hex
|
||||
|
||||
with mock.patch('barbican.model.repositories.ProjectSecretStoreRepo.'
|
||||
'get_secret_store_for_project') as pref_func:
|
||||
|
||||
# set preferred secret store value which is not defined in config
|
||||
m_dict = {'store_plugin': 'old_preferred_plugin'}
|
||||
m_rec = mock.MagicMock()
|
||||
m_rec.secret_store.to_dict_fields.return_value = m_dict
|
||||
pref_func.return_value = m_rec
|
||||
|
||||
self.assertRaises(exception.MultipleStorePreferredPluginMissing,
|
||||
multiple_backends.get_applicable_store_plugins,
|
||||
ss_manager, project_id, None)
|
||||
self.assertIn(project_id, pref_func.call_args_list[0][0])
|
||||
|
||||
def test_get_when_project_preferred_plugin_not_set_then_default_used(self):
|
||||
ss_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
# setting second plugin to be global default
|
||||
self.init_via_conf_file(ss_plugins, cr_plugins, enabled=True,
|
||||
global_default_index=1)
|
||||
cr_manager = MockedManager(cr_plugins,
|
||||
plugin_lookup_field='crypto_plugin')
|
||||
project_id = uuid.uuid4().hex
|
||||
|
||||
with mock.patch('barbican.plugin.util.multiple_backends.'
|
||||
'get_global_default_secret_store') as gd_func:
|
||||
|
||||
m_dict = {'crypto_plugin': 'cr_p2'}
|
||||
gd_func.return_value.to_dict_fields.return_value = m_dict
|
||||
objs = multiple_backends.get_applicable_crypto_plugins(cr_manager,
|
||||
project_id,
|
||||
None)
|
||||
gd_func.assert_called_once_with()
|
||||
self.assertIsInstance(objs, list)
|
||||
self.assertEqual(1, len(objs))
|
||||
self.assertIn('cr_p2', objs[0].get_plugin_name())
|
||||
|
||||
# call again with no project_id set
|
||||
objs = multiple_backends.get_applicable_crypto_plugins(cr_manager,
|
||||
None, None)
|
||||
gd_func.assert_called_once_with()
|
||||
self.assertIsInstance(objs, list)
|
||||
self.assertEqual(1, len(objs))
|
||||
self.assertIn('cr_p2', objs[0].get_plugin_name())
|
||||
|
||||
def test_get_applicable_store_plugins_when_multiple_backend_not_enabled(
|
||||
self):
|
||||
|
||||
ss_config = config.get_module_config('secretstore')
|
||||
ss_plugins = ['ss_p11', 'ss_p22', 'ss_p33', 'ss_p44']
|
||||
ss_conf_plugins = ['ss_p1', 'ss_p2', 'ss_p3']
|
||||
cr_conf_plugins = ['cr_p1', 'cr_p2', 'cr_p3']
|
||||
self.init_via_conf_file(ss_conf_plugins, cr_conf_plugins,
|
||||
enabled=False)
|
||||
ss_manager = MockedManager(ss_plugins)
|
||||
|
||||
ss_config.set_override("enabled_secretstore_plugins",
|
||||
ss_plugins, group='secretstore',
|
||||
enforce_type=True)
|
||||
|
||||
objs = multiple_backends.get_applicable_store_plugins(ss_manager, None,
|
||||
None)
|
||||
self.assertEqual(4, len(objs))
|
||||
|
||||
|
||||
@test_utils.parameterized_test_case
|
||||
class TestPluginsGenerateStoreAPIMultipleBackend(
|
||||
test_utils.MultipleBackendsTestCase):
|
||||
|
||||
backend_dataset = {
|
||||
"db_backend": [{
|
||||
'store_plugins': ['store_crypto', 'kmip_plugin', 'store_crypto'],
|
||||
'crypto_plugins': ['simple_crypto', '', 'p11_crypto'],
|
||||
'default_store_class': store_crypto.StoreCryptoAdapterPlugin,
|
||||
'default_crypto_class': simple_crypto.SimpleCryptoPlugin
|
||||
}],
|
||||
"kmip": [{
|
||||
'store_plugins': ['kmip_plugin', 'store_crypto', 'store_crypto'],
|
||||
'crypto_plugins': ['', 'p11_crypto', 'simple_crypto'],
|
||||
'default_store_class': kss.KMIPSecretStore,
|
||||
'default_crypto_class': None
|
||||
}],
|
||||
"pkcs11": [{
|
||||
'store_plugins': ['store_crypto', 'store_crypto', 'kmip_plugin'],
|
||||
'crypto_plugins': ['p11_crypto', 'simple_crypto', ''],
|
||||
'default_store_class': store_crypto.StoreCryptoAdapterPlugin,
|
||||
'default_crypto_class': p11_crypto.P11CryptoPlugin
|
||||
}]
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestPluginsGenerateStoreAPIMultipleBackend, self).setUp()
|
||||
|
||||
def _create_project(self):
|
||||
session = repositories.get_project_repository().get_session()
|
||||
|
||||
project = models.Project()
|
||||
project.external_id = "keystone_project_id" + uuid.uuid4().hex
|
||||
project.save(session=session)
|
||||
return project
|
||||
|
||||
def _create_project_store(self, project_id, secret_store_id):
|
||||
proj_store_repo = repositories.get_project_secret_store_repository()
|
||||
session = proj_store_repo.get_session()
|
||||
|
||||
proj_model = models.ProjectSecretStore(project_id, secret_store_id)
|
||||
|
||||
proj_s_store = proj_store_repo.create_from(proj_model, session)
|
||||
proj_s_store.save(session=session)
|
||||
return proj_s_store
|
||||
|
||||
@test_utils.parameterized_dataset(backend_dataset)
|
||||
def test_no_preferred_default_plugin(self, dataset):
|
||||
"""Check name, plugin and crypto class used for default secret store
|
||||
|
||||
Secret store name is crypto class plugin name if defined otherwise user
|
||||
friendly name is derived from store class plugin name
|
||||
"""
|
||||
|
||||
self.init_via_conf_file(dataset['store_plugins'],
|
||||
dataset['crypto_plugins'],
|
||||
enabled=True)
|
||||
|
||||
with mock.patch('barbican.plugin.crypto.p11_crypto.P11CryptoPlugin.'
|
||||
'_create_pkcs11'), \
|
||||
mock.patch('kmip.pie.client.ProxyKmipClient'):
|
||||
manager = secret_store.SecretStorePluginManager()
|
||||
|
||||
keySpec = secret_store.KeySpec(secret_store.KeyAlgorithm.AES, 128)
|
||||
|
||||
plugin_found = manager.get_plugin_store(keySpec)
|
||||
self.assertIsInstance(plugin_found,
|
||||
dataset['default_store_class'])
|
||||
|
||||
global_secret_store = multiple_backends.\
|
||||
get_global_default_secret_store()
|
||||
|
||||
if dataset['default_crypto_class']:
|
||||
crypto_plugin = cm.get_manager().get_plugin_store_generate(
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT)
|
||||
self.assertIsInstance(crypto_plugin,
|
||||
dataset['default_crypto_class'])
|
||||
|
||||
# make sure secret store name is same as crypto class friendly name
|
||||
# as store_plugin class is not direct impl of SecretStoreBase
|
||||
self.assertEqual(global_secret_store.name,
|
||||
crypto_plugin.get_plugin_name())
|
||||
else: # crypto class is not used
|
||||
# make sure secret store name is same as store plugin class
|
||||
# friendly name
|
||||
self.assertEqual(global_secret_store.name,
|
||||
plugin_found.get_plugin_name())
|
||||
# error raised for no crypto plugin
|
||||
self.assertRaises(crypto.CryptoPluginNotFound,
|
||||
cm.get_manager().get_plugin_store_generate,
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT)
|
||||
|
||||
@test_utils.parameterized_dataset(backend_dataset)
|
||||
def test_project_preferred_default_plugin(self, dataset):
|
||||
"""Check project preferred behavior with different global default"""
|
||||
|
||||
self.init_via_conf_file(dataset['store_plugins'],
|
||||
dataset['crypto_plugins'],
|
||||
enabled=True)
|
||||
|
||||
with mock.patch('barbican.plugin.crypto.p11_crypto.P11CryptoPlugin.'
|
||||
'_create_pkcs11'), \
|
||||
mock.patch('kmip.pie.client.ProxyKmipClient'):
|
||||
manager = secret_store.SecretStorePluginManager()
|
||||
|
||||
pkcs11_secret_store = self._get_secret_store_entry('store_crypto',
|
||||
'p11_crypto')
|
||||
kmip_secret_store = self._get_secret_store_entry('kmip_plugin', None)
|
||||
db_secret_store = self._get_secret_store_entry('store_crypto',
|
||||
'simple_crypto')
|
||||
|
||||
project1 = self._create_project()
|
||||
project2 = self._create_project()
|
||||
project3 = self._create_project()
|
||||
|
||||
# For project1 , make pkcs11 as preferred secret store
|
||||
self._create_project_store(project1.id, pkcs11_secret_store.id)
|
||||
# For project2 , make kmip as preferred secret store
|
||||
self._create_project_store(project2.id, kmip_secret_store.id)
|
||||
# For project3 , make db backend as preferred secret store
|
||||
self._create_project_store(project3.id, db_secret_store.id)
|
||||
|
||||
keySpec = secret_store.KeySpec(secret_store.KeyAlgorithm.AES, 128)
|
||||
cm_manager = cm.get_manager()
|
||||
|
||||
# For project1, verify store and crypto plugin instance used are pkcs11
|
||||
# specific
|
||||
plugin_found = manager.get_plugin_store(keySpec,
|
||||
project_id=project1.id)
|
||||
self.assertIsInstance(plugin_found,
|
||||
store_crypto.StoreCryptoAdapterPlugin)
|
||||
crypto_plugin = cm.get_manager().get_plugin_store_generate(
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT, project_id=project1.id)
|
||||
self.assertIsInstance(crypto_plugin, p11_crypto.P11CryptoPlugin)
|
||||
|
||||
# For project2, verify store plugin instance is kmip specific
|
||||
# and there is no crypto plugin instance
|
||||
plugin_found = manager.get_plugin_store(keySpec,
|
||||
project_id=project2.id)
|
||||
self.assertIsInstance(plugin_found, kss.KMIPSecretStore)
|
||||
|
||||
self.assertRaises(
|
||||
crypto.CryptoPluginNotFound, cm_manager.get_plugin_store_generate,
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT, project_id=project2.id)
|
||||
|
||||
# For project3, verify store and crypto plugin instance used are db
|
||||
# backend specific
|
||||
plugin_found = manager.get_plugin_store(keySpec,
|
||||
project_id=project3.id)
|
||||
self.assertIsInstance(plugin_found,
|
||||
store_crypto.StoreCryptoAdapterPlugin)
|
||||
crypto_plugin = cm.get_manager().get_plugin_store_generate(
|
||||
crypto.PluginSupportTypes.ENCRYPT_DECRYPT, project_id=project3.id)
|
||||
self.assertIsInstance(crypto_plugin, simple_crypto.SimpleCryptoPlugin)
|
||||
|
||||
# Make sure for project with no preferred setting, uses global default
|
||||
project4 = self._create_project()
|
||||
plugin_found = manager.get_plugin_store(keySpec,
|
||||
project_id=project4.id)
|
||||
self.assertIsInstance(plugin_found,
|
||||
dataset['default_store_class'])
|
|
@ -22,7 +22,10 @@ import types
|
|||
import uuid
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import oslotest.base as oslotest
|
||||
from oslotest import createfile
|
||||
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
import webtest
|
||||
|
@ -30,7 +33,14 @@ import webtest
|
|||
from OpenSSL import crypto
|
||||
|
||||
from barbican.api import app
|
||||
from barbican.common import config
|
||||
import barbican.context
|
||||
from barbican.model import repositories
|
||||
from barbican.plugin.crypto import manager as cm
|
||||
from barbican.plugin.crypto import p11_crypto
|
||||
|
||||
from barbican.plugin.interface import secret_store
|
||||
from barbican.plugin import kmip_secret_store as kss
|
||||
from barbican.tests import database_utils
|
||||
|
||||
|
||||
|
@ -96,6 +106,9 @@ class BaseTestCase(oslotest.BaseTestCase):
|
|||
|
||||
def tearDown(self):
|
||||
super(BaseTestCase, self).tearDown()
|
||||
ss_conf = config.get_module_config('secretstore')
|
||||
ss_conf.clear_override("enable_multiple_secret_stores",
|
||||
group='secretstore')
|
||||
|
||||
|
||||
class MockModelRepositoryMixin(object):
|
||||
|
@ -289,6 +302,32 @@ class MockModelRepositoryMixin(object):
|
|||
mock_repo_obj=mock_preferred_ca_repo,
|
||||
patcher_obj=self.mock_preferred_ca_repo_patcher)
|
||||
|
||||
def setup_secret_stores_repository_mock(
|
||||
self, mock_secret_stores_repo=mock.MagicMock()):
|
||||
"""Mocks the project repository factory function
|
||||
|
||||
:param mock_secret_stores_repo: The pre-configured mock secret stores
|
||||
repo to be returned.
|
||||
"""
|
||||
self.mock_secret_stores_repo_patcher = None
|
||||
self._setup_repository_mock(
|
||||
repo_factory='get_secret_stores_repository',
|
||||
mock_repo_obj=mock_secret_stores_repo,
|
||||
patcher_obj=self.mock_secret_stores_repo_patcher)
|
||||
|
||||
def setup_project_secret_store_repository_mock(
|
||||
self, mock_project_secret_store_repo=mock.MagicMock()):
|
||||
"""Mocks the project repository factory function
|
||||
|
||||
:param mock_project_secret_store_repo: The pre-configured mock project
|
||||
secret store repo to be returned.
|
||||
"""
|
||||
self.mock_proj_secret_store_repo_patcher = None
|
||||
self._setup_repository_mock(
|
||||
repo_factory='get_project_secret_store_repository',
|
||||
mock_repo_obj=mock_project_secret_store_repo,
|
||||
patcher_obj=self.mock_proj_secret_store_repo_patcher)
|
||||
|
||||
def setup_project_ca_repository_mock(
|
||||
self, mock_project_ca_repo=mock.MagicMock()):
|
||||
"""Mocks the project repository factory function
|
||||
|
@ -400,6 +439,142 @@ def parameterized_dataset(build_data):
|
|||
return decorator
|
||||
|
||||
|
||||
def setup_oslo_config_conf(testcase, content, conf_instance=None):
|
||||
|
||||
conf_file_fixture = testcase.useFixture(
|
||||
createfile.CreateFileWithContent('barbican', content))
|
||||
if conf_instance is None:
|
||||
conf_instance = cfg.CONF
|
||||
conf_instance([], project="barbican",
|
||||
default_config_files=[conf_file_fixture.path])
|
||||
|
||||
testcase.addCleanup(conf_instance.reset)
|
||||
|
||||
|
||||
def setup_multiple_secret_store_plugins_conf(testcase, store_plugin_names,
|
||||
crypto_plugin_names,
|
||||
global_default_index,
|
||||
conf_instance=None,
|
||||
multiple_support_enabled=None):
|
||||
"""Sets multiple secret store support conf as oslo conf file.
|
||||
|
||||
Generating file based conf based on input store and crypto plugin names
|
||||
provided as list. Index specified in argument is used to mark that specific
|
||||
secret store as global_default = True.
|
||||
|
||||
Input lists are
|
||||
'store_plugins': ['store_crypto', 'kmip_plugin', 'store_crypto'],
|
||||
'crypto_plugins': ['simple_crypto', '', 'p11_crypto'],
|
||||
|
||||
Sample output conf file generated is
|
||||
|
||||
[secretstore]
|
||||
enable_multiple_secret_stores = True
|
||||
stores_lookup_suffix = plugin_0, plugin_1, plugin_2
|
||||
|
||||
[secretstore:plugin_0]
|
||||
secret_store_plugin = store_crypto
|
||||
crypto_plugin = simple_crypto
|
||||
global_default = True
|
||||
|
||||
[secretstore:plugin_1]
|
||||
secret_store_plugin = kmip_plugin
|
||||
|
||||
[secretstore:plugin_2]
|
||||
secret_store_plugin = store_crypto
|
||||
crypto_plugin = p11_crypto
|
||||
|
||||
"""
|
||||
|
||||
def _get_conf_line(name, value, section=None):
|
||||
out_line = "\n[{0}]\n".format(section) if section else ""
|
||||
out_line += "{0} = {1}\n".format(name, value) if name else ""
|
||||
return out_line
|
||||
|
||||
if multiple_support_enabled is None:
|
||||
multiple_support_enabled = True
|
||||
|
||||
conf_content = ""
|
||||
|
||||
if store_plugin_names is not None:
|
||||
|
||||
if len(store_plugin_names) < len(crypto_plugin_names):
|
||||
max_count = len(crypto_plugin_names)
|
||||
else:
|
||||
max_count = len(store_plugin_names)
|
||||
|
||||
lookup_names = ['plugin_{0}'.format(indx) for indx in range(max_count)]
|
||||
section_names = ['secretstore:{0}'.format(lname) for lname in
|
||||
lookup_names]
|
||||
lookup_str = ", ".join(lookup_names)
|
||||
|
||||
conf_content = _get_conf_line('enable_multiple_secret_stores',
|
||||
multiple_support_enabled,
|
||||
section='secretstore')
|
||||
conf_content += _get_conf_line('stores_lookup_suffix',
|
||||
lookup_str, section=None)
|
||||
|
||||
for indx, section_name in enumerate(section_names):
|
||||
if indx < len(store_plugin_names):
|
||||
store_plugin = store_plugin_names[indx]
|
||||
conf_content += _get_conf_line('secret_store_plugin',
|
||||
store_plugin,
|
||||
section=section_name)
|
||||
else:
|
||||
conf_content += _get_conf_line(None, None,
|
||||
section=section_name)
|
||||
if indx < len(crypto_plugin_names):
|
||||
crypto_plugin = crypto_plugin_names[indx]
|
||||
conf_content += _get_conf_line('crypto_plugin', crypto_plugin,
|
||||
section=None)
|
||||
if indx == global_default_index:
|
||||
conf_content += _get_conf_line('global_default', 'True',
|
||||
section=None)
|
||||
|
||||
setup_oslo_config_conf(testcase, conf_content, conf_instance)
|
||||
|
||||
|
||||
class MultipleBackendsTestCase(database_utils.RepositoryTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MultipleBackendsTestCase, self).setUp()
|
||||
|
||||
def _mock_plugin_settings(self):
|
||||
|
||||
kmip_conf = kss.CONF
|
||||
kmip_conf.kmip_plugin.username = "sample_username"
|
||||
kmip_conf.kmip_plugin.password = "sample_password"
|
||||
kmip_conf.kmip_plugin.keyfile = None
|
||||
kmip_conf.kmip_plugin.pkcs1_only = False
|
||||
|
||||
pkcs11_conf = p11_crypto.CONF
|
||||
pkcs11_conf.p11_crypto_plugin.library_path = "/tmp" # any dummy path
|
||||
|
||||
def init_via_conf_file(self, store_plugin_names, crypto_plugin_names,
|
||||
enabled=True, global_default_index=0):
|
||||
secretstore_conf = config.get_module_config('secretstore')
|
||||
|
||||
setup_multiple_secret_store_plugins_conf(
|
||||
self, store_plugin_names=store_plugin_names,
|
||||
crypto_plugin_names=crypto_plugin_names,
|
||||
global_default_index=global_default_index,
|
||||
conf_instance=secretstore_conf,
|
||||
multiple_support_enabled=enabled)
|
||||
|
||||
# clear globals if already set in previous tests
|
||||
secret_store._SECRET_STORE = None # clear secret store manager
|
||||
cm._PLUGIN_MANAGER = None # clear crypto manager
|
||||
self._mock_plugin_settings()
|
||||
|
||||
def _get_secret_store_entry(self, store_plugin, crypto_plugin):
|
||||
all_ss = repositories.get_secret_stores_repository().get_all()
|
||||
for ss in all_ss:
|
||||
if (ss.store_plugin == store_plugin and
|
||||
ss.crypto_plugin == crypto_plugin):
|
||||
return ss
|
||||
return None
|
||||
|
||||
|
||||
def create_timestamp_w_tz_and_offset(timezone=None, days=0, hours=0, minutes=0,
|
||||
seconds=0):
|
||||
"""Creates a timestamp with a timezone and offset in days
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
barbican (1:3.0.0~b3-1) experimental; urgency=medium
|
||||
barbican (1:3.0.0~rc1-1) experimental; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* Fixed (build-)depends for this release.
|
||||
|
|
|
@ -17,6 +17,7 @@ API Reference
|
|||
./reference/secrets
|
||||
./reference/secret_types
|
||||
./reference/secret_metadata
|
||||
./reference/store_backends.rst
|
||||
./reference/containers
|
||||
./reference/acls
|
||||
./reference/certificates
|
||||
|
|
|
@ -0,0 +1,416 @@
|
|||
*****************************
|
||||
Secret Stores API - Reference
|
||||
*****************************
|
||||
|
||||
Barbican provides API to manage secret stores available in a deployment. APIs
|
||||
are provided for listing available secret stores and to manage project level
|
||||
secret store mapping. There are two types of secret stores. One is global
|
||||
default secret store which is used for all projects. And then project
|
||||
`preferred` secret store which is used to store all *new* secrets created in
|
||||
that project. For an introduction to multiple store backends support, see
|
||||
:doc:`Using Multiple Secret Store Plugins </setup/plugin_backends>` . This
|
||||
document will focus on the details of the Barbican `/v1/secret-stores` REST API.
|
||||
|
||||
When multiple secret store backends support is not enabled in service
|
||||
configuration, then all of these API will return resource not found (http
|
||||
status code 404) error. Error message text will highlight that the support is
|
||||
not enabled in configuration.
|
||||
|
||||
GET /v1/secret-stores
|
||||
#####################
|
||||
Project administrator can request list of available secret store backends.
|
||||
Response contains list of secret stores which are currently configured in
|
||||
barbican deployment. If multiple store backends support is not enabled, then
|
||||
list will return resource not found (404) error.
|
||||
|
||||
.. _get_secret_stores_request_response:
|
||||
|
||||
Request/Response:
|
||||
*****************
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
Request:
|
||||
|
||||
GET /secret-stores
|
||||
Headers:
|
||||
X-Auth-Token: "f9cf2d480ba3485f85bdb9d07a4959f1"
|
||||
Accept: application/json
|
||||
|
||||
Response:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"secret_stores":[
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2016-08-22T23:46:45.114283",
|
||||
"name": "PKCS11 HSM",
|
||||
"created": "2016-08-22T23:46:45.114283",
|
||||
"secret_store_ref": "http://localhost:9311/v1/secret-stores/4d27b7a7-b82f-491d-88c0-746bd67dadc8",
|
||||
"global_default": True,
|
||||
"crypto_plugin": "p11_crypto",
|
||||
"secret_store_plugin": "store_crypto"
|
||||
},
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2016-08-22T23:46:45.124554",
|
||||
"name": "KMIP HSM",
|
||||
"created": "2016-08-22T23:46:45.124554",
|
||||
"secret_store_ref": "http://localhost:9311/v1/secret-stores/93869b0f-60eb-4830-adb9-e2f7154a080b",
|
||||
"global_default": False,
|
||||
"crypto_plugin": None,
|
||||
"secret_store_plugin": "kmip_plugin"
|
||||
},
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2016-08-22T23:46:45.127866",
|
||||
"name": "Software Only Crypto",
|
||||
"created": "2016-08-22T23:46:45.127866",
|
||||
"secret_store_ref": "http://localhost:9311/v1/secret-stores/0da45858-9420-42fe-a269-011f5f35deaa",
|
||||
"global_default": False,
|
||||
"crypto_plugin": "simple_crypto",
|
||||
"secret_store_plugin": "store_crypto"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.. _get_secret_stores_response_attributes:
|
||||
|
||||
Response Attributes
|
||||
*******************
|
||||
|
||||
+---------------+--------+---------------------------------------------+
|
||||
| Name | Type | Description |
|
||||
+===============+========+=============================================+
|
||||
| secret_stores | list | A list of secret store references |
|
||||
+---------------+--------+---------------------------------------------+
|
||||
| name | string | store and crypto plugin name delimited by + |
|
||||
| | | (plus) sign. |
|
||||
+---------------+--------+---------------------------------------------+
|
||||
| secret_store | string | URL for referencing a specific secret store |
|
||||
| _ref | | |
|
||||
+---------------+--------+---------------------------------------------+
|
||||
|
||||
.. _get_secret_stores_status_codes:
|
||||
|
||||
HTTP Status Codes
|
||||
*****************
|
||||
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| Code | Description |
|
||||
+======+==========================================================================+
|
||||
| 200 | Successful Request |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 401 | Authentication error. Missing or invalid X-Auth-Token. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 403 | The user was authenticated, but is not authorized to perform this action |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 404 | Not Found. When multiple secret store backends support is not enabled. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
|
||||
|
||||
GET /v1/secret-stores/{secret_store_id}
|
||||
#######################################
|
||||
|
||||
A project administrator (user with admin role) can request details of secret
|
||||
store by its ID. Returned response will highlight whether this secret store is
|
||||
currently configured as global default or not.
|
||||
|
||||
.. _get_secret_stores_id_request_response:
|
||||
|
||||
Request/Response:
|
||||
*****************
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
Request:
|
||||
GET /secret-stores/93869b0f-60eb-4830-adb9-e2f7154a080b
|
||||
Headers:
|
||||
X-Auth-Token: "f9cf2d480ba3485f85bdb9d07a4959f1"
|
||||
Accept: application/json
|
||||
|
||||
Response:
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2016-08-22T23:46:45.124554",
|
||||
"name": "KMIP HSM",
|
||||
"created": "2016-08-22T23:46:45.124554",
|
||||
"secret_store_ref": "http://localhost:9311/v1/secret-stores/93869b0f-60eb-4830-adb9-e2f7154a080b",
|
||||
"global_default": False,
|
||||
"crypto_plugin": None,
|
||||
"secret_store_plugin": "kmip_plugin"
|
||||
}
|
||||
|
||||
|
||||
.. _get_secret_stores_id_response_attributes:
|
||||
|
||||
Response Attributes
|
||||
*******************
|
||||
|
||||
+------------------+---------+---------------------------------------------------------------+
|
||||
| Name | Type | Description |
|
||||
+==================+=========+===============================================================+
|
||||
| name | string | store and crypto plugin name delimited by '+' (plus) sign |
|
||||
+------------------+---------+---------------------------------------------------------------+
|
||||
| global_default | boolean | flag indicating if this secret store is global default or not |
|
||||
+------------------+---------+---------------------------------------------------------------+
|
||||
| status | list | Status of the secret store |
|
||||
+------------------+---------+---------------------------------------------------------------+
|
||||
| updated | time | Date and time secret store was last updated |
|
||||
+------------------+---------+---------------------------------------------------------------+
|
||||
| created | time | Date and time secret store was created |
|
||||
+------------------+---------+---------------------------------------------------------------+
|
||||
| secret_store_ref | string | URL for referencing a specific secret store |
|
||||
+------------------+---------+---------------------------------------------------------------+
|
||||
|
||||
|
||||
.. _get_secret_stores_id_status_codes:
|
||||
|
||||
HTTP Status Codes
|
||||
*****************
|
||||
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| Code | Description |
|
||||
+======+==========================================================================+
|
||||
| 200 | Successful Request |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 401 | Authentication error. Missing or invalid X-Auth-Token. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 403 | The user was authenticated, but is not authorized to perform this action |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 404 | Not Found. When multiple secret store backends support is not enabled or |
|
||||
| | that secret store id does not exist. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
|
||||
GET /v1/secret-stores/preferred
|
||||
###############################
|
||||
|
||||
A project administrator (user with admin role) can request a reference to the
|
||||
preferred secret store if assigned previously. When a preferred secret store is
|
||||
set for a project, then new project secrets are stored using that store
|
||||
backend. If multiple secret store support is not enabled, then this resource
|
||||
will return 404 (Not Found) error.
|
||||
|
||||
.. _get_secret_stores_preferred_request_response:
|
||||
|
||||
Request/Response:
|
||||
*****************
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
Request:
|
||||
|
||||
GET /v1/secret-stores/preferred
|
||||
Headers:
|
||||
X-Auth-Token: "f9cf2d480ba3485f85bdb9d07a4959f1"
|
||||
Accept: application/json
|
||||
|
||||
|
||||
Response:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2016-08-22T23:46:45.114283",
|
||||
"name": "PKCS11 HSM",
|
||||
"created": "2016-08-22T23:46:45.114283",
|
||||
"secret_store_ref": "http://localhost:9311/v1/secret-stores/4d27b7a7-b82f-491d-88c0-746bd67dadc8",
|
||||
"global_default": True,
|
||||
"crypto_plugin": "p11_crypto",
|
||||
"secret_store_plugin": "store_crypto"
|
||||
}
|
||||
|
||||
|
||||
.. _get_secret_stores_preferred_response_attributes:
|
||||
|
||||
Response Attributes
|
||||
*******************
|
||||
|
||||
+------------------+--------+-----------------------------------------------+
|
||||
| Name | Type | Description |
|
||||
+==================+========+===============================================+
|
||||
| secret_store_ref | string | A URL that references a specific secret store |
|
||||
+------------------+--------+-----------------------------------------------+
|
||||
|
||||
.. _get_secret_stores_preferred_status_codes:
|
||||
|
||||
HTTP Status Codes
|
||||
*****************
|
||||
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| Code | Description |
|
||||
+======+==========================================================================+
|
||||
| 200 | Successful Request |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 401 | Authentication error. Missing or invalid X-Auth-Token. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 403 | The user was authenticated, but is not authorized to perform this action |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 404 | Not found. No preferred secret store has been defined or multiple secret |
|
||||
| | store backends support is not enabled. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
|
||||
POST /v1/secret-stores/{secret_store_id}/preferred
|
||||
##################################################
|
||||
|
||||
A project administrator can set a secret store backend to be preferred store
|
||||
backend for his/her project. From there on, any new secret stored in that
|
||||
project will use specified plugin backend for storage and reading thereafter.
|
||||
Existing secret storage will not be impacted as each secret captures its plugin
|
||||
backend information when initially stored. If multiple secret store support is
|
||||
not enabled, then this resource will return 404 (Not Found) error.
|
||||
|
||||
.. _post_secret_stores_id_preferred_request_response:
|
||||
|
||||
Request/Response:
|
||||
*****************
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
Request:
|
||||
|
||||
POST /v1/secret-stores/7776adb8-e865-413c-8ccc-4f09c3fe0213/preferred
|
||||
Headers:
|
||||
X-Auth-Token: "f9cf2d480ba3485f85bdb9d07a4959f1"
|
||||
|
||||
Response:
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
|
||||
|
||||
.. _post_secret_stores_id_preferred_status_codes:
|
||||
|
||||
HTTP Status Codes
|
||||
*****************
|
||||
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| Code | Description |
|
||||
+======+==========================================================================+
|
||||
| 204 | Successful Request |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 401 | Authentication error. Missing or invalid X-Auth-Token. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 403 | The user was authenticated, but is not authorized to perform this action |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 404 | The requested entity was not found or multiple secret store backends |
|
||||
| | support is not enabled. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
|
||||
|
||||
DELETE /v1/secret-stores/{secret_store_id}/preferred
|
||||
####################################################
|
||||
|
||||
A project administrator can remove preferred secret store backend setting. If
|
||||
multiple secret store support is not enabled, then this resource will return
|
||||
404 (Not Found) error.
|
||||
|
||||
.. _delete_secret_stores_id_preferred_request_response:
|
||||
|
||||
Request/Response:
|
||||
*****************
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
Request:
|
||||
|
||||
DELETE /v1/secret-stores/7776adb8-e865-413c-8ccc-4f09c3fe0213/preferred
|
||||
Headers:
|
||||
X-Auth-Token: "f9cf2d480ba3485f85bdb9d07a4959f1"
|
||||
|
||||
Response:
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
|
||||
.. _delete_secret_stores_id_preferred_status_codes:
|
||||
|
||||
HTTP Status Codes
|
||||
*****************
|
||||
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| Code | Description |
|
||||
+======+==========================================================================+
|
||||
| 204 | Successful Request |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 401 | Authentication error. Missing or invalid X-Auth-Token. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 403 | The user was authenticated, but is not authorized to perform this action |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 404 | The requested entity was not found or multiple secret store backends |
|
||||
| | support is not enabled. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
|
||||
|
||||
GET /v1/secret-stores/global-default
|
||||
####################################
|
||||
|
||||
A project or service administrator can can request a reference to the secret
|
||||
store that is used as default secret store backend for the deployment.
|
||||
|
||||
.. _get_secret_stores_global_default_request_response:
|
||||
|
||||
Request/Response:
|
||||
*****************
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
Request:
|
||||
|
||||
GET /v1/secret-stores/global-default
|
||||
Headers:
|
||||
X-Auth-Token: "f9cf2d480ba3485f85bdb9d07a4959f1"
|
||||
Accept: application/json
|
||||
|
||||
|
||||
Response:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2016-08-22T23:46:45.114283",
|
||||
"name": "PKCS11 HSM",
|
||||
"created": "2016-08-22T23:46:45.114283",
|
||||
"secret_store_ref": "http://localhost:9311/v1/secret-stores/4d27b7a7-b82f-491d-88c0-746bd67dadc8",
|
||||
"global_default": True,
|
||||
"crypto_plugin": "p11_crypto",
|
||||
"secret_store_plugin": "store_crypto"
|
||||
}
|
||||
|
||||
|
||||
.. _get_secret_stores_global_default_response_attributes:
|
||||
|
||||
Response Attributes
|
||||
*******************
|
||||
|
||||
+------------------+--------+-----------------------------------------------+
|
||||
| Name | Type | Description |
|
||||
+==================+========+===============================================+
|
||||
| secret_store_ref | string | A URL that references a specific secret store |
|
||||
+------------------+--------+-----------------------------------------------+
|
||||
|
||||
.. _get_secret_stores_global_default_status_codes:
|
||||
|
||||
HTTP Status Codes
|
||||
*****************
|
||||
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| Code | Description |
|
||||
+======+==========================================================================+
|
||||
| 200 | Successful Request |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 401 | Authentication error. Missing or invalid X-Auth-Token. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 403 | The user was authenticated, but is not authorized to perform this action |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
| 404 | Not Found. When multiple secret store backends support is not enabled. |
|
||||
+------+--------------------------------------------------------------------------+
|
||||
|
|
@ -11,3 +11,4 @@ Setting up Barbican
|
|||
troubleshooting
|
||||
noauth
|
||||
audit
|
||||
plugin_backends
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
Using Secret Store Plugins in Barbican
|
||||
======================================
|
||||
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
By default, Barbican is configured to use one active secret store plugin in a
|
||||
deployment. This means that all of the new secrets are going to be stored via
|
||||
same plugin mechanism (i.e. same storage backend).
|
||||
|
||||
In **Newton** OpenStack release, support for configuring multiple secret store
|
||||
plugin backends is added (`Spec Link`_). As part of this change, client can
|
||||
choose to select preferred plugin backend for storing their secret at a project
|
||||
level.
|
||||
|
||||
|
||||
.. _Spec Link: https://review.openstack.org/#/c/263972
|
||||
|
||||
|
||||
Enabling Multiple Barbican Backends
|
||||
-----------------------------------
|
||||
|
||||
Multiple backends support may be needed in specific deployment/ use-case
|
||||
scenarios and can be enabled via configuration.
|
||||
|
||||
For this, a Barbican deployment may have more than one secret storage backend
|
||||
added in service configuration. Project administrators will have choice of
|
||||
pre-selecting one backend as the preferred choice for secrets created under
|
||||
that project. Any **new** secret created under that project will use the
|
||||
preferred backend to store its key material. When there is no project level
|
||||
storage backend selected, then new secret will use the global secret storage
|
||||
backend.
|
||||
|
||||
Multiple plugin configuration can be defined as follows.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[secretstore]
|
||||
# Set to True when multiple plugin backends support is needed
|
||||
enable_multiple_secret_stores = True
|
||||
stores_lookup_suffix = software, kmip, pkcs11, dogtag
|
||||
|
||||
[secretstore:software]
|
||||
secret_store_plugin = store_crypto
|
||||
crypto_plugin = simple_crypto
|
||||
|
||||
[secretstore:kmip]
|
||||
secret_store_plugin = kmip_plugin
|
||||
global_default = True
|
||||
|
||||
[secretstore:dogtag]
|
||||
secret_store_plugin = dogtag_plugin
|
||||
|
||||
[secretstore:pkcs11]
|
||||
secret_store_plugin = store_crypto
|
||||
crypto_plugin = p11_crypto
|
||||
|
||||
When `enable_multiple_secret_stores` is enabled (True), then list property
|
||||
`stores_lookup_suffix` is used for looking up supported plugin names in
|
||||
configuration section. This section name is constructed using pattern
|
||||
'secretstore:{one_of_suffix}'. One of the plugin **must** be explicitly
|
||||
identified as global default i.e. `global_default = True`. Ordering of suffix
|
||||
and label used does not matter as long as there is a matching section defined
|
||||
in service configuration.
|
||||
|
||||
.. note::
|
||||
|
||||
For existing Barbican deployment case, its recommended to keep existing
|
||||
secretstore and crypto plugin (if applicable) name combination to be used as
|
||||
global default secret store. This is needed to be consistent with existing
|
||||
behavior.
|
||||
|
||||
.. warning::
|
||||
|
||||
When multiple plugins support is enabled, then `enabled_secretstore_plugins`
|
||||
and `enabled_crypto_plugins` values are **not** used to instantiate relevant
|
||||
plugins. Only above mentioned mechanism is used to identify and instantiate
|
||||
store and crypto plugins.
|
||||
|
||||
Multiple backend can be useful in following type of usage scenarios.
|
||||
|
||||
* In a deployment, a deployer may be okay in storing their dev/test resources
|
||||
using a low-security secret store, such as one backend using software-only
|
||||
crypto, but may want to use an HSM-backed secret store for production
|
||||
resources.
|
||||
* In a deployment, for certain use cases where a client requires high
|
||||
concurrent access of stored keys, HSM might not be a good storage backend.
|
||||
Also scaling them horizontally to provide higher scalability is a costly
|
||||
approach with respect to database.
|
||||
* HSM devices generally have limited storage capacity so a deployment will
|
||||
have to watch its stored keys size proactively to remain under the limit
|
||||
constraint. This is more applicable in KMIP backend than with PKCS11 backend
|
||||
because of plugin's different storage apporach. This aspect can also result
|
||||
from above use case scenario where deployment is storing non-sensitive (from
|
||||
dev/test environment) encryption keys in HSM.
|
||||
* Barbican running as IaaS service or platform component where some class of
|
||||
client services have strict compliance requirements (e.g. FIPS) so will use
|
||||
HSM backed plugins whereas others may be okay storing keys in software-only
|
||||
crypto plugin.
|
|
@ -69,6 +69,10 @@ auditor_b_password=barbican
|
|||
# Default value is True.
|
||||
server_host_href_set = True
|
||||
|
||||
# Flag to indicate if multiple backends support is enabled or not at barbican
|
||||
# server side. Functional tests behavior changes depending on this flag value.
|
||||
server_multiple_backends_enabled = False
|
||||
|
||||
[quotas]
|
||||
# For each resource, the default maximum number that can be used for
|
||||
# a project is set below. This value can be overridden for each
|
||||
|
|
|
@ -263,6 +263,9 @@ enabled_crypto_plugins = simple_crypto
|
|||
# the kek should be a 32-byte value which is base64 encoded
|
||||
kek = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY='
|
||||
|
||||
# User friendly plugin name
|
||||
# plugin_name = 'Software Only Crypto'
|
||||
|
||||
[dogtag_plugin]
|
||||
pem_path = '/etc/barbican/kra_admin_cert.pem'
|
||||
dogtag_host = localhost
|
||||
|
@ -274,6 +277,9 @@ simple_cmc_profile = 'caOtherCert'
|
|||
ca_expiration_time = 1
|
||||
plugin_working_dir = '/etc/barbican/dogtag'
|
||||
|
||||
# User friendly plugin name
|
||||
# plugin_name = 'Dogtag KRA'
|
||||
|
||||
|
||||
[p11_crypto_plugin]
|
||||
# Path to vendor PKCS11 library
|
||||
|
@ -297,6 +303,9 @@ hmac_label = 'my_hmac_label'
|
|||
# Max number of items in pkek cache
|
||||
# pkek_cache_limit = 100
|
||||
|
||||
# User friendly plugin name
|
||||
# plugin_name = 'PKCS11 HSM'
|
||||
|
||||
|
||||
# ================== KMIP plugin =====================
|
||||
[kmip_plugin]
|
||||
|
@ -308,6 +317,9 @@ keyfile = '/path/to/certs/cert.key'
|
|||
certfile = '/path/to/certs/cert.crt'
|
||||
ca_certs = '/path/to/certs/LocalCA.crt'
|
||||
|
||||
# User friendly plugin name
|
||||
# plugin_name = 'KMIP HSM'
|
||||
|
||||
|
||||
# ================= Certificate plugin ===================
|
||||
[certificate]
|
||||
|
|
|
@ -38,10 +38,10 @@
|
|||
"order:get": "rule:all_users",
|
||||
"order:put": "rule:admin_or_creator",
|
||||
"order:delete": "rule:admin",
|
||||
"consumer:get": "rule:all_users",
|
||||
"consumers:get": "rule:all_users",
|
||||
"consumers:post": "rule:admin",
|
||||
"consumers:delete": "rule:admin",
|
||||
"consumer:get": "rule:admin or rule:observer or rule:creator or rule:audit or rule:container_non_private_read or rule:container_project_creator or rule:container_project_admin or rule:container_acl_read",
|
||||
"consumers:get": "rule:admin or rule:observer or rule:creator or rule:audit or rule:container_non_private_read or rule:container_project_creator or rule:container_project_admin or rule:container_acl_read",
|
||||
"consumers:post": "rule:admin or rule:container_non_private_read or rule:container_project_creator or rule:container_project_admin or rule:container_acl_read",
|
||||
"consumers:delete": "rule:admin or rule:container_non_private_read or rule:container_project_creator or rule:container_project_admin or rule:container_acl_read",
|
||||
"containers:post": "rule:admin_or_creator",
|
||||
"containers:get": "rule:all_but_audit",
|
||||
"container:get": "rule:container_non_private_read or rule:container_project_creator or rule:container_project_admin or rule:container_acl_read",
|
||||
|
@ -80,5 +80,11 @@
|
|||
"secret_meta:get": "rule:all_but_audit",
|
||||
"secret_meta:post": "rule:admin_or_creator",
|
||||
"secret_meta:put": "rule:admin_or_creator",
|
||||
"secret_meta:delete": "rule:admin_or_creator"
|
||||
"secret_meta:delete": "rule:admin_or_creator",
|
||||
"secretstores:get": "rule:admin",
|
||||
"secretstores:get_global_default": "rule:admin",
|
||||
"secretstores:get_preferred": "rule:admin",
|
||||
"secretstore_preferred:post": "rule:admin",
|
||||
"secretstore_preferred:delete": "rule:admin",
|
||||
"secretstore:get": "rule:admin"
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ from functionaltests.common import config
|
|||
CONF = config.get_config()
|
||||
|
||||
conf_host_href_used = CONF.keymanager.server_host_href_set
|
||||
conf_multiple_backends_enabled = CONF.keymanager.\
|
||||
server_multiple_backends_enabled
|
||||
|
||||
|
||||
class TestCase(oslotest.BaseTestCase):
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
# (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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 functionaltests.api.v1.behaviors import base_behaviors
|
||||
|
||||
|
||||
class SecretStoresBehaviors(base_behaviors.BaseBehaviors):
|
||||
|
||||
def get_all_secret_stores(self, extra_headers=None, use_auth=True,
|
||||
user_name=None):
|
||||
"""Retrieves list of secret stores available in barbican"""
|
||||
resp = self.client.get('secret-stores',
|
||||
extra_headers=extra_headers,
|
||||
use_auth=use_auth, user_name=user_name)
|
||||
json_data = None
|
||||
if resp.status_code == 200:
|
||||
json_data = self.get_json(resp)
|
||||
|
||||
return resp, json_data
|
||||
|
||||
def get_global_default(self, extra_headers=None, use_auth=True,
|
||||
user_name=None):
|
||||
"""Retrieves global default secret store."""
|
||||
|
||||
resp = self.client.get('secret-stores/global-default',
|
||||
extra_headers=extra_headers, use_auth=use_auth,
|
||||
user_name=user_name)
|
||||
json_data = None
|
||||
if resp.status_code == 200:
|
||||
json_data = self.get_json(resp)
|
||||
|
||||
return resp, json_data
|
||||
|
||||
def get_project_preferred_store(self, extra_headers=None, use_auth=True,
|
||||
user_name=None):
|
||||
"""Retrieve global default secret store."""
|
||||
|
||||
resp = self.client.get('secret-stores/preferred',
|
||||
extra_headers=extra_headers, use_auth=use_auth,
|
||||
user_name=user_name)
|
||||
json_data = None
|
||||
if resp.status_code == 200:
|
||||
json_data = self.get_json(resp)
|
||||
|
||||
return resp, json_data
|
||||
|
||||
def get_a_secret_store(self, secret_store_ref, extra_headers=None,
|
||||
use_auth=True, user_name=None):
|
||||
"""Retrieve a specific secret store."""
|
||||
|
||||
resp = self.client.get(secret_store_ref, extra_headers=extra_headers,
|
||||
use_auth=use_auth, user_name=user_name)
|
||||
json_data = None
|
||||
if resp.status_code == 200:
|
||||
json_data = self.get_json(resp)
|
||||
|
||||
return resp, json_data
|
||||
|
||||
def set_preferred_secret_store(self, secret_store_ref,
|
||||
extra_headers=None, use_auth=True,
|
||||
user_name=None):
|
||||
"""Set a preferred secret store."""
|
||||
project_id = None
|
||||
try:
|
||||
if user_name:
|
||||
project_id = self.client.get_project_id_from_name(user_name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
resp = self.client.post(secret_store_ref + '/preferred',
|
||||
extra_headers=extra_headers,
|
||||
use_auth=use_auth, user_name=user_name)
|
||||
if resp.status_code == 204 and project_id:
|
||||
# add tuple of store ref, user_name to cleanup later
|
||||
self.created_entities.append((secret_store_ref, user_name))
|
||||
|
||||
return resp
|
||||
|
||||
def unset_preferred_secret_store(self, secret_store_ref,
|
||||
extra_headers=None, use_auth=True,
|
||||
user_name=None):
|
||||
"""Unset a preferred secret store."""
|
||||
|
||||
return self.client.delete(secret_store_ref + '/preferred',
|
||||
extra_headers=extra_headers,
|
||||
use_auth=use_auth, user_name=user_name)
|
||||
|
||||
def cleanup_preferred_secret_store_entities(self):
|
||||
for (store_ref, user_name) in self.created_entities:
|
||||
self.unset_preferred_secret_store(store_ref, user_name=user_name)
|
|
@ -130,8 +130,8 @@ test_data_read_container_consumer_acl_only = {
|
|||
'with_creator_a': {'user': creator_a, 'expected_return': 200},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 200},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 200},
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 404},
|
||||
'with_observer_b': {'user': observer_b, 'expected_return': 404},
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 200},
|
||||
'with_observer_b': {'user': observer_b, 'expected_return': 200},
|
||||
}
|
||||
|
||||
test_data_delete_container_consumer_acl_only = {
|
||||
|
@ -139,7 +139,7 @@ test_data_delete_container_consumer_acl_only = {
|
|||
'with_creator_a': {'user': creator_a, 'expected_return': 403},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 403},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 403},
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 404},
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 403},
|
||||
'with_observer_b': {'user': observer_b, 'expected_return': 403},
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ test_data_create_container_consumer_acl_only = {
|
|||
'with_creator_a': {'user': creator_a, 'expected_return': 403},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 403},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 403},
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 404},
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 200},
|
||||
'with_observer_b': {'user': observer_b, 'expected_return': 403},
|
||||
}
|
||||
|
||||
|
@ -169,6 +169,7 @@ class AclTestCase(base.TestCase):
|
|||
self.acl_behaviors.delete_all_created_acls()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.consumer_behaviors.delete_all_created_consumers()
|
||||
super(AclTestCase, self).tearDown()
|
||||
|
||||
@utils.parameterized_dataset(test_data_read_secret_rbac_only)
|
||||
|
|
|
@ -112,6 +112,8 @@ class CATestCommon(base.TestCase):
|
|||
def tearDown(self):
|
||||
self.order_behaviors.delete_all_created_orders()
|
||||
self.ca_behaviors.delete_all_created_cas()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
super(CATestCommon, self).tearDown()
|
||||
|
||||
def send_test_order(self, ca_ref=None, user_name=None,
|
||||
|
|
|
@ -155,6 +155,9 @@ class CertificatesTestCase(base.TestCase):
|
|||
|
||||
def tearDown(self):
|
||||
self.behaviors.delete_all_created_orders()
|
||||
self.ca_behaviors.delete_all_created_cas()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
super(CertificatesTestCase, self).tearDown()
|
||||
|
||||
def wait_for_order(
|
||||
|
|
|
@ -74,6 +74,7 @@ class ConsumersBaseTestCase(base.TestCase):
|
|||
def tearDown(self):
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.consumer_behaviors.delete_all_created_consumers()
|
||||
super(ConsumersBaseTestCase, self).tearDown()
|
||||
|
||||
def _create_a_secret(self):
|
||||
|
|
|
@ -89,6 +89,8 @@ class OrdersTestCase(base.TestCase):
|
|||
|
||||
def tearDown(self):
|
||||
self.behaviors.delete_all_created_orders()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
super(OrdersTestCase, self).tearDown()
|
||||
|
||||
def wait_for_order(self, order_resp, order_ref):
|
||||
|
@ -654,6 +656,8 @@ class OrdersUnauthedTestCase(base.TestCase):
|
|||
|
||||
def tearDown(self):
|
||||
self.behaviors.delete_all_created_orders()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
super(OrdersUnauthedTestCase, self).tearDown()
|
||||
|
||||
@testcase.attr('negative', 'security')
|
||||
|
|
|
@ -27,7 +27,14 @@ from barbican.tests import keys
|
|||
from barbican.tests import utils
|
||||
from functionaltests.api import base
|
||||
from functionaltests.api.v1.behaviors import secret_behaviors
|
||||
from functionaltests.api.v1.behaviors import secretstores_behaviors
|
||||
from functionaltests.api.v1.models import secret_models
|
||||
from functionaltests.common import config
|
||||
|
||||
|
||||
CONF = config.get_config()
|
||||
admin_a = CONF.rbac_users.admin_a
|
||||
admin_b = CONF.rbac_users.admin_b
|
||||
|
||||
|
||||
def get_pem_content(pem):
|
||||
|
@ -1440,3 +1447,120 @@ class SecretsUnauthedTestCase(base.TestCase):
|
|||
use_auth=False
|
||||
)
|
||||
self.assertEqual(401, resp.status_code)
|
||||
|
||||
|
||||
@utils.parameterized_test_case
|
||||
class SecretsMultipleBackendTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SecretsMultipleBackendTestCase, self).setUp()
|
||||
self.behaviors = secret_behaviors.SecretBehaviors(self.client)
|
||||
self.ss_behaviors = secretstores_behaviors.SecretStoresBehaviors(
|
||||
self.client)
|
||||
self.default_secret_create_data = get_default_data()
|
||||
if base.conf_multiple_backends_enabled:
|
||||
resp, stores = self.ss_behaviors.get_all_secret_stores(
|
||||
user_name=admin_a)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
secret_store_ref = None
|
||||
for store in stores['secret-stores']:
|
||||
if not store['global_default']:
|
||||
secret_store_ref = store['secret_store_ref']
|
||||
break
|
||||
# set preferred secret store for admin_a (project a) user
|
||||
# and don't set preferred secret store for admin_b (project b) user
|
||||
self.ss_behaviors.set_preferred_secret_store(secret_store_ref,
|
||||
user_name=admin_a)
|
||||
|
||||
def tearDown(self):
|
||||
self.behaviors.delete_all_created_secrets()
|
||||
if base.conf_multiple_backends_enabled:
|
||||
self.ss_behaviors.cleanup_preferred_secret_store_entities()
|
||||
super(SecretsMultipleBackendTestCase, self).tearDown()
|
||||
|
||||
@testcase.skipUnless(base.conf_multiple_backends_enabled, 'executed only '
|
||||
'when multiple backends support is enabled in '
|
||||
'barbican server side')
|
||||
@utils.parameterized_dataset({
|
||||
'symmetric_type_preferred_store': [
|
||||
admin_a,
|
||||
'symmetric',
|
||||
base64.b64decode(get_default_payload()),
|
||||
get_default_data()
|
||||
],
|
||||
'private_type_preferred_store': [
|
||||
admin_a,
|
||||
'private',
|
||||
keys.get_private_key_pem(),
|
||||
get_private_key_req()
|
||||
],
|
||||
'public_type_preferred_store': [
|
||||
admin_a,
|
||||
'public',
|
||||
keys.get_public_key_pem(),
|
||||
get_public_key_req()
|
||||
],
|
||||
'certificate_type_preferred_store': [
|
||||
admin_a,
|
||||
'certificate',
|
||||
keys.get_certificate_pem(),
|
||||
get_certificate_req()
|
||||
],
|
||||
'passphrase_type_preferred_store': [
|
||||
admin_a,
|
||||
'passphrase',
|
||||
'mysecretpassphrase',
|
||||
get_passphrase_req()
|
||||
],
|
||||
'symmetric_type_no_preferred_store': [
|
||||
admin_b,
|
||||
'symmetric',
|
||||
base64.b64decode(get_default_payload()),
|
||||
get_default_data()
|
||||
],
|
||||
'private_type_no_preferred_store': [
|
||||
admin_b,
|
||||
'private',
|
||||
keys.get_private_key_pem(),
|
||||
get_private_key_req()
|
||||
],
|
||||
'public_type_no_preferred_store': [
|
||||
admin_b,
|
||||
'public',
|
||||
keys.get_public_key_pem(),
|
||||
get_public_key_req()
|
||||
],
|
||||
'certificate_type_no_preferred_store': [
|
||||
admin_b,
|
||||
'certificate',
|
||||
keys.get_certificate_pem(),
|
||||
get_certificate_req()
|
||||
],
|
||||
'passphrase_type_no_preferred_store': [
|
||||
admin_b,
|
||||
'passphrase',
|
||||
'mysecretpassphrase',
|
||||
get_passphrase_req()
|
||||
],
|
||||
})
|
||||
def test_secret_create_for(self, user_name, secret_type, expected, spec):
|
||||
"""Create secrets with various secret types with multiple backends."""
|
||||
test_model = secret_models.SecretModel(**spec)
|
||||
test_model.secret_type = secret_type
|
||||
|
||||
resp, secret_ref = self.behaviors.create_secret(test_model,
|
||||
user_name=user_name,
|
||||
admin=user_name)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
resp = self.behaviors.get_secret_metadata(secret_ref,
|
||||
user_name=user_name)
|
||||
secret_type_response = resp.model.secret_type
|
||||
self.assertIsNotNone(secret_type_response)
|
||||
self.assertEqual(secret_type, secret_type_response)
|
||||
|
||||
content_type = spec['payload_content_type']
|
||||
get_resp = self.behaviors.get_secret(secret_ref,
|
||||
content_type,
|
||||
user_name=user_name)
|
||||
self.assertEqual(expected, get_resp.content)
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
# (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
#
|
||||
# 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 testtools import testcase
|
||||
|
||||
from barbican.tests import utils
|
||||
from functionaltests.api import base
|
||||
from functionaltests.api.v1.behaviors import secret_behaviors
|
||||
from functionaltests.api.v1.behaviors import secretstores_behaviors
|
||||
from functionaltests.common import config
|
||||
|
||||
CONF = config.get_config()
|
||||
admin_a = CONF.rbac_users.admin_a
|
||||
creator_a = CONF.rbac_users.creator_a
|
||||
observer_a = CONF.rbac_users.observer_a
|
||||
auditor_a = CONF.rbac_users.auditor_a
|
||||
admin_b = CONF.rbac_users.admin_b
|
||||
observer_b = CONF.rbac_users.observer_b
|
||||
|
||||
test_user_data_when_enabled = {
|
||||
'with_admin_a': {'user': admin_a, 'expected_return': 200},
|
||||
'with_creator_a': {'user': creator_a, 'expected_return': 403},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 403},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 403},
|
||||
}
|
||||
|
||||
test_user_data_when_not_enabled = {
|
||||
'with_admin_a': {'user': admin_a, 'expected_return': 404},
|
||||
'with_creator_a': {'user': creator_a, 'expected_return': 403},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 403},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 403},
|
||||
}
|
||||
|
||||
|
||||
@utils.parameterized_test_case
|
||||
class SecretStoresTestCase(base.TestCase):
|
||||
"""Functional tests exercising ACL Features"""
|
||||
def setUp(self):
|
||||
super(SecretStoresTestCase, self).setUp()
|
||||
self.secret_behaviors = secret_behaviors.SecretBehaviors(self.client)
|
||||
self.ss_behaviors = secretstores_behaviors.SecretStoresBehaviors(
|
||||
self.client)
|
||||
|
||||
def tearDown(self):
|
||||
self.ss_behaviors.cleanup_preferred_secret_store_entities()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
super(SecretStoresTestCase, self).tearDown()
|
||||
|
||||
def _validate_secret_store_fields(self, secret_store):
|
||||
self.assertIsNotNone(secret_store['name'])
|
||||
self.assertIsNotNone(secret_store['secret_store_ref'])
|
||||
self.assertIsNotNone(secret_store['secret_store_plugin'])
|
||||
self.assertIsNotNone(secret_store['global_default'])
|
||||
self.assertIsNotNone(secret_store['created'])
|
||||
self.assertIsNotNone(secret_store['updated'])
|
||||
self.assertEqual("ACTIVE", secret_store['status'])
|
||||
|
||||
@testcase.skipUnless(base.conf_multiple_backends_enabled, 'executed only '
|
||||
'when multiple backends support is enabled in '
|
||||
'barbican server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_enabled)
|
||||
def test_get_all_secret_stores_multiple_enabled(self, user,
|
||||
expected_return):
|
||||
|
||||
resp, json_data = self.ss_behaviors.get_all_secret_stores(
|
||||
user_name=user)
|
||||
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
if expected_return == 200:
|
||||
self.assertIsNotNone(json_data['secret-stores'])
|
||||
stores = json_data['secret-stores']
|
||||
for secret_store in stores:
|
||||
self._validate_secret_store_fields(secret_store)
|
||||
|
||||
@testcase.skipIf(base.conf_multiple_backends_enabled, 'executed only when '
|
||||
'multiple backends support is NOT enabled in barbican '
|
||||
'server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_not_enabled)
|
||||
def test_get_all_secret_stores_multiple_disabled(self, user,
|
||||
expected_return):
|
||||
|
||||
resp, _ = self.ss_behaviors.get_all_secret_stores(user_name=user)
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
|
||||
@testcase.skipUnless(base.conf_multiple_backends_enabled, 'executed only '
|
||||
'when multiple backends support is enabled in '
|
||||
'barbican server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_enabled)
|
||||
def test_get_global_default_multiple_enabled(self, user, expected_return):
|
||||
|
||||
resp, json_data = self.ss_behaviors.get_global_default(user_name=user)
|
||||
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
if expected_return == 200:
|
||||
self._validate_secret_store_fields(json_data)
|
||||
|
||||
@testcase.skipIf(base.conf_multiple_backends_enabled, 'executed only when '
|
||||
'multiple backends support is NOT enabled in barbican '
|
||||
'server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_not_enabled)
|
||||
def test_get_global_default_multiple_disabled(self, user, expected_return):
|
||||
|
||||
resp, _ = self.ss_behaviors.get_global_default(user_name=user)
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
|
||||
@testcase.skipUnless(base.conf_multiple_backends_enabled, 'executed only '
|
||||
'when multiple backends support is enabled in '
|
||||
'barbican server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_enabled)
|
||||
def test_get_project_preferred_multiple_enabled(self, user,
|
||||
expected_return):
|
||||
|
||||
resp, json_data = self.ss_behaviors.get_all_secret_stores(
|
||||
user_name=admin_a)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
stores = json_data['secret-stores']
|
||||
|
||||
store = stores[len(stores)-1]
|
||||
secret_store_ref = store['secret_store_ref']
|
||||
resp = self.ss_behaviors.set_preferred_secret_store(secret_store_ref,
|
||||
user_name=user)
|
||||
|
||||
if resp.status_code == 204:
|
||||
resp, json_data = self.ss_behaviors.get_project_preferred_store(
|
||||
user_name=user)
|
||||
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
if expected_return == 200:
|
||||
self._validate_secret_store_fields(json_data)
|
||||
self.assertEqual(store['secret_store_ref'],
|
||||
json_data['secret_store_ref'])
|
||||
|
||||
@testcase.skipIf(base.conf_multiple_backends_enabled, 'executed only when '
|
||||
'multiple backends support is NOT enabled in barbican '
|
||||
'server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_not_enabled)
|
||||
def test_get_project_preferred_multiple_disabled(self, user,
|
||||
expected_return):
|
||||
|
||||
resp, _ = self.ss_behaviors.get_project_preferred_store(user_name=user)
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
|
||||
@testcase.skipUnless(base.conf_multiple_backends_enabled, 'executed only '
|
||||
'when multiple backends support is enabled in '
|
||||
'barbican server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_enabled)
|
||||
def test_get_a_secret_store_multiple_enabled(self, user, expected_return):
|
||||
# read global default secret store via admin user as this is not a
|
||||
# global default API check.
|
||||
resp, json_data = self.ss_behaviors.get_global_default(
|
||||
user_name=admin_a)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
resp, json_data = self.ss_behaviors.get_a_secret_store(
|
||||
json_data['secret_store_ref'], user_name=user)
|
||||
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
if expected_return == 200:
|
||||
self._validate_secret_store_fields(json_data)
|
||||
self.assertEqual(True, json_data['global_default'])
|
||||
|
||||
@testcase.skipIf(base.conf_multiple_backends_enabled, 'executed only when '
|
||||
'multiple backends support is NOT enabled in barbican '
|
||||
'server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_not_enabled)
|
||||
def test_get_a_secret_store_multiple_disabled(self, user, expected_return):
|
||||
|
||||
resp, _ = self.ss_behaviors.get_project_preferred_store(user_name=user)
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
|
||||
@testcase.skipUnless(base.conf_multiple_backends_enabled, 'executed only '
|
||||
'when multiple backends support is enabled in '
|
||||
'barbican server side')
|
||||
@utils.parameterized_dataset(test_user_data_when_enabled)
|
||||
def test_unset_project_preferred_store_multiple_enabled(self, user,
|
||||
expected_return):
|
||||
|
||||
resp, json_data = self.ss_behaviors.get_all_secret_stores(
|
||||
user_name=admin_a)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
stores = json_data['secret-stores']
|
||||
|
||||
store = stores[len(stores)-1]
|
||||
secret_store_ref = store['secret_store_ref']
|
||||
resp = self.ss_behaviors.set_preferred_secret_store(secret_store_ref,
|
||||
user_name=user)
|
||||
|
||||
if resp.status_code == 204:
|
||||
# after setting project preference, get preferred will return 200
|
||||
resp, json_data = self.ss_behaviors.get_project_preferred_store(
|
||||
user_name=user)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# now, remove project preferred secret store
|
||||
self.ss_behaviors.unset_preferred_secret_store(
|
||||
json_data['secret_store_ref'], user_name=user)
|
||||
# get project preferred call should now return 404
|
||||
resp, json_data = self.ss_behaviors.get_project_preferred_store(
|
||||
user_name=user)
|
||||
self.assertEqual(404, resp.status_code)
|
|
@ -13,10 +13,10 @@ 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 functionaltests.api.v1.models.base_models import BaseModel
|
||||
from functionaltests.api.v1.models import base_models
|
||||
|
||||
|
||||
class AclModel(BaseModel):
|
||||
class AclModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, acl_ref=None, read=None):
|
||||
super(AclModel, self).__init__()
|
||||
|
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
"""
|
||||
|
||||
from functionaltests.api.v1.models.base_models import BaseModel
|
||||
from functionaltests.api.v1.models import base_models
|
||||
|
||||
|
||||
class CAModel(BaseModel):
|
||||
class CAModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, expiration=None, ca_id=None, ca_ref=None,
|
||||
status=None, updated=None, created=None, plugin_name=None,
|
||||
|
|
|
@ -13,10 +13,10 @@ 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 functionaltests.api.v1.models.base_models import BaseModel
|
||||
from functionaltests.api.v1.models import base_models
|
||||
|
||||
|
||||
class ConsumerModel(BaseModel):
|
||||
class ConsumerModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, name=None, URL=None, created=None, updated=None,
|
||||
status=None):
|
||||
|
|
|
@ -13,17 +13,17 @@ 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 functionaltests.api.v1.models.base_models import BaseModel
|
||||
from functionaltests.api.v1.models import base_models
|
||||
|
||||
|
||||
class SecretRefModel(BaseModel):
|
||||
class SecretRefModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, name=None, secret_ref=None):
|
||||
self.name = name
|
||||
self.secret_ref = secret_ref
|
||||
|
||||
|
||||
class ContainerModel(BaseModel):
|
||||
class ContainerModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, name=None, type=None, secret_refs=[],
|
||||
container_ref=None, consumers=None, status=None,
|
||||
|
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
"""
|
||||
|
||||
from functionaltests.api.v1.models.base_models import BaseModel
|
||||
from functionaltests.api.v1.models import base_models
|
||||
|
||||
|
||||
class QuotasModel(BaseModel):
|
||||
class QuotasModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, secrets=None, orders=None, containers=None,
|
||||
consumers=None, cas=None):
|
||||
|
@ -29,7 +29,7 @@ class QuotasModel(BaseModel):
|
|||
self.cas = cas
|
||||
|
||||
|
||||
class QuotasResponseModel(BaseModel):
|
||||
class QuotasResponseModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, quotas=None):
|
||||
super(QuotasResponseModel, self).__init__()
|
||||
|
@ -41,7 +41,7 @@ class QuotasResponseModel(BaseModel):
|
|||
return cls(quotas=quotas)
|
||||
|
||||
|
||||
class ProjectQuotaRequestModel(BaseModel):
|
||||
class ProjectQuotaRequestModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, project_quotas=None):
|
||||
super(ProjectQuotaRequestModel, self).__init__()
|
||||
|
@ -53,14 +53,14 @@ class ProjectQuotaRequestModel(BaseModel):
|
|||
return cls(project_quotas=project_quotas)
|
||||
|
||||
|
||||
class ProjectQuotaOneModel(BaseModel):
|
||||
class ProjectQuotaOneModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, project_quotas=None):
|
||||
super(ProjectQuotaOneModel, self).__init__()
|
||||
self.project_quotas = QuotasModel(**project_quotas)
|
||||
|
||||
|
||||
class ProjectQuotaListItemModel(BaseModel):
|
||||
class ProjectQuotaListItemModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, project_id=None, project_quotas=None):
|
||||
super(ProjectQuotaListItemModel, self).__init__()
|
||||
|
@ -68,7 +68,7 @@ class ProjectQuotaListItemModel(BaseModel):
|
|||
self.project_quotas = QuotasModel(**project_quotas)
|
||||
|
||||
|
||||
class ProjectQuotaListModel(BaseModel):
|
||||
class ProjectQuotaListModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, project_quotas=None):
|
||||
super(ProjectQuotaListModel, self).__init__()
|
||||
|
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
"""
|
||||
|
||||
from functionaltests.api.v1.models.base_models import BaseModel
|
||||
from functionaltests.api.v1.models import base_models
|
||||
|
||||
|
||||
class SecretModel(BaseModel):
|
||||
class SecretModel(base_models.BaseModel):
|
||||
|
||||
def __init__(self, name=None, expiration=None, algorithm=None,
|
||||
secret_ref=None, bit_length=None, mode=None, secret_type=None,
|
||||
|
|
|
@ -98,6 +98,7 @@ class ConsumersTestCase(base.TestCase):
|
|||
def tearDown(self):
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.consumer_behaviors.delete_all_created_consumers()
|
||||
super(ConsumersTestCase, self).tearDown()
|
||||
|
||||
@testcase.attr('positive')
|
||||
|
|
|
@ -76,7 +76,8 @@ def setup_config(config_file=''):
|
|||
cfg.StrOpt('override_url', default=''),
|
||||
cfg.StrOpt('override_url_version', default=''),
|
||||
cfg.BoolOpt('verify_ssl', default=True),
|
||||
cfg.BoolOpt('server_host_href_set', default=True)
|
||||
cfg.BoolOpt('server_host_href_set', default=True),
|
||||
cfg.BoolOpt('server_multiple_backends_enabled', default=False)
|
||||
]
|
||||
TEST_CONF.register_group(keymanager_group)
|
||||
TEST_CONF.register_opts(keymanager_options, group=keymanager_group)
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
# This script is executed inside post_test_hook function in devstack gate.
|
||||
|
||||
# Install packages from test-requirements.txt
|
||||
|
||||
set -ex
|
||||
|
||||
sudo pip install -r /opt/stack/new/barbican/test-requirements.txt
|
||||
|
||||
cd /opt/stack/new/barbican/functionaltests
|
||||
|
|
|
@ -10,7 +10,9 @@ Key Manager service
|
|||
verify.rst
|
||||
next-steps.rst
|
||||
|
||||
The Key Manager service (barbican) provides...
|
||||
The Key Manager service (barbican) provides secure storage, provisioning and
|
||||
management of secret data. This includes keying material such as symmetric
|
||||
keys, asymmetric keys, certificates and raw binary data.
|
||||
|
||||
This chapter assumes a working setup of OpenStack following the
|
||||
`OpenStack Installation Tutorial <http://docs.openstack.org/#install-guides>`_.
|
||||
|
|
|
@ -3,22 +3,71 @@
|
|||
Verify operation
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Verify operation of the Key Manager service.
|
||||
Verify operation of the Key Manager (barbican) service.
|
||||
|
||||
.. note::
|
||||
|
||||
Perform these commands on the controller node.
|
||||
|
||||
#. Source the ``admin`` project credentials to gain access to
|
||||
admin-only CLI commands:
|
||||
#. Source the ``admin`` credentials to be able to perform Barbican
|
||||
API calls:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ . admin-openrc
|
||||
|
||||
#. List service components to verify successful launch and registration
|
||||
of each process:
|
||||
#. Use the OpenStack CLI to store a secret:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack key manager service list
|
||||
$ openstack secret store --name mysecret --payload j4=]d21
|
||||
+---------------+-----------------------------------------------------------------------+
|
||||
| Field | Value |
|
||||
+---------------+-----------------------------------------------------------------------+
|
||||
| Secret href | http://10.0.2.15:9311/v1/secrets/655d7d30-c11a-49d9-a0f1-34cdf53a36fa |
|
||||
| Name | mysecret |
|
||||
| Created | None |
|
||||
| Status | None |
|
||||
| Content types | None |
|
||||
| Algorithm | aes |
|
||||
| Bit length | 256 |
|
||||
| Secret type | opaque |
|
||||
| Mode | cbc |
|
||||
| Expiration | None |
|
||||
+---------------+-----------------------------------------------------------------------+
|
||||
|
||||
#. Confirm that the secret was stored by retrieving it:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack secret get http://10.0.2.15:9311/v1/secrets/655d7d30-c11a-49d9-a0f1-34cdf53a36fa
|
||||
+---------------+-----------------------------------------------------------------------+
|
||||
| Field | Value |
|
||||
+---------------+-----------------------------------------------------------------------+
|
||||
| Secret href | http://10.0.2.15:9311/v1/secrets/655d7d30-c11a-49d9-a0f1-34cdf53a36fa |
|
||||
| Name | mysecret |
|
||||
| Created | 2016-08-16 16:04:10+00:00 |
|
||||
| Status | ACTIVE |
|
||||
| Content types | {u'default': u'application/octet-stream'} |
|
||||
| Algorithm | aes |
|
||||
| Bit length | 256 |
|
||||
| Secret type | opaque |
|
||||
| Mode | cbc |
|
||||
| Expiration | None |
|
||||
+---------------+-----------------------------------------------------------------------+
|
||||
|
||||
.. note::
|
||||
|
||||
Some items are populated after the secret has been created and will only
|
||||
display when retrieving it.
|
||||
|
||||
#. Confirm that the secret payload was stored by retrieving it:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack secret get http://10.0.2.15:9311/v1/secrets/655d7d30-c11a-49d9-a0f1-34cdf53a36fa --payload
|
||||
+---------+---------+
|
||||
| Field | Value |
|
||||
+---------+---------+
|
||||
| Payload | j4=]d21 |
|
||||
+---------+---------+
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
prelude: >
|
||||
Now within a single deployment, multiple secret store plugin backends can
|
||||
be configured and used. With this change, a project adminstrator can
|
||||
pre-define a preferred plugin backend for storing their secrets. New APIs
|
||||
are added to manage this project level secret store preference.
|
||||
features:
|
||||
- New feature to support multiple secret store plugin backends. This feature
|
||||
is not enabled by default. To use this feature, the relevant feature flag
|
||||
needs to be enabled and supporting configuration needs to be added in the
|
||||
service configuration. Once enabled, a project adminstrator will be able
|
||||
to specify one of the available secret store backends as a preferred
|
||||
secret store for their project secrets. This secret store preference
|
||||
applies only to new secrets (key material) created or stored within that
|
||||
project. Existing secrets are not impacted. See http://docs.openstack.org/developer/barbican/setup/plugin_backends.html
|
||||
for instructions on how to setup Barbican multiple backends, and the API
|
||||
documentation for further details.
|
|
@ -7,6 +7,6 @@ Contents:
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
liberty
|
||||
unreleased
|
||||
mitaka
|
||||
liberty
|
||||
|
|
3
tox.ini
3
tox.ini
|
@ -1,8 +1,9 @@
|
|||
[tox]
|
||||
minversion = 2.0
|
||||
envlist = pep8,py35,py34,py27,docs
|
||||
|
||||
[testenv]
|
||||
install_command = pip install -U {opts} {packages}
|
||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -U {opts} {packages}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
|
||||
|
|
Loading…
Reference in New Issue