From 6d0248e9d9803d66f9cf8ee8837eb31a2a88f77d Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Thu, 23 Jun 2016 13:25:00 +0000 Subject: [PATCH] Add barbican-hsm-plugin interface support The barbican-hsm-plugin interface provides a mechanism for the Barbican charm to communicate with an HSM plugin. The plugin (from the Barbican perspective) is provided as a PKCS#11 compliant library (.so) and so is local to the Barbican installation. Thus, the hsm-plugin charms are subordinate to the Barbican charm and run on the same unit. This change also provides two actions (generate-mkek and generate-hmac) which are 'one-off' operations to initialise the HSM with the global master keys. Add a note to the README that the generate-mkek and generate-hmac actions may only be done once as the HSM may reject overwriting the key. Add Apache2.0 LICENSE and license headers to files Removed redundant copyright file Change the reference for the internal port to 9311 The barbican project changed the INTERNAL port to the same as the PUBLIC port. Add in seed_file and seed_length to template. These are needed for a change in Barbican to support seeding the RNG in the HSM if required. They are set to /dev/random and 32. Fetch the barbican sources from a PPA (for bug: 1599550) Remove the trusty support for Py3 from install hook --- LICENSE | 202 ++++++++++++++++ Makefile | 2 +- README.md | 87 +++++++ copyright | 13 - src/actions.yaml | 8 + src/actions/actions.py | 82 +++++++ src/actions/generate-hmac | 1 + src/actions/generate-mkek | 1 + src/config.yaml | 35 +++ src/hooks/install | 15 +- src/layer.yaml | 8 +- src/lib/__init__.py | 13 + src/lib/charm/__init__.py | 13 + src/lib/charm/openstack/__init__.py | 13 + src/lib/charm/openstack/barbican.py | 221 ++++++++++++++--- src/metadata.yaml | 3 + src/reactive/__init__.py | 13 + src/reactive/barbican_handlers.py | 24 ++ src/templates/juno/barbican-admin-paste.ini | 8 - src/templates/juno/barbican-api-paste.ini | 58 +++-- .../juno/{barbican-api.conf => barbican.conf} | 225 +++++++++++++++--- test-requirements.txt | 2 +- tox.ini | 8 +- unit_tests/__init__.py | 15 ++ unit_tests/test_barbican_handlers.py | 29 ++- .../test_lib_charm_openstack_barbican.py | 49 ++-- 26 files changed, 990 insertions(+), 158 deletions(-) create mode 100644 LICENSE create mode 100644 README.md delete mode 100644 copyright create mode 100644 src/actions.yaml create mode 100755 src/actions/actions.py create mode 120000 src/actions/generate-hmac create mode 120000 src/actions/generate-mkek delete mode 100644 src/templates/juno/barbican-admin-paste.ini rename src/templates/juno/{barbican-api.conf => barbican.conf} (50%) 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