From 905d31dd9715505599b0a2ad123eebef37f606f5 Mon Sep 17 00:00:00 2001 From: jichenjc Date: Sun, 9 Jul 2017 10:35:23 +0800 Subject: [PATCH] Enable custom certificates for keystone communication Nova creates a session back to keystone to verify project ids for quota and flavor access APIs. The session that was being created was not based on conf options, so it only worked in simple default scenarios. This updates the session by using the newly added keystone section to utilize keystoneauth1 to manage the session creation, which allows for specifying custom site certificates to secure the link between Nova and Keystone. Change-Id: Ice4b226fdabdfb66e60b61de05ac8f3b37610661 Closes-Bug: 1704798 --- nova/api/openstack/identity.py | 9 ++- nova/conf/__init__.py | 2 + nova/conf/keystone.py | 34 +++++++++++ nova/tests/unit/test_identity.py | 58 ++++++++++++------- .../add_keystone_option-138dff5efb9a53aa.yaml | 7 +++ 5 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 nova/conf/keystone.py create mode 100644 releasenotes/notes/add_keystone_option-138dff5efb9a53aa.yaml diff --git a/nova/api/openstack/identity.py b/nova/api/openstack/identity.py index 4bcc0a026a0d..833d3b57b23a 100644 --- a/nova/api/openstack/identity.py +++ b/nova/api/openstack/identity.py @@ -13,12 +13,15 @@ # under the License. from keystoneauth1 import exceptions as kse -from keystoneauth1 import session +from keystoneauth1 import loading as ks_loading from oslo_log import log as logging import webob +import nova.conf from nova.i18n import _ + +CONF = nova.conf.CONF LOG = logging.getLogger(__name__) @@ -29,7 +32,9 @@ def verify_project_id(context, project_id): an HTTPBadRequest is emitted. """ - sess = session.Session(auth=context.get_auth_plugin()) + sess = ks_loading.load_session_from_conf_options( + CONF, 'keystone', auth=context.get_auth_plugin()) + failure = webob.exc.HTTPBadRequest( explanation=_("Project ID %s is not a valid project.") % project_id) diff --git a/nova/conf/__init__.py b/nova/conf/__init__.py index f0bd45d81576..547390164a68 100644 --- a/nova/conf/__init__.py +++ b/nova/conf/__init__.py @@ -40,6 +40,7 @@ from nova.conf import hyperv from nova.conf import ipv6 from nova.conf import ironic from nova.conf import key_manager +from nova.conf import keystone from nova.conf import libvirt from nova.conf import mks from nova.conf import netconf @@ -93,6 +94,7 @@ mks.register_opts(CONF) ipv6.register_opts(CONF) ironic.register_opts(CONF) key_manager.register_opts(CONF) +keystone.register_opts(CONF) libvirt.register_opts(CONF) netconf.register_opts(CONF) network.register_opts(CONF) diff --git a/nova/conf/keystone.py b/nova/conf/keystone.py new file mode 100644 index 000000000000..56d811ddbbcd --- /dev/null +++ b/nova/conf/keystone.py @@ -0,0 +1,34 @@ +# +# 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 keystoneauth1 import loading as ks_loading +from oslo_config import cfg + + +keystone_group = cfg.OptGroup( + 'keystone', + title='Keystone Options', + help='Configuration options for the identity service') + + +def register_opts(conf): + conf.register_group(keystone_group) + + ks_loading.register_session_conf_options(conf, keystone_group.name) + + +def list_opts(): + return { + keystone_group: ( + ks_loading.get_session_conf_options()) + } diff --git a/nova/tests/unit/test_identity.py b/nova/tests/unit/test_identity.py index bd2285ba7781..1f0c320c6359 100644 --- a/nova/tests/unit/test_identity.py +++ b/nova/tests/unit/test_identity.py @@ -16,6 +16,8 @@ import mock from keystoneauth1 import exceptions as kse +from keystoneauth1 import loading as ks_loading +from keystoneauth1.session import Session import webob from nova.api.openstack import identity @@ -67,81 +69,91 @@ class IdentityValidationTest(test.NoDBTestCase): """ - @mock.patch('keystoneauth1.session.Session.get') - def test_good_id(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_good_id(self, mock_load): """Test response 200. This indicates we have permissions, and we have definitively found the project exists. """ - get.return_value = FakeResponse(200) + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(200) + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_no_project(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_no_project(self, mock_load): """Test response 404. This indicates that we have permissions, and we have definitively found the project does not exist. """ - get.return_value = FakeResponse(404) + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(404) + mock_load.return_value = session self.assertRaises(webob.exc.HTTPBadRequest, identity.verify_project_id, mock.MagicMock(), "foo") - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_unknown_id(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_unknown_id(self, mock_load): """Test response 403. This indicates we don't have permissions. We fail open here and assume the project exists. """ - get.return_value = FakeResponse(403) + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(403) + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_unknown_error(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_unknown_error(self, mock_load): """Test some other return from keystone. If we got anything else, something is wrong on the keystone side. We don't want to fail on our side. """ - get.return_value = FakeResponse(500, "Oh noes!") + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(500, "Oh noes!") + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_early_fail(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_early_fail(self, mock_load): """Test if we get a keystoneauth exception. If we get a random keystoneauth exception, fall back and assume the project exists. """ - get.side_effect = kse.ConnectionError() + session = mock.create_autospec(Session) + session.get.side_effect = kse.ConnectionError() + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - @mock.patch('keystoneauth1.session.Session.get') - def test_wrong_version(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_wrong_version(self, mock_load): """Test endpoint not found. EndpointNotFound will be made when the keystone v3 API is not @@ -149,7 +161,9 @@ class IdentityValidationTest(test.NoDBTestCase): registered as the root endpoint. We treat this the same as 404. """ - get.side_effect = kse.EndpointNotFound() + session = mock.create_autospec(Session) + session.get.side_effect = kse.EndpointNotFound() + mock_load.return_value = session self.assertRaises(webob.exc.HTTPBadRequest, identity.verify_project_id, mock.MagicMock(), "foo") diff --git a/releasenotes/notes/add_keystone_option-138dff5efb9a53aa.yaml b/releasenotes/notes/add_keystone_option-138dff5efb9a53aa.yaml new file mode 100644 index 000000000000..21f017842813 --- /dev/null +++ b/releasenotes/notes/add_keystone_option-138dff5efb9a53aa.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + A new ``keystone`` config section is added so that you can + set session link attributes for communicating with keystone. This + allows the use of custom certificates to secure the link between + Nova and Keystone.