From 2d52d4177ca7cf41ee8162f07e444d9b75c377dd Mon Sep 17 00:00:00 2001 From: Brianna Poulos Date: Thu, 6 Nov 2014 13:07:16 -0800 Subject: [PATCH] Fix wrapper to work with barbicanclient 3.0.1 Due to the updates to the python-barbicanclient in version 3.0.1, the cinder barbican wrapper no longer functions. This patch updates the cinder barbican wrapper and test cases to work with barbicanclient 3.0.1. Change-Id: Ibef4abd5fd9b12962420eaa5d7ebf62700171861 Closes-Bug: #1388461 --- cinder/keymgr/barbican.py | 149 +++++++++-------- cinder/keymgr/key_mgr.py | 2 +- cinder/tests/keymgr/test_barbican.py | 231 +++++++++++++++++++++++++++ etc/cinder/cinder.conf.sample | 2 +- 4 files changed, 316 insertions(+), 68 deletions(-) create mode 100644 cinder/tests/keymgr/test_barbican.py diff --git a/cinder/keymgr/barbican.py b/cinder/keymgr/barbican.py index 3d7e8ac44b2..afbf41cd0d7 100644 --- a/cinder/keymgr/barbican.py +++ b/cinder/keymgr/barbican.py @@ -22,8 +22,8 @@ import binascii from barbicanclient import client as barbican_client -from barbicanclient.common import auth -from keystoneclient.v2_0 import client as keystone_client +from keystoneclient.auth import identity +from keystoneclient import session from oslo.config import cfg from cinder import exception @@ -42,31 +42,40 @@ class BarbicanKeyManager(key_mgr.KeyManager): """Key Manager Interface that wraps the Barbican client API.""" - def _create_connection(self, ctxt): - """Creates a connection to the Barbican service. + def __init__(self): + self._base_url = CONF.keymgr.encryption_api_url + # the barbican endpoint can't have the '/v1' on the end + self._barbican_endpoint = self._base_url.rpartition('/')[0] + self._barbican_client = None + + def _get_barbican_client(self, ctxt): + """Creates a client to connect to the Barbican service. :param ctxt: the user context for authentication - :return: a Barbican Connection object + :return: a Barbican Client object :throws NotAuthorized: if the ctxt is None """ - # Confirm context is provided, if not raise not authorized - if not ctxt: - msg = _("User is not authorized to use key manager.") - LOG.error(msg) - raise exception.NotAuthorized(msg) - - try: - endpoint = CONF.keymgr.encryption_auth_url - keystone = keystone_client.Client(token=ctxt.auth_token, - endpoint=endpoint) - keystone_auth = auth.KeystoneAuthV2(keystone=keystone) - keystone_auth._barbican_url = CONF.keymgr.encryption_api_url - connection = barbican_client.Client(auth_plugin=keystone_auth) - return connection - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.error(_("Error creating Barbican client: %s"), (e)) + if not self._barbican_client: + # Confirm context is provided, if not raise not authorized + if not ctxt: + msg = _("User is not authorized to use key manager.") + LOG.error(msg) + raise exception.NotAuthorized(msg) + + try: + auth = identity.v3.Token( + auth_url=CONF.keymgr.encryption_auth_url, + token=ctxt.auth_token) + sess = session.Session(auth=auth) + self._barbican_client = barbican_client.Client( + session=sess, + endpoint=self._barbican_endpoint) + except Exception as e: + with excutils.save_and_reraise_exception(): + LOG.error(_("Error creating Barbican client: %s"), (e)) + + return self._barbican_client def create_key(self, ctxt, expiration=None, name='Cinder Volume Key', payload_content_type='application/octet-stream', mode='CBC', @@ -85,13 +94,18 @@ def create_key(self, ctxt, expiration=None, name='Cinder Volume Key', :return: the UUID of the new key :throws Exception: if key creation fails """ - connection = self._create_connection(ctxt) + barbican_client = self._get_barbican_client(ctxt) try: - order_ref = connection.orders.create(name, payload_content_type, - algorithm, length, mode, - expiration) - order = connection.orders.get(order_ref) + key_order = barbican_client.orders.create_key( + name, + algorithm, + length, + mode, + payload_content_type, + expiration) + order_ref = key_order.submit() + order = barbican_client.orders.get(order_ref) secret_uuid = order.secret_ref.rpartition('/')[2] return secret_uuid except Exception as e: @@ -123,7 +137,7 @@ def store_key(self, ctxt, key, expiration=None, name='Cinder Volume Key', :returns: the UUID of the stored key :throws Exception: if key storage fails """ - connection = self._create_connection(ctxt) + barbican_client = self._get_barbican_client(ctxt) try: if key.get_algorithm(): @@ -138,11 +152,15 @@ def store_key(self, ctxt, key, expiration=None, name='Cinder Volume Key', encoded_key = base64.b64encode(binascii.unhexlify(string_key)) else: encoded_key = key.get_encoded() - secret_ref = connection.secrets.store(name, encoded_key, - payload_content_type, - payload_content_encoding, - algorithm, bit_length, mode, - expiration) + secret = barbican_client.secrets.create(name, + encoded_key, + payload_content_type, + payload_content_encoding, + algorithm, + bit_length, + mode, + expiration) + secret_ref = secret.store() secret_uuid = secret_ref.rpartition('/')[2] return secret_uuid except Exception as e: @@ -158,36 +176,40 @@ def copy_key(self, ctxt, key_id): :return: the UUID of the key copy :throws Exception: if key copying fails """ - connection = self._create_connection(ctxt) + barbican_client = self._get_barbican_client(ctxt) try: - secret_ref = self._create_secret_ref(key_id, connection) - meta = self._get_secret_metadata(ctxt, secret_ref) - con_type = meta.content_types['default'] - secret_data = self._get_secret_data(ctxt, secret_ref, + secret_ref = self._create_secret_ref(key_id, barbican_client) + secret = self._get_secret(ctxt, secret_ref) + con_type = secret.content_types['default'] + secret_data = self._get_secret_data(secret, payload_content_type=con_type) - key = keymgr_key.SymmetricKey(meta.algorithm, secret_data) - copy_uuid = self.store_key(ctxt, key, meta.expiration, - meta.name, con_type, + key = keymgr_key.SymmetricKey(secret.algorithm, secret_data) + copy_uuid = self.store_key(ctxt, key, secret.expiration, + secret.name, con_type, 'base64', - meta.algorithm, meta.bit_length, - meta.mode, True) + secret.algorithm, secret.bit_length, + secret.mode, True) return copy_uuid except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_("Error copying key: %s"), (e)) - def _create_secret_ref(self, key_id, connection): + def _create_secret_ref(self, key_id, barbican_client): """Creates the URL required for accessing a secret. :param key_id: the UUID of the key to copy - :param connection: barbican key manager object + :param barbican_client: barbican key manager object :return: the URL of the requested secret """ - return connection.base_url + "/secrets/" + key_id + if not key_id: + msg = "Key ID is None" + raise exception.KeyManagerError(msg) + return self._base_url + "/secrets/" + key_id - def _get_secret_data(self, ctxt, secret_ref, + def _get_secret_data(self, + secret, payload_content_type='application/octet-stream'): """Retrieves the secret data given a secret_ref and content_type. @@ -199,11 +221,8 @@ def _get_secret_data(self, ctxt, secret_ref, :returns: the secret data :throws Exception: if data cannot be retrieved """ - connection = self._create_connection(ctxt) - try: - generated_data = connection.secrets.decrypt(secret_ref, - payload_content_type) + generated_data = secret.payload if payload_content_type == 'application/octet-stream': secret_data = base64.b64encode(generated_data) else: @@ -213,7 +232,7 @@ def _get_secret_data(self, ctxt, secret_ref, with excutils.save_and_reraise_exception(): LOG.error(_("Error getting secret data: %s"), (e)) - def _get_secret_metadata(self, ctxt, secret_ref): + def _get_secret(self, ctxt, secret_ref): """Creates the URL required for accessing a secret's metadata. :param ctxt: contains information of the user and the environment for @@ -224,10 +243,10 @@ def _get_secret_metadata(self, ctxt, secret_ref): :throws Exception: if there is an error retrieving the data """ - connection = self._create_connection(ctxt) + barbican_client = self._get_barbican_client(ctxt) try: - return connection.secrets.get(secret_ref) + return barbican_client.secrets.get(secret_ref) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_("Error getting secret metadata: %s"), (e)) @@ -244,20 +263,18 @@ def get_key(self, ctxt, key_id, :return: SymmetricKey representation of the key :throws Exception: if key retrieval fails """ - connection = self._create_connection(ctxt) - try: - secret_ref = self._create_secret_ref(key_id, connection) - secret_data = self._get_secret_data(ctxt, secret_ref, + secret_ref = self._create_secret_ref(key_id, barbican_client) + secret = self._get_secret(ctxt, secret_ref) + secret_data = self._get_secret_data(secret, payload_content_type) if payload_content_type == 'application/octet-stream': # convert decoded string to list of unsigned ints for each byte - secret = array.array('B', - base64.b64decode(secret_data)).tolist() + key_data = array.array('B', + base64.b64decode(secret_data)).tolist() else: - secret = secret_data - meta = self._get_secret_metadata(ctxt, secret_ref) - key = keymgr_key.SymmetricKey(meta.algorithm, secret) + key_data = secret_data + key = keymgr_key.SymmetricKey(secret.algorithm, key_data) return key except Exception as e: with excutils.save_and_reraise_exception(): @@ -271,11 +288,11 @@ def delete_key(self, ctxt, key_id): :param key_id: the UUID of the key to delete :throws Exception: if key deletion fails """ - connection = self._create_connection(ctxt) + barbican_client = self._get_barbican_client(ctxt) try: - secret_ref = self._create_secret_ref(key_id, connection) - connection.secrets.delete(secret_ref) + secret_ref = self._create_secret_ref(key_id, barbican_client) + barbican_client.secrets.delete(secret_ref) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_("Error deleting key: %s"), (e)) diff --git a/cinder/keymgr/key_mgr.py b/cinder/keymgr/key_mgr.py index 372d8250674..9ed05a656ba 100644 --- a/cinder/keymgr/key_mgr.py +++ b/cinder/keymgr/key_mgr.py @@ -24,7 +24,7 @@ encryption_opts = [ cfg.StrOpt('encryption_auth_url', - default='http://localhost:5000/v2.0', + default='http://localhost:5000/v3', help='Authentication url for encryption service.'), cfg.StrOpt('encryption_api_url', default='http://localhost:9311/v1', diff --git a/cinder/tests/keymgr/test_barbican.py b/cinder/tests/keymgr/test_barbican.py new file mode 100644 index 00000000000..ea726c129cf --- /dev/null +++ b/cinder/tests/keymgr/test_barbican.py @@ -0,0 +1,231 @@ +# Copyright (c) 2014 The Johns Hopkins University/Applied Physics Laboratory +# All Rights Reserved. +# +# 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. + +""" +Test cases for the barbican key manager. +""" + +import array +import base64 +import binascii + +import mock +from oslo.config import cfg + +from cinder import exception +from cinder.keymgr import barbican +from cinder.keymgr import key as keymgr_key +from cinder.tests.keymgr import test_key_mgr + +CONF = cfg.CONF +CONF.import_opt('encryption_auth_url', 'cinder.keymgr.key_mgr', group='keymgr') +CONF.import_opt('encryption_api_url', 'cinder.keymgr.key_mgr', group='keymgr') + + +class BarbicanKeyManagerTestCase(test_key_mgr.KeyManagerTestCase): + + def _create_key_manager(self): + return barbican.BarbicanKeyManager() + + def setUp(self): + super(BarbicanKeyManagerTestCase, self).setUp() + + # Create fake auth_token + self.ctxt = mock.Mock() + self.ctxt.auth_token = "fake_token" + + # Create mock barbican client + self._build_mock_barbican() + + # Create a key_id, secret_ref, pre_hex, and hex to use + self.key_id = "d152fa13-2b41-42ca-a934-6c21566c0f40" + self.secret_ref = self.key_mgr._create_secret_ref(self.key_id, + self.mock_barbican) + self.pre_hex = "AIDxQp2++uAbKaTVDMXFYIu8PIugJGqkK0JLqkU0rhY=" + self.hex = ("0080f1429dbefae01b29a4d50cc5c5608bbc3c8ba0246aa42b424baa4" + "534ae16") + self.addCleanup(self._restore) + + def _restore(self): + if hasattr(self, 'original_key'): + keymgr_key.SymmetricKey = self.original_key + if hasattr(self, 'original_base64'): + base64.b64encode = self.original_base64 + + def _build_mock_barbican(self): + self.mock_barbican = mock.MagicMock(name='mock_barbican') + + # Set commonly used methods + self.get = self.mock_barbican.secrets.get + self.delete = self.mock_barbican.secrets.delete + self.store = self.mock_barbican.secrets.store + self.create = self.mock_barbican.secrets.create + + self.key_mgr._barbican_client = self.mock_barbican + + def _build_mock_symKey(self): + self.mock_symKey = mock.Mock() + + def fake_sym_key(alg, key): + self.mock_symKey.get_encoded.return_value = key + self.mock_symKey.get_algorithm.return_value = alg + return self.mock_symKey + self.original_key = keymgr_key.SymmetricKey + keymgr_key.SymmetricKey = fake_sym_key + + def _build_mock_base64(self): + + def fake_base64_b64encode(string): + return self.pre_hex + + self.original_base64 = base64.b64encode + base64.b64encode = fake_base64_b64encode + + def test_copy_key(self): + # Create metadata for original secret + original_secret_metadata = mock.Mock() + original_secret_metadata.algorithm = 'fake_algorithm' + original_secret_metadata.bit_length = 'fake_bit_length' + original_secret_metadata.name = 'original_name' + original_secret_metadata.expiration = 'fake_expiration' + original_secret_metadata.mode = 'fake_mode' + content_types = {'default': 'fake_type'} + original_secret_metadata.content_types = content_types + original_secret_data = mock.Mock() + original_secret_metadata.payload = original_secret_data + self.get.return_value = original_secret_metadata + + # Create the mock key + self._build_mock_symKey() + + # Copy the original + self.key_mgr.copy_key(self.ctxt, self.key_id) + + # Assert proper methods were called + self.get.assert_called_once_with(self.secret_ref) + self.create.assert_called_once_with( + original_secret_metadata.name, + self.mock_symKey.get_encoded(), + content_types['default'], + 'base64', + original_secret_metadata.algorithm, + original_secret_metadata.bit_length, + original_secret_metadata.mode, + original_secret_metadata.expiration) + self.store.assert_called() + + def test_copy_null_context(self): + self.key_mgr._barbican_client = None + self.assertRaises(exception.NotAuthorized, + self.key_mgr.copy_key, None, self.key_id) + + def test_create_key(self): + # Create order_ref_url and assign return value + order_ref_url = ("http://localhost:9311/v1/None/orders/" + "4fe939b7-72bc-49aa-bd1e-e979589858af") + key_order = mock.Mock() + self.mock_barbican.orders.create_key.return_value = key_order + key_order.submit.return_value = order_ref_url + + # Create order and assign return value + order = mock.Mock() + order.secret_ref = self.secret_ref + self.mock_barbican.orders.get.return_value = order + + # Create the key, get the UUID + returned_uuid = self.key_mgr.create_key(self.ctxt) + + self.mock_barbican.orders.get.assert_called_once_with(order_ref_url) + self.assertEqual(returned_uuid, self.key_id) + + def test_create_null_context(self): + self.key_mgr._barbican_client = None + self.assertRaises(exception.NotAuthorized, + self.key_mgr.create_key, None) + + def test_delete_null_context(self): + self.key_mgr._barbican_client = None + self.assertRaises(exception.NotAuthorized, + self.key_mgr.delete_key, None, self.key_id) + + def test_delete_key(self): + self.key_mgr.delete_key(self.ctxt, self.key_id) + self.delete.assert_called_once_with(self.secret_ref) + + def test_delete_unknown_key(self): + self.assertRaises(exception.KeyManagerError, + self.key_mgr.delete_key, self.ctxt, None) + + def test_get_key(self): + self._build_mock_base64() + content_type = 'application/octet-stream' + + key = self.key_mgr.get_key(self.ctxt, self.key_id, content_type) + + self.get.assert_called_once_with(self.secret_ref) + encoded = array.array('B', binascii.unhexlify(self.hex)).tolist() + self.assertEqual(key.get_encoded(), encoded) + + def test_get_null_context(self): + self.key_mgr._barbican_client = None + self.assertRaises(exception.NotAuthorized, + self.key_mgr.get_key, None, self.key_id) + + def test_get_unknown_key(self): + self.assertRaises(exception.KeyManagerError, + self.key_mgr.get_key, self.ctxt, None) + + def test_store_key_base64(self): + # Create Key to store + secret_key = array.array('B', [0x01, 0x02, 0xA0, 0xB3]).tolist() + _key = keymgr_key.SymmetricKey('AES', secret_key) + + # Define the return values + secret = mock.Mock() + self.create.return_value = secret + secret.store.return_value = self.secret_ref + + # Store the Key + returned_uuid = self.key_mgr.store_key(self.ctxt, _key, bit_length=32) + + self.create.assert_called_once_with('Cinder Volume Key', + 'AQKgsw==', + 'application/octet-stream', + 'base64', + 'AES', 32, 'CBC', + None) + self.assertEqual(returned_uuid, self.key_id) + + def test_store_key_plaintext(self): + # Create the plaintext key + secret_key_text = "This is a test text key." + _key = keymgr_key.SymmetricKey('AES', secret_key_text) + + # Store the Key + self.key_mgr.store_key(self.ctxt, _key, + payload_content_type='text/plain', + payload_content_encoding=None) + self.create.assert_called_once_with('Cinder Volume Key', + secret_key_text, + 'text/plain', + None, + 'AES', 256, 'CBC', + None) + self.store.assert_called_once() + + def test_store_null_context(self): + self.key_mgr._barbican_client = None + self.assertRaises(exception.NotAuthorized, + self.key_mgr.store_key, None, None) diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index 6665e30cef7..0bca292d4eb 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -2569,7 +2569,7 @@ # # Authentication url for encryption service. (string value) -#encryption_auth_url=http://localhost:5000/v2.0 +#encryption_auth_url=http://localhost:5000/v3 # Url for encryption service. (string value) #encryption_api_url=http://localhost:9311/v1