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
This commit is contained in:
jichenjc 2017-07-09 10:35:23 +08:00 committed by Sean Dague
parent bd37c64b4a
commit 905d31dd97
5 changed files with 86 additions and 24 deletions

View File

@ -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)

View File

@ -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)

34
nova/conf/keystone.py Normal file
View File

@ -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())
}

View File

@ -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")

View File

@ -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.