From 5f193f2e0ed3f271243d8d0980393b457df7ee51 Mon Sep 17 00:00:00 2001 From: Madhuri Kumari Date: Tue, 31 May 2016 11:53:11 +0530 Subject: [PATCH] Add x509keypair_cert_manager to store certs in DB x509keypair_cert_manager stores certs in Magnum DB. Change-Id: Id8106d7bd5c270679bc189fc0dc17bcbde367d73 Implements: blueprint blueprint barbican-alternative-store --- devstack/lib/magnum | 8 +- .../cert_manager/x509keypair_cert_manager.py | 103 ++++++++++++++++++ magnum/conductor/handlers/bay_conductor.py | 10 +- magnum/conductor/handlers/ca_conductor.py | 5 +- .../conductor/handlers/common/cert_manager.py | 39 ++++--- magnum/conductor/k8s_api.py | 2 +- .../test_x509keypair_cert_manager.py | 96 ++++++++++++++++ .../handlers/common/test_cert_manager.py | 27 +++-- .../conductor/handlers/test_bay_conductor.py | 4 +- .../conductor/handlers/test_ca_conductor.py | 2 +- setup.cfg | 1 + 11 files changed, 254 insertions(+), 43 deletions(-) create mode 100644 magnum/common/cert_manager/x509keypair_cert_manager.py create mode 100644 magnum/tests/unit/common/cert_manager/test_x509keypair_cert_manager.py diff --git a/devstack/lib/magnum b/devstack/lib/magnum index ed80ab2d97..3e690de3f2 100644 --- a/devstack/lib/magnum +++ b/devstack/lib/magnum @@ -201,13 +201,7 @@ function create_magnum_conf { if is_service_enabled barbican; then iniset $MAGNUM_CONF certificates cert_manager_type "barbican" else - MAGNUM_LOCAL_CERT_DIR=${MAGNUM_LOCAL_CERT_DIR:-/var/lib/magnum/certificates/} - if [[ ! -d $MAGNUM_LOCAL_CERT_DIR ]]; then - sudo mkdir -p $MAGNUM_LOCAL_CERT_DIR - sudo chown $STACK_USER $MAGNUM_LOCAL_CERT_DIR - fi - iniset $MAGNUM_CONF certificates storage_path "$MAGNUM_LOCAL_CERT_DIR" - iniset $MAGNUM_CONF certificates cert_manager_type "local" + iniset $MAGNUM_CONF certificates cert_manager_type "x509keypair" fi trustee_domain_id=$(get_or_create_domain magnum 'Owns users and projects created by magnum') diff --git a/magnum/common/cert_manager/x509keypair_cert_manager.py b/magnum/common/cert_manager/x509keypair_cert_manager.py new file mode 100644 index 0000000000..085a309d10 --- /dev/null +++ b/magnum/common/cert_manager/x509keypair_cert_manager.py @@ -0,0 +1,103 @@ +# Copyright (c) 2016 Intel, Inc. +# +# 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 oslo_config import cfg +from oslo_log import log as logging + +from magnum.common.cert_manager import cert_manager +from magnum import objects + + +LOG = logging.getLogger(__name__) + +CONF = cfg.CONF + + +class Cert(cert_manager.Cert): + """Representation of a Cert for Magnum DB storage.""" + def __init__(self, certificate, private_key, intermediates=None, + private_key_passphrase=None): + self.certificate = certificate + self.intermediates = intermediates + self.private_key = private_key + self.private_key_passphrase = private_key_passphrase + + def get_certificate(self): + return self.certificate + + def get_intermediates(self): + return self.intermediates + + def get_private_key(self): + return self.private_key + + def get_private_key_passphrase(self): + return self.private_key_passphrase + + +class CertManager(cert_manager.CertManager): + """Cert Manager Interface that stores data locally in Magnum db. + + """ + + @staticmethod + def store_cert(certificate, private_key, intermediates=None, + private_key_passphrase=None, context=None, **kwargs): + """Stores (i.e., registers) a cert with the cert manager. + + This method stores the specified cert to x509keypair model and returns + a UUID that can be used to retrieve it. + + :param certificate: PEM encoded TLS certificate + :param private_key: private key for the supplied certificate + :param intermediates: ordered and concatenated intermediate certs + :param private_key_passphrase: optional passphrase for the supplied key + + :returns: the UUID of the stored cert + """ + x509keypair = {'certificate': certificate, 'private_key': private_key, + 'private_key_passphrase': private_key_passphrase, + 'intermediates': intermediates, + 'project_id': context.project_id, + 'user_id': context.user_id} + x509keypair_obj = objects.X509KeyPair(context, **x509keypair) + x509keypair_obj.create() + return x509keypair_obj.uuid + + @staticmethod + def get_cert(cert_ref, context=None, **kwargs): + """Retrieves the specified cert. + + :param cert_ref: the UUID of the cert to retrieve + + :return: magnum.common.cert_manager.cert_manager.Cert + representation of the certificate data + """ + cert_data = dict() + x509keypair_obj = objects.X509KeyPair.get_by_uuid(context, cert_ref) + cert_data['certificate'] = x509keypair_obj.certificate + cert_data['private_key'] = x509keypair_obj.private_key + cert_data['private_key_passphrase'] = \ + x509keypair_obj.private_key_passphrase + cert_data['intermediates'] = x509keypair_obj.intermediates + return Cert(**cert_data) + + @staticmethod + def delete_cert(cert_ref, context=None, **kwargs): + """Deletes the specified cert. + + :param cert_ref: the UUID of the cert to delete + """ + x509keypair_obj = objects.X509KeyPair.get_by_uuid(context, cert_ref) + x509keypair_obj.destroy() diff --git a/magnum/conductor/handlers/bay_conductor.py b/magnum/conductor/handlers/bay_conductor.py index 4a969ea861..bffd2026b1 100644 --- a/magnum/conductor/handlers/bay_conductor.py +++ b/magnum/conductor/handlers/bay_conductor.py @@ -130,13 +130,13 @@ class Handler(object): # Create trustee/trust and set them to bay trust_manager.create_trustee_and_trust(osc, bay) # Generate certificate and set the cert reference to bay - cert_manager.generate_certificates_to_bay(bay) + cert_manager.generate_certificates_to_bay(bay, context=context) conductor_utils.notify_about_bay_operation( context, taxonomy.ACTION_CREATE, taxonomy.OUTCOME_PENDING) created_stack = _create_stack(context, osc, bay, bay_create_timeout) except Exception as e: - cert_manager.delete_certificates_from_bay(bay) + cert_manager.delete_certificates_from_bay(bay, context=context) trust_manager.delete_trustee_and_trust(osc, context, bay) conductor_utils.notify_about_bay_operation( context, taxonomy.ACTION_CREATE, taxonomy.OUTCOME_FAILURE) @@ -212,7 +212,7 @@ class Handler(object): ' deletion.'), stack_id) try: trust_manager.delete_trustee_and_trust(osc, context, bay) - cert_manager.delete_certificates_from_bay(bay) + cert_manager.delete_certificates_from_bay(bay, context=context) bay.destroy() except exception.BayNotFound: LOG.info(_LI('The bay %s has been deleted by others.'), uuid) @@ -227,7 +227,6 @@ class Handler(object): conductor_utils.notify_about_bay_operation( context, taxonomy.ACTION_DELETE, taxonomy.OUTCOME_FAILURE) raise - self._poll_and_check(osc, bay) return None @@ -319,7 +318,8 @@ class HeatPoller(object): trust_manager.delete_trustee_and_trust(self.openstack_client, self.context, self.bay) - cert_manager.delete_certificates_from_bay(self.bay) + cert_manager.delete_certificates_from_bay(self.bay, + context=self.context) self.bay.destroy() except exception.BayNotFound: LOG.info(_LI('The bay %s has been deleted by others.') diff --git a/magnum/conductor/handlers/ca_conductor.py b/magnum/conductor/handlers/ca_conductor.py index ea2ff509da..eb4825e312 100644 --- a/magnum/conductor/handlers/ca_conductor.py +++ b/magnum/conductor/handlers/ca_conductor.py @@ -34,12 +34,13 @@ class Handler(object): def sign_certificate(self, context, bay, certificate): LOG.debug("Creating self signed x509 certificate") signed_cert = cert_manager.sign_node_certificate(bay, - certificate.csr) + certificate.csr, + context=context) certificate.pem = signed_cert return certificate def get_ca_certificate(self, context, bay): - ca_cert = cert_manager.get_bay_ca_certificate(bay) + ca_cert = cert_manager.get_bay_ca_certificate(bay, context=context) certificate = objects.Certificate.from_object_bay(bay) certificate.pem = ca_cert.get_certificate() return certificate diff --git a/magnum/conductor/handlers/common/cert_manager.py b/magnum/conductor/handlers/common/cert_manager.py index 952dd09166..918c0191a8 100644 --- a/magnum/conductor/handlers/common/cert_manager.py +++ b/magnum/conductor/handlers/common/cert_manager.py @@ -29,7 +29,7 @@ CONDUCTOR_CLIENT_NAME = six.u('Magnum-Conductor') LOG = logging.getLogger(__name__) -def _generate_ca_cert(issuer_name): +def _generate_ca_cert(issuer_name, context=None): """Generate and store ca_cert :param issuer_name: CA subject name @@ -43,12 +43,13 @@ def _generate_ca_cert(issuer_name): private_key=ca_cert['private_key'], private_key_passphrase=ca_password, name=issuer_name, + context=context, ) LOG.debug('CA cert is created: %s', ca_cert_ref) return ca_cert_ref, ca_cert, ca_password -def _generate_client_cert(issuer_name, ca_cert, ca_password): +def _generate_client_cert(issuer_name, ca_cert, ca_password, context=None): """Generate and store magnum_client_cert :param issuer_name: CA subject name @@ -69,6 +70,7 @@ def _generate_client_cert(issuer_name, ca_cert, ca_password): private_key=client_cert['private_key'], private_key_passphrase=client_password, name=CONDUCTOR_CLIENT_NAME, + context=context ) LOG.debug('Magnum client cert is created: %s', magnum_cert_ref) return magnum_cert_ref @@ -83,7 +85,7 @@ def _get_issuer_name(bay): return issuer_name -def generate_certificates_to_bay(bay): +def generate_certificates_to_bay(bay, context=None): """Generate ca_cert and magnum client cert and set to bay :param bay: The bay to set CA cert and magnum client cert @@ -94,10 +96,12 @@ def generate_certificates_to_bay(bay): LOG.debug('Start to generate certificates: %s', issuer_name) - ca_cert_ref, ca_cert, ca_password = _generate_ca_cert(issuer_name) + ca_cert_ref, ca_cert, ca_password = _generate_ca_cert(issuer_name, + context=context) magnum_cert_ref = _generate_client_cert(issuer_name, ca_cert, - ca_password) + ca_password, + context=context) bay.ca_cert_ref = ca_cert_ref bay.magnum_cert_ref = magnum_cert_ref @@ -107,27 +111,29 @@ def generate_certificates_to_bay(bay): raise exception.CertificatesToBayFailed(bay_uuid=bay.uuid) -def get_bay_ca_certificate(bay): +def get_bay_ca_certificate(bay, context=None): ca_cert = cert_manager.get_backend().CertManager.get_cert( bay.ca_cert_ref, - resource_ref=bay.uuid + resource_ref=bay.uuid, + context=context ) return ca_cert -def get_bay_magnum_cert(bay): +def get_bay_magnum_cert(bay, context=None): magnum_cert = cert_manager.get_backend().CertManager.get_cert( bay.magnum_cert_ref, - resource_ref=bay.uuid + resource_ref=bay.uuid, + context=context ) return magnum_cert -def create_client_files(bay): - ca_cert = get_bay_ca_certificate(bay) - magnum_cert = get_bay_magnum_cert(bay) +def create_client_files(bay, context=None): + ca_cert = get_bay_ca_certificate(bay, context) + magnum_cert = get_bay_magnum_cert(bay, context) ca_cert_file = tempfile.NamedTemporaryFile() ca_cert_file.write(ca_cert.get_certificate()) @@ -144,10 +150,11 @@ def create_client_files(bay): return ca_cert_file, magnum_key_file, magnum_cert_file -def sign_node_certificate(bay, csr): +def sign_node_certificate(bay, csr, context=None): ca_cert = cert_manager.get_backend().CertManager.get_cert( bay.ca_cert_ref, - resource_ref=bay.uuid + resource_ref=bay.uuid, + context=context ) node_cert = x509.sign(csr, @@ -157,7 +164,7 @@ def sign_node_certificate(bay, csr): return node_cert -def delete_certificates_from_bay(bay): +def delete_certificates_from_bay(bay, context=None): """Delete ca cert and magnum client cert from bay :param bay: The bay which has certs @@ -167,6 +174,6 @@ def delete_certificates_from_bay(bay): cert_ref = getattr(bay, cert_ref, None) if cert_ref: cert_manager.get_backend().CertManager.delete_cert( - cert_ref, resource_ref=bay.uuid) + cert_ref, resource_ref=bay.uuid, context=context) except Exception: LOG.warning(_LW("Deleting certs is failed for Bay %s"), bay.uuid) diff --git a/magnum/conductor/k8s_api.py b/magnum/conductor/k8s_api.py index cdd88051d8..06f53004ce 100644 --- a/magnum/conductor/k8s_api.py +++ b/magnum/conductor/k8s_api.py @@ -47,7 +47,7 @@ class K8sAPI(apiv_api.ApivApi): if bay.magnum_cert_ref: (self.ca_file, self.key_file, - self.cert_file) = create_client_files(bay) + self.cert_file) = create_client_files(bay, context) # build a connection with Kubernetes master client = api_client.ApiClient(bay.api_address, diff --git a/magnum/tests/unit/common/cert_manager/test_x509keypair_cert_manager.py b/magnum/tests/unit/common/cert_manager/test_x509keypair_cert_manager.py new file mode 100644 index 0000000000..c83af8de06 --- /dev/null +++ b/magnum/tests/unit/common/cert_manager/test_x509keypair_cert_manager.py @@ -0,0 +1,96 @@ +# Copyright 2016 Intel, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import mock + +from magnum.common.cert_manager import x509keypair_cert_manager as x509_cm +from magnum.common import context +from magnum.tests import base +from magnum.tests.unit.db import base as db_base +from magnum.tests.unit.db import utils + + +class TestX509keypairCert(base.BaseTestCase): + + def setUp(self): + self.certificate = "My Certificate" + self.intermediates = "My Intermediates" + self.private_key = "My Private Key" + self.private_key_passphrase = "My Private Key Passphrase" + + super(TestX509keypairCert, self).setUp() + + def test_x509keypair_cert(self): + # Create a cert + cert = x509_cm.Cert( + certificate=self.certificate, + intermediates=self.intermediates, + private_key=self.private_key, + private_key_passphrase=self.private_key_passphrase + ) + + # Validate the cert functions + self.assertEqual(self.certificate, cert.get_certificate()) + self.assertEqual(self.intermediates, cert.get_intermediates()) + self.assertEqual(self.private_key, cert.get_private_key()) + self.assertEqual(self.private_key_passphrase, + cert.get_private_key_passphrase()) + + +class TestX509keypairManager(db_base.DbTestCase): + + def setUp(self): + self.certificate = "My Certificate" + self.intermediates = "My Intermediates" + self.private_key = "My Private Key" + self.private_key_passphrase = "My Private Key Passphrase" + self.context = context.make_admin_context() + super(TestX509keypairManager, self).setUp() + + def test_store_cert(self): + x509keypair = utils.get_test_x509keypair() + with mock.patch.object(self.dbapi, 'create_x509keypair', + autospec=True) as mock_create_x509keypair: + mock_create_x509keypair.return_value = x509keypair + + uuid = x509_cm.CertManager.store_cert(context=self.context, + **x509keypair) + self.assertEqual(uuid, '72625085-c507-4410-9b28-cd7cf1fbf1ad') + + def test_get_cert(self): + x509keypair = utils.get_test_x509keypair(uuid='fake-uuid') + with mock.patch.object(self.dbapi, 'get_x509keypair_by_uuid', + autospec=True) as mock_get_x509keypair: + mock_get_x509keypair.return_value = x509keypair + cert_obj = x509_cm.CertManager.get_cert('fake-uuid', + context=self.context) + self.assertEqual(cert_obj.certificate, 'certificate') + self.assertEqual(cert_obj.private_key, 'private_key') + self.assertEqual(cert_obj.private_key_passphrase, + 'private_key_passphrase') + self.assertEqual(cert_obj.intermediates, 'intermediates') + mock_get_x509keypair.assert_called_once_with(self.context, + 'fake-uuid') + + def test_delete_cert(self): + x509keypair = utils.get_test_x509keypair(uuid='fake-uuid') + with mock.patch.object(self.dbapi, 'get_x509keypair_by_uuid', + autospec=True) as mock_get_x509keypair: + mock_get_x509keypair.return_value = x509keypair + with mock.patch.object(self.dbapi, 'destroy_x509keypair', + autospec=True) as mock_destroy_x509keypair: + x509_cm.CertManager.delete_cert('fake-uuid', + context=self.context) + mock_get_x509keypair.assert_called_once_with(self.context, + 'fake-uuid') + mock_destroy_x509keypair.assert_called_once_with('fake-uuid') diff --git a/magnum/tests/unit/conductor/handlers/common/test_cert_manager.py b/magnum/tests/unit/conductor/handlers/common/test_cert_manager.py index 7593d075e5..9aee37141f 100644 --- a/magnum/tests/unit/conductor/handlers/common/test_cert_manager.py +++ b/magnum/tests/unit/conductor/handlers/common/test_cert_manager.py @@ -57,6 +57,7 @@ class CertManagerTestCase(base.BaseTestCase): private_key=expected_ca_cert['private_key'], private_key_passphrase=expected_ca_password, name=expected_ca_name, + context=None ) @mock.patch('magnum.common.x509.operations.generate_client_certificate') @@ -96,6 +97,7 @@ class CertManagerTestCase(base.BaseTestCase): private_key=expected_cert['private_key'], private_key_passphrase=expected_password, name=expected_name, + context=None ) def _test_generate_certificates(self, @@ -118,9 +120,11 @@ class CertManagerTestCase(base.BaseTestCase): self.assertEqual(expected_ca_cert_ref, mock_bay.ca_cert_ref) self.assertEqual(expected_cert_ref, mock_bay.magnum_cert_ref) - mock_generate_ca_cert.assert_called_once_with(expected_ca_name) + mock_generate_ca_cert.assert_called_once_with(expected_ca_name, + context=None) mock_generate_client_cert.assert_called_once_with( - expected_ca_name, expected_ca_cert, expected_ca_password) + expected_ca_name, expected_ca_cert, expected_ca_password, + context=None) @mock.patch('magnum.conductor.handlers.common.cert_manager.' '_generate_client_cert') @@ -178,7 +182,8 @@ class CertManagerTestCase(base.BaseTestCase): bay_ca_cert = cert_manager.sign_node_certificate(mock_bay, mock_csr) self.CertManager.get_cert.assert_called_once_with( - mock_bay.ca_cert_ref, resource_ref=mock_bay.uuid) + mock_bay.ca_cert_ref, resource_ref=mock_bay.uuid, + context=None) mock_x509_sign.assert_called_once_with(mock_csr, mock_bay.name, mock.sentinel.priv_key, passphrase) @@ -200,7 +205,7 @@ class CertManagerTestCase(base.BaseTestCase): bay_ca_cert = cert_manager.sign_node_certificate(mock_bay, mock_csr) self.CertManager.get_cert.assert_called_once_with( - mock_bay.ca_cert_ref, resource_ref=mock_bay.uuid) + mock_bay.ca_cert_ref, resource_ref=mock_bay.uuid, context=None) mock_x509_sign.assert_called_once_with(mock_csr, mock_bay.uuid, mock.sentinel.priv_key, passphrase) @@ -215,7 +220,7 @@ class CertManagerTestCase(base.BaseTestCase): bay_ca_cert = cert_manager.get_bay_ca_certificate(mock_bay) self.CertManager.get_cert.assert_called_once_with( - mock_bay.ca_cert_ref, resource_ref=mock_bay.uuid) + mock_bay.ca_cert_ref, resource_ref=mock_bay.uuid, context=None) self.assertEqual(mock_ca_cert, bay_ca_cert) def test_delete_certtificate(self): @@ -229,9 +234,11 @@ class CertManagerTestCase(base.BaseTestCase): cert_manager.delete_certificates_from_bay(mock_bay) mock_delete_cert.assert_any_call(expected_ca_cert_ref, - resource_ref=mock_bay.uuid) + resource_ref=mock_bay.uuid, + context=None) mock_delete_cert.assert_any_call(expected_cert_ref, - resource_ref=mock_bay.uuid) + resource_ref=mock_bay.uuid, + context=None) def test_delete_certtificate_if_raise_error(self): mock_delete_cert = self.CertManager.delete_cert @@ -245,9 +252,11 @@ class CertManagerTestCase(base.BaseTestCase): cert_manager.delete_certificates_from_bay(mock_bay) mock_delete_cert.assert_any_call(expected_ca_cert_ref, - resource_ref=mock_bay.uuid) + resource_ref=mock_bay.uuid, + context=None) mock_delete_cert.assert_any_call(expected_cert_ref, - resource_ref=mock_bay.uuid) + resource_ref=mock_bay.uuid, + context=None) def test_delete_certtificate_without_cert_ref(self): mock_delete_cert = self.CertManager.delete_cert diff --git a/magnum/tests/unit/conductor/handlers/test_bay_conductor.py b/magnum/tests/unit/conductor/handlers/test_bay_conductor.py index e66644516e..e542861cf7 100644 --- a/magnum/tests/unit/conductor/handlers/test_bay_conductor.py +++ b/magnum/tests/unit/conductor/handlers/test_bay_conductor.py @@ -216,7 +216,7 @@ class TestHandler(db_base.DbTestCase): mock.sentinel.osc, self.bay, timeout) mock_cert_manager.generate_certificates_to_bay.assert_called_once_with( - self.bay) + self.bay, context=self.context) self.assertEqual(bay_status.CREATE_IN_PROGRESS, bay.status) mock_trust_manager.create_trustee_and_trust.assert_called_once_with( osc, self.bay) @@ -241,7 +241,7 @@ class TestHandler(db_base.DbTestCase): gctb = mock_cert_manager.generate_certificates_to_bay if is_create_cert_called: - gctb.assert_called_once_with(self.bay) + gctb.assert_called_once_with(self.bay, context=self.context) else: gctb.assert_not_called() ctat = mock_trust_manager.create_trustee_and_trust diff --git a/magnum/tests/unit/conductor/handlers/test_ca_conductor.py b/magnum/tests/unit/conductor/handlers/test_ca_conductor.py index f36d861917..7628526db7 100644 --- a/magnum/tests/unit/conductor/handlers/test_ca_conductor.py +++ b/magnum/tests/unit/conductor/handlers/test_ca_conductor.py @@ -35,7 +35,7 @@ class TestSignConductor(base.TestCase): mock_certificate) mock_cert_manager.sign_node_certificate.assert_called_once_with( - mock_bay, 'fake-csr' + mock_bay, 'fake-csr', context=self.context ) self.assertEqual('fake-pem', actual_cert.pem) diff --git a/setup.cfg b/setup.cfg index 9fb4c29781..dfe62d5520 100644 --- a/setup.cfg +++ b/setup.cfg @@ -67,6 +67,7 @@ magnum.database.migration_backend = magnum.cert_manager.backend = barbican = magnum.common.cert_manager.barbican_cert_manager local = magnum.common.cert_manager.local_cert_manager + x509keypair = magnum.common.cert_manager.x509keypair_cert_manager tempest.test_plugins = magnum_tests = magnum.tests.functional.tempest_tests.plugin:MagnumTempestPlugin