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:
Thomas Goirand 2016-09-21 23:15:40 +02:00
commit e9995b8a6c
70 changed files with 4183 additions and 147 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}'"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
debian/changelog vendored
View File

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

View File

@ -17,6 +17,7 @@ API Reference
./reference/secrets
./reference/secret_types
./reference/secret_metadata
./reference/store_backends.rst
./reference/containers
./reference/acls
./reference/certificates

View File

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

View File

@ -11,3 +11,4 @@ Setting up Barbican
troubleshooting
noauth
audit
plugin_backends

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,6 @@ Contents:
.. toctree::
:maxdepth: 1
liberty
unreleased
mitaka
liberty

View File

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