diff --git a/devstack/lib/magnum b/devstack/lib/magnum index f1161a1546..de82cb20d6 100644 --- a/devstack/lib/magnum +++ b/devstack/lib/magnum @@ -179,6 +179,15 @@ function create_magnum_conf { if is_service_enabled ceilometer; then iniset $MAGNUM_CONF DEFAULT notification_driver "messaging" fi + + # Temporary work until barbican is available + 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" } function update_heat_policy { diff --git a/magnum/conductor/handlers/bay_conductor.py b/magnum/conductor/handlers/bay_conductor.py index cbd3030f2c..5417c3ece0 100644 --- a/magnum/conductor/handlers/bay_conductor.py +++ b/magnum/conductor/handlers/bay_conductor.py @@ -21,6 +21,7 @@ from oslo_service import loopingcall from magnum.common import clients from magnum.common import exception from magnum.common import short_id +from magnum.conductor.handlers.common import cert_manager from magnum.conductor import scale_manager from magnum.conductor.template_definition import TemplateDefinition as TDef from magnum.conductor import utils as conductor_utils @@ -130,6 +131,8 @@ class Handler(object): osc = clients.OpenStackClients(context) try: + # Generate certificate and set the cert reference to bay + cert_manager.generate_certificates_to_bay(bay) created_stack = _create_stack(context, osc, bay, bay_create_timeout) except exc.HTTPBadRequest as e: diff --git a/magnum/conductor/handlers/common/cert_manager.py b/magnum/conductor/handlers/common/cert_manager.py new file mode 100644 index 0000000000..6bcd5ea0e7 --- /dev/null +++ b/magnum/conductor/handlers/common/cert_manager.py @@ -0,0 +1,85 @@ +# Copyright 2015 NEC Corporation. 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. + +from oslo_log import log as logging +import six + +from magnum.common import cert_manager +from magnum.common import short_id +from magnum.common.x509 import operations as x509 + +CONDUCTOR_CLIENT_NAME = six.u('Magnum-Conductor') + +LOG = logging.getLogger(__name__) + + +def _generate_ca_cert(issuer_name): + """Generate and store ca_cert + + :param issuer_name: CA subject name + :returns: CA cert uuid and CA cert, CA private key password + """ + ca_password = short_id.generate_id() + ca_cert = x509.generate_ca_certificate(issuer_name, + encryption_password=ca_password) + ca_cert_ref = cert_manager.get_backend().CertManager.store_cert( + certificate=ca_cert['certificate'], + private_key=ca_cert['private_key'], + private_key_passphrase=ca_password, + name=issuer_name, + ) + 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): + """Generate and store magnum_client_cert + + :param issuer_name: CA subject name + :param ca_cert: CA certificate + :param ca_password: CA private key password + :returns: Magnum client cert uuid + """ + client_password = short_id.generate_id() + client_cert = x509.generate_client_certificate( + issuer_name, + CONDUCTOR_CLIENT_NAME, + ca_cert['private_key'], + encryption_password=client_password, + ca_key_password=ca_password, + ) + magnum_cert_ref = cert_manager.get_backend().CertManager.store_cert( + certificate=client_cert['certificate'], + private_key=client_cert['private_key'], + private_key_passphrase=client_password, + name=CONDUCTOR_CLIENT_NAME, + ) + LOG.debug('Magnum client cert is created: %s' % magnum_cert_ref) + return magnum_cert_ref + + +def generate_certificates_to_bay(bay): + """Generate ca_cert and magnum client cert and set to bay + + :param bay: The bay to set CA cert and magnum client cert + :returns: CA cert uuid and magnum client cert uuid + """ + issuer_name = bay.name + LOG.debug('Start to generate certificates: %s' % issuer_name) + + ca_cert_ref, ca_cert, ca_password = _generate_ca_cert(issuer_name) + magnum_cert_ref = _generate_client_cert(issuer_name, ca_cert, ca_password) + + bay.ca_cert_ref = ca_cert_ref + bay.magnum_cert_ref = magnum_cert_ref diff --git a/magnum/tests/unit/conductor/handlers/common/test_cert_manager.py b/magnum/tests/unit/conductor/handlers/common/test_cert_manager.py new file mode 100644 index 0000000000..e10f19eb4d --- /dev/null +++ b/magnum/tests/unit/conductor/handlers/common/test_cert_manager.py @@ -0,0 +1,117 @@ +# Copyright 2015 NEC Corporation. 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. + +import mock + +from magnum.common.cert_manager import get_backend +from magnum.conductor.handlers.common import cert_manager +from magnum.tests import base + + +class CertManagerTestCase(base.BaseTestCase): + + @mock.patch('magnum.common.x509.operations.generate_ca_certificate') + @mock.patch('magnum.common.short_id.generate_id') + def test_generate_ca_cert(self, mock_generate_id, mock_generate_ca_cert): + expected_ca_name = 'ca-name' + expected_ca_password = 'password' + expected_ca_cert = { + 'private_key': 'private_key', 'certificate': 'certificate'} + expected_ca_cert_ref = 'ca_cert_ref' + + mock_generate_id.return_value = expected_ca_password + mock_generate_ca_cert.return_value = expected_ca_cert + + with mock.patch.object(get_backend().CertManager, + 'store_cert') as mock_store_cert: + mock_store_cert.return_value = expected_ca_cert_ref + self.assertEqual( + cert_manager._generate_ca_cert(expected_ca_name), + (expected_ca_cert_ref, expected_ca_cert, + expected_ca_password)) + + mock_generate_ca_cert.assert_called_once_with( + expected_ca_name, encryption_password=expected_ca_password) + mock_store_cert.assert_called_once_with( + certificate=expected_ca_cert['certificate'], + private_key=expected_ca_cert['private_key'], + private_key_passphrase=expected_ca_password, + name=expected_ca_name, + ) + + @mock.patch('magnum.common.x509.operations.generate_client_certificate') + @mock.patch('magnum.common.short_id.generate_id') + def test_generate_client_cert(self, mock_generate_id, mock_generate_cert): + expected_name = cert_manager.CONDUCTOR_CLIENT_NAME + expected_ca_name = 'ca-name' + expected_password = 'password' + expected_ca_password = 'ca-password' + expected_cert = { + 'private_key': 'private_key', 'certificate': 'certificate'} + expected_ca_cert = { + 'private_key': 'ca_private_key', 'certificate': 'ca_certificate'} + expected_cert_ref = 'cert_ref' + + mock_generate_id.return_value = expected_password + mock_generate_cert.return_value = expected_cert + + with mock.patch.object(get_backend().CertManager, + 'store_cert') as mock_store_cert: + mock_store_cert.return_value = expected_cert_ref + self.assertEqual( + cert_manager._generate_client_cert( + expected_ca_name, expected_ca_cert, expected_ca_password), + expected_cert_ref) + + mock_generate_cert.assert_called_once_with( + expected_ca_name, + expected_name, + expected_ca_cert['private_key'], + encryption_password=expected_password, + ca_key_password=expected_ca_password, + ) + mock_store_cert.assert_called_once_with( + certificate=expected_cert['certificate'], + private_key=expected_cert['private_key'], + private_key_passphrase=expected_password, + name=expected_name, + ) + + @mock.patch('magnum.conductor.handlers.common.cert_manager.' + '_generate_client_cert') + @mock.patch('magnum.conductor.handlers.common.cert_manager.' + '_generate_ca_cert') + def test_generate_certificates(self, mock_generate_ca_cert, + mock_generate_client_cert): + expected_ca_name = 'ca-name' + expected_ca_password = 'ca-password' + expected_ca_cert = { + 'private_key': 'ca_private_key', 'certificate': 'ca_certificate'} + expected_cert_ref = 'cert_ref' + expected_ca_cert_ref = 'ca-cert-ref' + mock_bay = mock.MagicMock() + mock_bay.name = expected_ca_name + + mock_generate_ca_cert.return_value = (expected_ca_cert_ref, + expected_ca_cert, + expected_ca_password) + mock_generate_client_cert.return_value = expected_cert_ref + + cert_manager.generate_certificates_to_bay(mock_bay) + self.assertEqual(mock_bay.ca_cert_ref, expected_ca_cert_ref) + self.assertEqual(mock_bay.magnum_cert_ref, expected_cert_ref) + + mock_generate_ca_cert.assert_called_once_with(expected_ca_name) + mock_generate_client_cert.assert_called_once_with( + expected_ca_name, expected_ca_cert, expected_ca_password) diff --git a/magnum/tests/unit/conductor/handlers/test_bay_conductor.py b/magnum/tests/unit/conductor/handlers/test_bay_conductor.py index 5f88db7c57..1557e4fd98 100644 --- a/magnum/tests/unit/conductor/handlers/test_bay_conductor.py +++ b/magnum/tests/unit/conductor/handlers/test_bay_conductor.py @@ -798,14 +798,18 @@ class TestHandler(db_base.DbTestCase): bay = objects.Bay.get(self.context, self.bay.uuid) self.assertEqual(bay.node_count, 1) + @patch('magnum.conductor.handlers.common.cert_manager.' + 'generate_certificates_to_bay') @patch('magnum.conductor.handlers.bay_conductor._create_stack') @patch('magnum.common.clients.OpenStackClients') - def test_create(self, mock_openstack_client_class, mock_create_stack): + def test_create(self, mock_openstack_client_class, mock_create_stack, + mock_generate_certificates): mock_create_stack.side_effect = exc.HTTPBadRequest timeout = 15 self.assertRaises(exception.InvalidParameterValue, self.handler.bay_create, self.context, self.bay, timeout) + mock_generate_certificates.assert_called_once_with(self.bay) @patch('magnum.common.clients.OpenStackClients') def test_bay_delete(self, mock_openstack_client_class):