diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Makefile b/Makefile index bc3343e..8156b37 100644 --- a/Makefile +++ b/Makefile @@ -12,4 +12,4 @@ lint: test: @echo Starting unit tests... - @tox -e py27,py34,py35 + @tox -e py34,py35 diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd223a9 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# Barbican Source Charm + +This repository is for the reactive, layered, +[Barbican](https://wiki.openstack.org/wiki/Barbican) _source_ charm. From the +[wiki](https://wiki.openstack.org/wiki/Barbican) 'Barbican is a REST API +designed for the secure storage, provisioning and management of secrets such as +passwords, encryption keys and X.509 Certificates. It is aimed at being useful +for all environments, including large ephemeral Clouds.' + +# Plugins + +The Barbican charm currently supports the following plugins: + + - [charm-barbican-softhsm-plugin](https://github.com/openstack/charm-barbican-softhsm-plugin) + +# Creating the primary MKEK and primary HMAC + +Barbican (can use|uses) a Master Key Encryption Key (MKEK) scheme to wrap other +keys so that in the course of issuing new encryption keys, it doesn't exhaust +the storage capacity of an HSM. + +See [KMIP MKEK Model +Plugin](https://specs.openstack.org/openstack/barbican-specs/specs/kilo/barbican-mkek-model.html) +for more details. + +Barbican itself can generate the MKEK and HMAC keys and store them in the +associated HSM through the use of two actions 'generate-mkek' and +'generate-hmac'. + +The names of the keys are stored in the configuration for the service as +'mkek-label' and 'hmac-label'. These default to 'primarymkek' and +'primaryhmac' respectively. + +Note that these keys are not recoverable _from_ the HSM. If the HSM has +already been configured with these keys then these actions would overwrite the +existing key. So only use them for the initial implementation or to change the +MKEK and HMAC keys in the HSM. + +## Use of actions + +For juju 1.x: +```bash +juju action do generate-mkek +``` + +For juju 2.x: + +```bash +juju run-action generate-mkek +``` + +Note that, depending on the HSM, it may only be possible to do this ONCE as the +HSM may reject setting up the keys more than once. + +# Developer Notes + +The Barbican charm has to be able to set `[crypto]` and `[xxx_plugin]` sections +in the `barbican-api.conf` file. This data comes via the `barbican-hsm-plugin` +interface from a charm (probably a subordinate) that provides the interface. + +On the `barbican-hsm-interface` the data is provided in the `plugin_data()` +method of the interface (or if it is adapted) in the `plugin_data` property. + +The theory of operation for the crypto plugin is that a local library that +supports the PKCS#11 interface that Barbican can talk to locally. + +Note(AJK): it is not clear yet how a clustered Barbican can be created with +a single HSM backend. It's likely to be a separate piece of hardward with +a local library that talks to it. + +In order for Barbican to be configured for the example softhsm2 library, the +configuration file needs to include the entries: + +```ini +[crypto] +enabled_crypto_plugins = p11_crypto + +[p11_crypto_plugin] +library_path = '/usr/lib/libCryptoki2_64.so' +login = 'catt' +mkek_label = 'primarymkek' +mkek_length = 32 +hmac_label = 'primaryhmac' slot_id = +``` + +Note that the /var/lib/softhsm/tokens directory HAS to exist as otherwise the +softhsm2-util command won't work. diff --git a/copyright b/copyright deleted file mode 100644 index c9349ff..0000000 --- a/copyright +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2016 Canonical Ltd. - -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. diff --git a/src/actions.yaml b/src/actions.yaml new file mode 100644 index 0000000..32881ea --- /dev/null +++ b/src/actions.yaml @@ -0,0 +1,8 @@ +generate-mkek: + description: | + Generate an MKEK in the associated HSM (via the barbican-hsm-plugin + interface). +generate-hmac: + description: | + Generate an HMAC in the associated HSM (via the barbican-hsm-plugin + interface). diff --git a/src/actions/actions.py b/src/actions/actions.py new file mode 100755 index 0000000..fc390e8 --- /dev/null +++ b/src/actions/actions.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright 2016 Canonical Ltd +# +# 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 os +import sys + +# Load modules from $CHARM_DIR/lib +sys.path.append('lib') + +from charms.layer import basic +basic.bootstrap_charm_deps() +basic.init_config_states() + +import charms.reactive as reactive + +import charmhelpers.core.hookenv as hookenv + +import charm.openstack.barbican as barbican + + +def generate_mkek_action(*args): + """Generate an MKEK in the backend HSM""" + # try and get the reactive relation instance for hsm: + # We only do this because there's no @action() yet that we could + # access from the reactive file. + hsm = reactive.RelationBase.from_state('hsm.available') + if hsm is None: + hookenv.action_fail( + "Can't generate an MKEK in associated HSM because HSM is not " + "available.") + return + barbican.generate_mkek(hsm) + + +def generate_hmac_action(*args): + """Generate an HMAC in the backend HSM""" + # try and get the reactive relation instance for hsm: + # We only do this because there's no @action() yet that we could + # access from the reactive file. + hsm = reactive.RelationBase.from_state('hsm.available') + if hsm is None: + hookenv.action_fail( + "Can't generate an HMAC in associated HSM because HSM is not " + "available.") + barbican.generate_hmac(hsm) + + +# Actions to function mapping, to allow for illegal python action names that +# can map to a python function. +ACTIONS = { + "generate-mkek": generate_mkek_action, + "generate-hmac": generate_hmac_action, +} + + +def main(args): + action_name = os.path.basename(args[0]) + try: + action = ACTIONS[action_name] + except KeyError: + return "Action %s undefined" % action_name + else: + try: + action(args) + except Exception as e: + hookenv.action_fail(str(e)) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/src/actions/generate-hmac b/src/actions/generate-hmac new file mode 120000 index 0000000..405a394 --- /dev/null +++ b/src/actions/generate-hmac @@ -0,0 +1 @@ +actions.py \ No newline at end of file diff --git a/src/actions/generate-mkek b/src/actions/generate-mkek new file mode 120000 index 0000000..405a394 --- /dev/null +++ b/src/actions/generate-mkek @@ -0,0 +1 @@ +actions.py \ No newline at end of file diff --git a/src/config.yaml b/src/config.yaml index 1cdafbb..1bec251 100644 --- a/src/config.yaml +++ b/src/config.yaml @@ -46,3 +46,38 @@ options: default: "3" type: string description: none, 2 or 3 + require-hsm-plugin: + default: True + type: boolean + description: | + If True (the default) then the barbcian-worker process won't be fully + functional until an HSM is associated with the charm. The charm will + remain in the blocked state until an HSM is available. + label-mkek: + default: primarymkek + type: string + description: | + This is the label for the primary MKEK (Master Key Encryption Key) stored + in the HSM that is used by Barbican to wrap other encryption keys that + are provided to projects. + + Note the assocated action 'generate-mkek' is used to create an MKEK when + initialising a system. + mkek-key-length: + default: 32 + type: int + description: The length for generating an MKEK + label-hmac: + default: primaryhmac + type: string + description: | + This is the label for the primary HMAC (keyed-hash message authentication + code) stored in the HSM that is used by Barbican to wrap other HMACs that + are provided to projects. + + Note the assocated action 'generate-hmac' is used to create an HMAC when + initialising a system. + hmac-key-length: + default: 32 + type: int + description: The length for generating an HMAC diff --git a/src/hooks/install b/src/hooks/install index 184c50e..b0771bf 100755 --- a/src/hooks/install +++ b/src/hooks/install @@ -10,19 +10,8 @@ check_and_install() { } status-set maintenance "Install hook running" -if [[ $(lsb_release -sc) -eq "trusty" ]]; then - juju-log "Enabling cloud archive to work around old trusty tools" - # Add a random cloud archive for the Openstack python3 clients - add-apt-repository --yes ppa:ubuntu-cloud-archive/mitaka-staging - apt-get update - check_and_install 'python3-pip' - # The trusty version of tox is too low (tox version is 1.6, required is at least 2.3.1) - # pip install tox to get around this and die a little inside - pip3 install tox -else - juju-log "Installing tox" - check_and_install 'tox' -fi +juju-log "Installing tox" +check_and_install 'tox' declare -a DEPS=('libssl-dev' 'libffi-dev' 'apt' 'python3-netaddr' 'python3-netifaces' 'python3-pip' 'python3-yaml' 'python-cinderclient' 'python-glanceclient' 'python-heatclient' 'python-keystoneclient' 'python-neutronclient' 'python-novaclient' 'python-swiftclient' 'python-ceilometerclient' 'openvswitch-test' 'python3-cinderclient' 'python3-glanceclient' 'python3-heatclient' 'python3-keystoneclient' 'python3-neutronclient' 'python3-novaclient' 'python3-swiftclient' 'python3-ceilometerclient') diff --git a/src/layer.yaml b/src/layer.yaml index 437453a..2b5c10f 100644 --- a/src/layer.yaml +++ b/src/layer.yaml @@ -1 +1,7 @@ -includes: ['layer:openstack', 'interface:mysql-shared', 'interface:rabbitmq', 'interface:keystone'] +includes: + - layer:openstack + - interface:mysql-shared + - interface:rabbitmq + - interface:keystone + - interface:barbican-hsm-plugin +repo: git@github.com:openstack-charmers/charm-barbican.git diff --git a/src/lib/__init__.py b/src/lib/__init__.py index e69de29..9b088de 100644 --- a/src/lib/__init__.py +++ b/src/lib/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016 Canonical Ltd +# +# 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. diff --git a/src/lib/charm/__init__.py b/src/lib/charm/__init__.py index e69de29..9b088de 100644 --- a/src/lib/charm/__init__.py +++ b/src/lib/charm/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016 Canonical Ltd +# +# 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. diff --git a/src/lib/charm/openstack/__init__.py b/src/lib/charm/openstack/__init__.py index e69de29..9b088de 100644 --- a/src/lib/charm/openstack/__init__.py +++ b/src/lib/charm/openstack/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016 Canonical Ltd +# +# 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. diff --git a/src/lib/charm/openstack/barbican.py b/src/lib/charm/openstack/barbican.py index 9828bb8..171c119 100644 --- a/src/lib/charm/openstack/barbican.py +++ b/src/lib/charm/openstack/barbican.py @@ -1,11 +1,28 @@ +# Copyright 2016 Canonical Ltd +# +# 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. # The barbican handlers class # bare functions are provided to the reactive handlers to perform the functions # needed on the class. from __future__ import absolute_import -import charmhelpers.fetch +import subprocess + import charmhelpers.contrib.openstack.utils as ch_utils +import charmhelpers.core.hookenv as hookenv +import charmhelpers.core.unitdata as unitdata +import charmhelpers.fetch import charms_openstack.charm import charms_openstack.adapters @@ -13,10 +30,11 @@ import charms_openstack.ip as os_ip PACKAGES = ['barbican-common', 'barbican-api', 'barbican-worker', 'python-mysqldb'] -BARBICAN_DIR = '/etc/barbican' -BARBICAN_ADMIN_PASTE_CONF = "barbican-admin-paste.ini" -BARBICAN_API_CONF = "barbican-api.conf" -BARBICAN_API_PASTE_CONF = "barbican-api-paste.ini" +BARBICAN_DIR = '/etc/barbican/' +BARBICAN_CONF = BARBICAN_DIR + "barbican.conf" +BARBICAN_API_PASTE_CONF = BARBICAN_DIR + "barbican-api-paste.ini" + +OPENSTACK_RELEASE_KEY = 'barbican-charm.openstack-release-version' ### @@ -26,12 +44,15 @@ def install(): """Use the singleton from the BarbicanCharm to install the packages on the unit """ + unitdata.kv().unset(OPENSTACK_RELEASE_KEY) BarbicanCharm.singleton.install() def setup_endpoint(keystone): """When the keystone interface connects, register this unit in the keystone catalogue. + + :param keystone: instance of KeystoneRequires() class from i/f """ charm = BarbicanCharm.singleton keystone.register_endpoints(charm.service_type, @@ -44,10 +65,26 @@ def setup_endpoint(keystone): def render_configs(interfaces_list): """Using a list of interfaces, render the configs and, if they have changes, restart the services on the unit. + + :param interfaces_list: [RelationBase] interfaces from reactive """ BarbicanCharm.singleton.render_with_interfaces(interfaces_list) +def generate_mkek(hsm): + """Ask barbican to generate an MKEK in the backend store using the HSM. + This assumes that an HSM is available, and configured. Uses the charm. + """ + BarbicanCharm.singleton.action_generate_mkek(hsm) + + +def generate_hmac(hsm): + """Ask barbican to generate an HMAC in the backend store using the HSM. + This assumes that an HSM is available, and configured. Uses the charm. + """ + BarbicanCharm.singleton.action_generate_hmac(hsm) + + def assess_status(): """Just call the BarbicanCharm.singleton.assess_status() command to update status on the unit. @@ -71,18 +108,59 @@ class BarbicanConfigurationAdapter( @property def barbican_api_keystone_pipeline(self): if self.keystone_api_version == "2": - return 'keystone_authtoken context apiapp' + return 'cors keystone_authtoken context apiapp' else: - return 'keystone_v3_authtoken context apiapp' + return 'cors keystone_v3_authtoken context apiapp' @property def barbican_api_pipeline(self): return { - "2": "keystone_authtoken context apiapp", - "3": "keystone_v3_authtoken context apiapp", - "none": "unauthenticated-context apiapp" + "2": "cors keystone_authtoken context apiapp", + "3": "cors keystone_v3_authtoken context apiapp", + "none": "cors unauthenticated-context apiapp" }[self.keystone_api_version] + @property + def barbican_api_keystone_audit_pipeline(self): + if self.keystone_api_version == "2": + return 'keystone_authtoken context audit apiapp' + else: + return 'keystone_v3_authtoken context audit apiapp' + + +class HSMAdapter(charms_openstack.adapters.OpenStackRelationAdapter): + """Adapt the barbican-hsm-plugin relation for use in rendering the config + for Barbican. Note that the HSM relation is optional, so we have a class + variable 'exists' that we can test in the template to see if we should + render HSM parameters into the template. + """ + + interface_type = 'hsm' + + @property + def library_path(self): + """Provide a library_path property to the template if it exists""" + try: + return self.relation.plugin_data['library_path'] + except: + return '' + + @property + def login(self): + """Provide a login property to the template if it exists""" + try: + return self.relation.plugin_data['login'] + except: + return '' + + @property + def slot_id(self): + """Provide a slot_id property to the template if it exists""" + try: + return self.relation.plugin_data['slot_id'] + except: + return '' + class BarbicanAdapters(charms_openstack.adapters.OpenStackRelationAdapters): """ @@ -91,9 +169,14 @@ class BarbicanAdapters(charms_openstack.adapters.OpenStackRelationAdapters): This plumbs in the BarbicanConfigurationAdapter as the ConfigurationAdapter to provide additional properties. """ + + relation_adapters = { + 'hsm': HSMAdapter, + } + def __init__(self, relations): super(BarbicanAdapters, self).__init__( - relations, options=BarbicanConfigurationAdapter) + relations, options_instance=BarbicanConfigurationAdapter()) class BarbicanCharm(charms_openstack.charm.OpenStackCharm): @@ -101,58 +184,126 @@ class BarbicanCharm(charms_openstack.charm.OpenStackCharm): functionality to manage a barbican unit. """ - release = 'liberty' + release = 'mitaka' name = 'barbican' packages = PACKAGES api_ports = { - 'barbican-api': { + 'barbican-worker': { os_ip.PUBLIC: 9311, os_ip.ADMIN: 9312, - os_ip.INTERNAL: 9313, + os_ip.INTERNAL: 9311, } } service_type = 'barbican' - default_service = 'barbican-api' - services = ['barbican-api', 'barbican-worker'] + default_service = 'barbican-worker' + services = ['apache2', 'barbican-worker'] + # Note that the hsm interface is optional - defined in config.yaml required_relations = ['shared-db', 'amqp', 'identity-service'] restart_map = { - "{}/{}".format(BARBICAN_DIR, BARBICAN_API_CONF): services, - "{}/{}".format(BARBICAN_DIR, BARBICAN_ADMIN_PASTE_CONF): services, - "{}/{}".format(BARBICAN_DIR, BARBICAN_API_PASTE_CONF): services, + BARBICAN_CONF: services, + BARBICAN_API_PASTE_CONF: services, } adapters_class = BarbicanAdapters - def __init__(self, release=None, **kwargs): - """Custom initialiser for class - - If no release is passed, then the charm determines the release from the - ch_utils.os_release() function. - - Note that the release_selector can be used to control which class is - instantiated. - """ - if release is None: - # release = ch_utils.os_release('barbican-common') - release = ch_utils.os_release('python-keystonemiddleware') - super(BarbicanCharm, self).__init__(release=release, **kwargs) - def install(self): """Customise the installation, configure the source and then call the parent install() method to install the packages """ - charmhelpers.fetch.add_source("ppa:gnuoy/barbican-alt") + # DEBUG - until seed random change lands into xenial cloud archive + # BUG #1599550 - barbican + softhsm2 + libssl1.0.0: + # pkcs11:_generate_random() fails + charmhelpers.fetch.add_source("ppa:ajkavanagh/barbican") self.configure_source() # and do the actual install super(BarbicanCharm, self).install() + def action_generate_mkek(self, hsm): + """Generate an MKEK on a connected HSM. Requires that an HSM is + avaiable via the barbican-hsm-plugin interface, generically known as + 'hsm'. + + Uses the barbican-manage command. + + :param hsm: instance of BarbicanRequires() class from the + barbican-hsm-plugin interface + """ + plugin_data = hsm.plugin_data + cmd = [ + 'barbican-manage', 'hsm', 'gen_mkek', + '--library-path', plugin_data['library_path'], + '--passphrase', plugin_data['login'], + '--slot-id', plugin_data['slot_id'], + '--length', str(hookenv.config('mkek-key-length')), + '--label', hookenv.config('label-mkek'), + ] + try: + subprocess.check_call(cmd) + hookenv.log("barbican-mangage hsm gen_mkek succeeded") + except subprocess.CalledProcessError: + str_err = "barbican-manage hsm gen_mkek failed." + hookenv.log(str_err) + raise Exception(str_err) + + def action_generate_hmac(self, hsm): + """Generate an HMAC on a connected HSM. Requires that an HSM is + avaiable via the barbican-hsm-plugin interface, generically known as + 'hsm'. + + Uses the barbican-manage command. + + :param hsm: instance of BarbicanRequires() class from the + barbican-hsm-plugin interface + """ + plugin_data = hsm.plugin_data + cmd = [ + 'barbican-manage', 'hsm', 'gen_hmac', + '--library-path', plugin_data['library_path'], + '--passphrase', plugin_data['login'], + '--slot-id', plugin_data['slot_id'], + '--length', str(hookenv.config('hmac-key-length')), + '--label', hookenv.config('label-hmac'), + ] + try: + subprocess.check_call(cmd) + hookenv.log("barbican-mangage hsm gen_hmac succeeded") + except subprocess.CalledProcessError: + str_err = "barbican-manage hsm gen_hmac failed." + hookenv.log(str_err) + raise Exception(str_err) + + def states_to_check(self, required_relations=None): + """Override the default states_to_check() for the assess_status + functionality so that, if we have to have an HSM relation, then enforce + it on the assess_status() call. + + If param required_relations is not None then it overrides the + instance/class variable self.required_relations. + + :param required_relations: [list of state names] + :returns: [states{} as per parent method] + """ + if required_relations is None: + required_relations = self.required_relations + if hookenv.config('require-hsm-plugin'): + required_relations.append('hsm') + return super(BarbicanCharm, self).states_to_check( + required_relations=required_relations) + # Determine the charm class by the supported release @charms_openstack.charm.register_os_release_selector def select_release(): """Determine the release based on the python-keystonemiddleware that is installed. + + Note that this function caches the release after the first install so that + it doesn't need to keep going and getting it from the package information. """ - return ch_utils.os_release('python-keystonemiddleware') + release_version = unitdata.kv().get(OPENSTACK_RELEASE_KEY, None) + if release_version is None: + release_version = ch_utils.os_release('python-keystonemiddleware') + unitdata.kv().set(OPENSTACK_RELEASE_KEY, release_version) + return release_version diff --git a/src/metadata.yaml b/src/metadata.yaml index 3afbdf7..310ef64 100644 --- a/src/metadata.yaml +++ b/src/metadata.yaml @@ -18,3 +18,6 @@ requires: interface: rabbitmq identity-service: interface: keystone + hsm: + interface: barbican-hsm-plugin + optional: true diff --git a/src/reactive/__init__.py b/src/reactive/__init__.py index e69de29..9b088de 100644 --- a/src/reactive/__init__.py +++ b/src/reactive/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016 Canonical Ltd +# +# 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. diff --git a/src/reactive/barbican_handlers.py b/src/reactive/barbican_handlers.py index 952ea50..bc5ee24 100644 --- a/src/reactive/barbican_handlers.py +++ b/src/reactive/barbican_handlers.py @@ -1,3 +1,17 @@ +# Copyright 2016 Canonical Ltd +# +# 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. + # this is just for the reactive handlers and calls into the charm. from __future__ import absolute_import @@ -48,6 +62,16 @@ def setup_endpoint(keystone): @reactive.when('identity-service.available') @reactive.when('amqp.available') def render_stuff(*args): + """Render the configuration for Barbican when all the interfaces are + available. + + Note that the HSM interface is optional (hence the @when_any) and thus is + only used if it is available. + """ + # Get the optional hsm relation, if it is available for rendering. + hsm = reactive.RelationBase.from_state('hsm.available') + if hsm is not None: + args = args + (hsm, ) barbican.render_configs(args) barbican.assess_status() diff --git a/src/templates/juno/barbican-admin-paste.ini b/src/templates/juno/barbican-admin-paste.ini deleted file mode 100644 index e24b9fb..0000000 --- a/src/templates/juno/barbican-admin-paste.ini +++ /dev/null @@ -1,8 +0,0 @@ -[pipeline:main] -pipeline = unauthenticated-context admin - -[app:admin] -paste.app_factory = barbican.api.app:create_admin_app - -[filter:unauthenticated-context] -paste.filter_factory = barbican.api.middleware.context:UnauthenticatedContextMiddleware.factory diff --git a/src/templates/juno/barbican-api-paste.ini b/src/templates/juno/barbican-api-paste.ini index dfd224a..aa70f4e 100644 --- a/src/templates/juno/barbican-api-paste.ini +++ b/src/templates/juno/barbican-api-paste.ini @@ -5,24 +5,28 @@ use = egg:Paste#urlmap # Use this pipeline for Barbican API - versions no authentication [pipeline:barbican_version] -pipeline = versionapp +pipeline = cors versionapp # Use this pipeline for Barbican API - DEFAULT no authentication [pipeline:barbican_api] -####pipeline = simple apiapp -#pipeline = keystone_authtoken context apiapp +# pipeline = cors unauthenticated-context apiapp pipeline = {{ options.barbican_api_pipeline }} #Use this pipeline to activate a repoze.profile middleware and HTTP port, # to provide profiling information for the REST API processing. [pipeline:barbican-profile] -pipeline = unauthenticated-context egg:Paste#cgitb egg:Paste#httpexceptions profile apiapp +pipeline = cors unauthenticated-context egg:Paste#cgitb egg:Paste#httpexceptions profile apiapp #Use this pipeline for keystone auth [pipeline:barbican-api-keystone] -#pipeline = keystone_authtoken context apiapp +# pipeline = cors keystone_authtoken context apiapp pipeline = {{ options.barbican_api_keystone_pipeline }} +#Use this pipeline for keystone auth with audit feature +[pipeline:barbican-api-keystone-audit] +# pipeline = keystone_authtoken context audit apiapp +pipeline = {{ options.barbican_api_keystone_audit_pipeline }} + [app:apiapp] paste.app_factory = barbican.api.app:create_main_app @@ -32,39 +36,43 @@ paste.app_factory = barbican.api.app:create_version_app [filter:simple] paste.filter_factory = barbican.api.middleware.simple:SimpleFilter.factory -[filter:unauthenticated-context] +[filter:unauthenticated-context] paste.filter_factory = barbican.api.middleware.context:UnauthenticatedContextMiddleware.factory -[filter:context] +[filter:context] paste.filter_factory = barbican.api.middleware.context:ContextMiddleware.factory -[filter:keystone_authtoken] +[filter:audit] +paste.filter_factory = keystonemiddleware.audit:filter_factory +audit_map_file = /etc/barbican/api_audit_map.conf + +[filter:keystone_authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory signing_dir = /var/lib/barbican/keystone-signing auth_host = {{ identity_service.auth_host }} #need ability to re-auth a token, thus admin url -auth_port = {{ identity_service.auth_port }} -auth_protocol = {{ identity_service.auth_protocol }} -admin_tenant_name = {{ identity_service.service_tenant }} -admin_user = {{ identity_service.service_username }} -admin_password = {{ identity_service.service_password }} -auth_version = v2.0 +auth_port = {{ identity_service.auth_port }} +auth_protocol = {{ identity_service.auth_protocol }} +admin_tenant_name = {{ identity_service.service_tenant }} +admin_user = {{ identity_service.service_username }} +admin_password = {{ identity_service.service_password }} +auth_version = v2.0 #delay failing perhaps to log the unauthorized request in barbican .. #delay_auth_decision = true -[filter:keystone_v3_authtoken] -paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory +[filter:keystone_v3_authtoken] +paste.filter_factory = keystonemiddleware.auth_token:filter_factory signing_dir = /var/lib/barbican/keystone-signing auth_host = {{ identity_service.auth_host }} #need ability to re-auth a token, thus admin url -auth_port = {{ identity_service.auth_port }} -auth_protocol = {{ identity_service.auth_protocol }} -admin_tenant_name = {{ identity_service.service_tenant }} -admin_user = {{ identity_service.service_username }} -admin_password = {{ identity_service.service_password }} -auth_version = v3.0 +auth_port = {{ identity_service.auth_port }} +auth_protocol = {{ identity_service.auth_protocol }} +admin_tenant_name = {{ identity_service.service_tenant }} +admin_user = {{ identity_service.service_username }} +admin_password = {{ identity_service.service_password }} +auth_version = v3.0 #delay failing perhaps to log the unauthorized request in barbican .. -#delay_auth_decision = true +#delay_auth_decision = true [filter:profile] use = egg:repoze.profile @@ -74,3 +82,7 @@ discard_first_request = true path = /__profile__ flush_at_shutdown = true unwind = false + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = barbican diff --git a/src/templates/juno/barbican-api.conf b/src/templates/juno/barbican.conf similarity index 50% rename from src/templates/juno/barbican-api.conf rename to src/templates/juno/barbican.conf index e329c87..fc5e056 100644 --- a/src/templates/juno/barbican-api.conf +++ b/src/templates/juno/barbican.conf @@ -11,7 +11,7 @@ bind_host = 0.0.0.0 # Port to bind the API server to bind_port = 9311 -# Host name, for use in HATEOS-style references +# Host name, for use in HATEOAS-style references # Note: Typically this would be the load balanced endpoint that clients would use # communicate back with this service. host_href = http://localhost:9311 @@ -38,6 +38,10 @@ max_allowed_request_size_in_bytes = 1000000 #sql_connection = sqlite:///barbican.sqlite # Note: For absolute addresses, use '////' slashes after 'sqlite:' # Uncomment for a more global development environment +#sql_connection = sqlite:////var/lib/barbican/barbican.sqlite + +# This is shared_db.uri from the shared_db relationship. +{% include "parts/database" %} # Period in seconds after which SQLAlchemy should reestablish its connection # to the database. @@ -48,19 +52,39 @@ max_allowed_request_size_in_bytes = 1000000 # before MySQL can drop the connection. sql_idle_timeout = 3600 +# Accepts a class imported from the sqlalchemy.pool module, and handles the +# details of building the pool for you. If commented out, SQLAlchemy +# will select based on the database dialect. Other options are QueuePool +# (for SQLAlchemy-managed connections) and NullPool (to disabled SQLAlchemy +# management of connections). +# See http://docs.sqlalchemy.org/en/latest/core/pooling.html for more details. +#sql_pool_class = QueuePool + +# Show SQLAlchemy pool-related debugging output in logs (sets DEBUG log level +# output) if specified. +#sql_pool_logging = True + +# Size of pool used by SQLAlchemy. This is the largest number of connections +# that will be kept persistently in the pool. Can be set to 0 to indicate no +# size limit. To disable pooling, use a NullPool with sql_pool_class instead. +# Comment out to allow SQLAlchemy to select the default. +#sql_pool_size = 5 + +# The maximum overflow size of the pool used by SQLAlchemy. When the number of +# checked-out connections reaches the size set in sql_pool_size, additional +# connections will be returned up to this limit. It follows then that the +# total number of simultaneous connections the pool will allow is +# sql_pool_size + sql_pool_max_overflow. Can be set to -1 to indicate no +# overflow limit, so no limit will be placed on the total number of concurrent +# connections. Comment out to allow SQLAlchemy to select the default. +#sql_pool_max_overflow = 10 + # Default page size for the 'limit' paging URL parameter. default_limit_paging = 10 # Maximum page size for the 'limit' paging URL parameter. max_limit_paging = 100 -# Number of Barbican API worker processes to start. -# On machines with more than one CPU increasing this value -# may improve performance (especially if using SSL with -# compression turned on). It is typically recommended to set -# this value to the number of CPUs present on your machine. -workers = 1 - # Role used to identify an authenticated user as administrator #admin_role = admin @@ -92,17 +116,27 @@ workers = 1 # Should be set to a random string of length 16, 24 or 32 bytes #metadata_encryption_key = <16, 24 or 32 char registry metadata key> -# ============ Delayed Delete Options ============================= +# ================= Queue Options - oslo.messaging ========================== -# Turn on/off delayed delete -delayed_delete = False +# rabbitmq-olso section from the rabbit-mq and releated relations. +{% include "parts/section-rabbitmq-oslo" %} -# Delayed delete time in seconds -scrub_time = 43200 -# Directory that the scrubber will use to remind itself of what to delete -# Make sure this is also set in glance-scrubber.conf -scrubber_datadir = /var/lib/barbican/scrubber +# For HA, specify queue nodes in cluster as 'user@host:5672', comma delimited, ending with '/offset': +# For example: transport_url = rabbit://guest@192.168.50.8:5672,guest@192.168.50.9:5672/ +# DO NOT USE THIS, due to '# FIXME(markmc): support multiple hosts' in oslo/messaging/_drivers/amqpdriver.py +# transport_url = rabbit://guest@localhost:5672/ + +# oslo notification driver for sending audit events via audit middleware. +# Meaningful only when middleware is enabled in barbican paste ini file. +# This is oslo config MultiStrOpt so can be defined multiple times in case +# there is need to route audit event to messaging as well as log. +# notification_driver = messagingv2 +# notification_driver = log + +# ======== OpenStack policy - oslo_policy =============== + +[oslo_policy] # ======== OpenStack policy integration # JSON file representing policy (string value) @@ -112,17 +146,6 @@ policy_file=/etc/barbican/policy.json policy_default_rule=default -{% include "parts/database" %} -# ================= Queue Options - oslo.messaging ========================== - -{% include "parts/section-rabbitmq-oslo" %} - -# For HA, specify queue nodes in cluster as 'user@host:5672', comma delimited, ending with '/offset': -# For example: transport_url = rabbit://guest@192.168.50.8:5672,guest@192.168.50.9:5672/ -# DO NOT USE THIS, due to '# FIXME(markmc): support multiple hosts' in oslo/messaging/_drivers/amqpdriver.py -# transport_url = rabbit://guest@localhost:5672/ - - # ================= Queue Options - Application ========================== [queue] @@ -142,6 +165,44 @@ version = '1.1' # Server name for RPC service server_name = 'barbican.queue' +# Number of asynchronous worker processes. +# When greater than 1, then that many additional worker processes are +# created for asynchronous worker functionality. +asynchronous_workers = 1 + +# ================= Retry/Scheduler Options ========================== + +[retry_scheduler] +# Seconds (float) to wait between starting retry scheduler +initial_delay_seconds = 10.0 + +# Seconds (float) to wait between starting retry scheduler +periodic_interval_max_seconds = 10.0 + + +# ====================== Quota Options =============================== + +[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 +# project through the API. A negative value means no limit. A zero +# value effectively disables the resource. + +# default number of secrets allowed per project +quota_secrets = -1 + +# default number of orders allowed per project +quota_orders = -1 + +# default number of containers allowed per project +quota_containers = -1 + +# default number of consumers allowed per project +quota_consumers = -1 + +# default number of CAs allowed per project +quota_cas = -1 + # ================= Keystone Notification Options - Application =============== [keystone_notifications] @@ -187,7 +248,11 @@ enabled_secretstore_plugins = store_crypto # ================= Crypto plugin =================== [crypto] namespace = barbican.crypto.plugin +{% if hsm -%} +enabled_crypto_plugins = p11_crypto +{% else -%} enabled_crypto_plugins = simple_crypto +{%- endif %} [simple_crypto_plugin] # the kek should be a 32-byte value which is base64 encoded @@ -198,19 +263,40 @@ pem_path = '/etc/barbican/kra_admin_cert.pem' dogtag_host = localhost dogtag_port = 8443 nss_db_path = '/etc/barbican/alias' +nss_db_path_ca = '/etc/barbican/alias-ca' nss_password = 'password123' +simple_cmc_profile = 'caOtherCert' +ca_expiration_time = 1 +plugin_working_dir = '/etc/barbican/dogtag' + +{% if hsm -%} [p11_crypto_plugin] # Path to vendor PKCS11 library -library_path = '/usr/lib/libCryptoki2_64.so' +library_path = {{ hsm.library_path }} # Password to login to PKCS11 session -login = 'mypassword' +login = {{ hsm.login }} # Label to identify master KEK in the HSM (must not be the same as HMAC label) -mkek_label = 'an_mkek' +mkek_label = {{ options.label_mkek }} # Length in bytes of master KEK -mkek_length = 32 +mkek_length = {{ options.mkek_key_length }} # Label to identify HMAC key in the HSM (must not be the same as MKEK label) -hmac_label = 'my_hmac_label' +hmac_label = {{ options.label_hmac }} +# HSM Slot id (Should correspond to a configured PKCS11 slot). Default: 1 +slot_id = {{ hsm.slot_id }} +# Enable Read/Write session with the HSM? +# rw_session = True +# Length of Project KEKs to create +# pkek_length = 32 +# How long to cache unwrapped Project KEKs +# pkek_cache_ttl = 900 +# Max number of items in pkek cache +# pkek_cache_limit = 100 +# Seedfile to generate random data from. +seed_file = /dev/random +# Seed length to read the random data for seeding the RNG +seed_length = 32 +{%- endif %} # ================== KMIP plugin ===================== @@ -218,13 +304,84 @@ hmac_label = 'my_hmac_label' username = 'admin' password = 'password' host = localhost -port = 9090 +port = 5696 +keyfile = '/path/to/certs/cert.key' +certfile = '/path/to/certs/cert.crt' +ca_certs = '/path/to/certs/LocalCA.crt' + # ================= Certificate plugin =================== [certificate] namespace = barbican.certificate.plugin enabled_certificate_plugins = simple_certificate +enabled_certificate_plugins = snakeoil_ca [certificate_event] namespace = barbican.certificate.event.plugin -enabled_certificate_event_plugins = simple_certificate +enabled_certificate_event_plugins = simple_certificate_event + +[snakeoil_ca_plugin] +ca_cert_path = /etc/barbican/snakeoil-ca.crt +ca_cert_key_path = /etc/barbican/snakeoil-ca.key +ca_cert_chain_path = /etc/barbican/snakeoil-ca.chain +ca_cert_pkcs7_path = /etc/barbican/snakeoil-ca.p7b +subca_cert_key_directory=/etc/barbican/snakeoil-cas + +[cors] + +# +# From oslo.middleware.cors +# + +# Indicate whether this resource may be shared with the domain +# received in the requests "origin" header. (list value) +#allowed_origin = + +# Indicate that the actual request can include user credentials +# (boolean value) +#allow_credentials = true + +# Indicate which headers are safe to expose to the API. Defaults to +# HTTP Simple Headers. (list value) +#expose_headers = X-Auth-Token, X-Openstack-Request-Id, X-Project-Id, X-Identity-Status, X-User-Id, X-Storage-Token, X-Domain-Id, X-User-Domain-Id, X-Project-Domain-Id, X-Roles + +# Maximum cache age of CORS preflight requests. (integer value) +#max_age = 3600 + +# Indicate which methods can be used during the actual request. (list +# value) +#allow_methods = GET,PUT,POST,DELETE,PATCH + +# Indicate which header field names may be used during the actual +# request. (list value) +#allow_headers = X-Auth-Token, X-Openstack-Request-Id, X-Project-Id, X-Identity-Status, X-User-Id, X-Storage-Token, X-Domain-Id, X-User-Domain-Id, X-Project-Domain-Id, X-Roles + + +[cors.subdomain] + +# +# From oslo.middleware.cors +# + +# Indicate whether this resource may be shared with the domain +# received in the requests "origin" header. (list value) +#allowed_origin = + +# Indicate that the actual request can include user credentials +# (boolean value) +#allow_credentials = true + +# Indicate which headers are safe to expose to the API. Defaults to +# HTTP Simple Headers. (list value) +#expose_headers = X-Auth-Token, X-Openstack-Request-Id, X-Project-Id, X-Identity-Status, X-User-Id, X-Storage-Token, X-Domain-Id, X-User-Domain-Id, X-Project-Domain-Id, X-Roles + +# Maximum cache age of CORS preflight requests. (integer value) +#max_age = 3600 + +# Indicate which methods can be used during the actual request. (list +# value) +#allow_methods = GET,PUT,POST,DELETE,PATCH + +# Indicate which header field names may be used during the actual +# request. (list value) +#allow_headers = X-Auth-Token, X-Openstack-Request-Id, X-Project-Id, X-Identity-Status, X-User-Id, X-Storage-Token, X-Domain-Id, X-User-Domain-Id, X-Project-Domain-Id, X-Roles diff --git a/test-requirements.txt b/test-requirements.txt index 362199a..0017fb5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,4 +3,4 @@ os-testr>=0.4.1 charms.reactive mock>=1.2 coverage>=3.6 -git+https://github.com/ajkavanagh/charm.openstack#egg=charms-openstack +git+https://github.com/ajkavanagh/charms.openstack.git#egg=charms-openstack diff --git a/tox.ini b/tox.ini index e62c99f..41a21b7 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ setenv = VIRTUAL_ENV={envdir} TERM=linux INTERFACE_PATH={toxinidir}/interfaces LAYER_PATH={toxinidir}/layers + INTERFACE_PATH={toxinidir}/interfaces JUJU_REPOSITORY={toxinidir}/build passenv = http_proxy https_proxy install_command = @@ -19,12 +20,7 @@ deps = [testenv:build] basepython = python2.7 commands = - charm build --log-level DEBUG -o {toxinidir}/build src - -[testenv:py27] -basepython = python2.7 -deps = -r{toxinidir}/test-requirements.txt -commands = ostestr {posargs} + charm-build --log-level DEBUG -o {toxinidir}/build src {posargs} [testenv:py34] basepython = python3.4 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 519bf15..4d30f85 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2016 Canonical Ltd +# +# 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 sys import mock @@ -11,6 +25,7 @@ sys.modules['charmhelpers'] = charmhelpers sys.modules['charmhelpers.core'] = charmhelpers.core sys.modules['charmhelpers.core.hookenv'] = charmhelpers.core.hookenv sys.modules['charmhelpers.core.host'] = charmhelpers.core.host +sys.modules['charmhelpers.core.unitdata'] = charmhelpers.core.unitdata sys.modules['charmhelpers.core.templating'] = charmhelpers.core.templating sys.modules['charmhelpers.contrib'] = charmhelpers.contrib sys.modules['charmhelpers.contrib.openstack'] = charmhelpers.contrib.openstack diff --git a/unit_tests/test_barbican_handlers.py b/unit_tests/test_barbican_handlers.py index 7433ec2..ee58b06 100644 --- a/unit_tests/test_barbican_handlers.py +++ b/unit_tests/test_barbican_handlers.py @@ -1,3 +1,17 @@ +# Copyright 2016 Canonical Ltd +# +# 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 __future__ import absolute_import from __future__ import print_function @@ -93,6 +107,7 @@ class TestBarbicanHandlers(unittest.TestCase): 'render_stuff': ('shared-db.available', 'identity-service.available', 'amqp.available',), + 'config_changed': ('config.changed', ), } when_not_patterns = { 'install_packages': ('charm.installed', ), @@ -102,6 +117,7 @@ class TestBarbicanHandlers(unittest.TestCase): (_when_not_args, when_not_patterns)]: for f, args in t.items(): # check that function is in patterns + # print("f: {}, args: {}".format(f, args)) self.assertTrue(f in p.keys()) # check that the lists are equal l = [a['args'][0] for a in args] @@ -122,9 +138,11 @@ class TestBarbicanHandlers(unittest.TestCase): 'rabbit-vhost': 'vhost1', } self.config.side_effect = lambda x: reply[x] + self.patch(handlers.barbican, 'assess_status') handlers.setup_amqp_req(amqp) amqp.request_access.assert_called_once_with( username='user1', vhost='vhost1') + self.assess_status.assert_called_once_with() def test_database(self): database = mock.MagicMock() @@ -135,16 +153,25 @@ class TestBarbicanHandlers(unittest.TestCase): } self.config.side_effect = lambda x: reply[x] self.patch(handlers.hookenv, 'unit_private_ip', 'private_ip') + self.patch(handlers.barbican, 'assess_status') handlers.setup_database(database) database.configure.assert_called_once_with( 'db1', 'dbuser1', 'private_ip') + self.assess_status.assert_called_once_with() def test_setup_endpoint(self): self.patch(handlers.barbican, 'setup_endpoint') + self.patch(handlers.barbican, 'assess_status') handlers.setup_endpoint('endpoint_object') self.setup_endpoint.assert_called_once_with('endpoint_object') + self.assess_status.assert_called_once_with() def test_render_stuff(self): self.patch(handlers.barbican, 'render_configs') + self.patch(handlers.barbican, 'assess_status') + self.patch(handlers.reactive.RelationBase, 'from_state', + return_value='hsm') handlers.render_stuff('arg1', 'arg2') - self.render_configs.assert_called_once_with(('arg1', 'arg2', )) + self.render_configs.assert_called_once_with(('arg1', 'arg2', 'hsm')) + self.assess_status.assert_called_once_with() + self.from_state.assert_called_once_with('hsm.available') diff --git a/unit_tests/test_lib_charm_openstack_barbican.py b/unit_tests/test_lib_charm_openstack_barbican.py index d203dad..030f545 100644 --- a/unit_tests/test_lib_charm_openstack_barbican.py +++ b/unit_tests/test_lib_charm_openstack_barbican.py @@ -1,3 +1,17 @@ +# Copyright 2016 Canonical Ltd +# +# 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 __future__ import absolute_import from __future__ import print_function @@ -13,6 +27,11 @@ class Helper(unittest.TestCase): def setUp(self): self._patches = {} self._patches_start = {} + # patch out the select_release to always return 'mitaka' + self.patch(barbican.unitdata, 'kv') + _getter = mock.MagicMock() + _getter.get.return_value = barbican.BarbicanCharm.release + self.kv.return_value = _getter def tearDown(self): for k, v in self._patches.items(): @@ -76,23 +95,23 @@ class TestBarbicanConfigurationAdapter(Helper): # Make one with no errors, api version 2 a = barbican.BarbicanConfigurationAdapter() self.assertEqual(a.barbican_api_keystone_pipeline, - 'keystone_authtoken context apiapp') + 'cors keystone_authtoken context apiapp') self.assertEqual(a.barbican_api_pipeline, - 'keystone_authtoken context apiapp') + 'cors keystone_authtoken context apiapp') # Now test it with api version 3 reply['keystone-api-version'] = '3' a = barbican.BarbicanConfigurationAdapter() self.assertEqual(a.barbican_api_keystone_pipeline, - 'keystone_v3_authtoken context apiapp') + 'cors keystone_v3_authtoken context apiapp') self.assertEqual(a.barbican_api_pipeline, - 'keystone_v3_authtoken context apiapp') + 'cors keystone_v3_authtoken context apiapp') # and a 'none' version reply['keystone-api-version'] = 'none' a = barbican.BarbicanConfigurationAdapter() self.assertEqual(a.barbican_api_keystone_pipeline, - 'keystone_v3_authtoken context apiapp') + 'cors keystone_v3_authtoken context apiapp') self.assertEqual(a.barbican_api_pipeline, - 'unauthenticated-context apiapp') + 'cors unauthenticated-context apiapp') # finally, try to create an invalid one. reply['keystone-api-version'] = None with self.assertRaises(ValueError): @@ -129,19 +148,5 @@ class TestBarbicanAdapters(Helper): class TestBarbicanCharm(Helper): - def test__init__(self): - self.patch(barbican.ch_utils, 'os_release') - barbican.BarbicanCharm() - self.os_release.assert_called_once_with('python-keystonemiddleware') - - def test_install(self): - self.patch(barbican.charmhelpers.fetch, 'add_source') - b = barbican.BarbicanCharm() - self.patch(barbican.charms_openstack.charm.OpenStackCharm, - 'configure_source') - self.patch(barbican.charms_openstack.charm.OpenStackCharm, - 'install') - b.install() - self.add_source.assert_called_once_with('ppa:gnuoy/barbican-alt') - self.configure_source.assert_called_once_with() - self.install.assert_called_once_with() + # tests to be added + pass