Handle SSL termination proxies for version list

Return correct scheme in version URLs if service
behind an SSL termination proxy.

This is done by adding a new configuration option,
secure_proxy_ssl_header, which, when defined, makes
the wsgi application take the host_url scheme from
that header. By default, when this option is not
specified, there is no difference in behavior.

The intention is to configure any ssl-decrypting
proxy to set that header, so that nova-api knows
which protocol to use in the URLs in response.

This patch is largely based on
https://review.openstack.org/#/c/132235/18

DocImpact
Closes-Bug: #1384379

Change-Id: I27ba166902ecc19c9b7fff2ee7f3bf733885efe1
This commit is contained in:
Radomir Dopieralski 2015-07-28 12:54:20 +02:00
parent 4a96b90623
commit ab35779238
3 changed files with 45 additions and 2 deletions

View File

@ -83,7 +83,7 @@ def get_media_map():
return dict(_MEDIA_TYPE_MAP.items())
class Request(webob.Request):
class Request(wsgi.Request):
"""Add some OpenStack API-specific logic to the base webob.Request."""
def __init__(self, *args, **kwargs):

View File

@ -23,6 +23,7 @@ from nova.api.openstack.compute import views
from nova import test
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import matchers
from nova import wsgi
NS = {
@ -89,6 +90,15 @@ EXP_VERSIONS = {
}
def _get_self_href(response):
"""Extract the URL to self from response data."""
data = jsonutils.loads(response.body)
for link in data['versions'][0]['links']:
if link['rel'] == 'self':
return link['href']
return ''
class VersionsTestV20(test.NoDBTestCase):
def test_get_version_list(self):
@ -422,3 +432,26 @@ class VersionsTestV21(test.NoDBTestCase):
version = jsonutils.loads(res.body)
expected = {"version": self.exp_versions['v2.1']}
self.assertEqual(expected, version)
class VersionBehindSslTestCase(test.NoDBTestCase):
def setUp(self):
super(VersionBehindSslTestCase, self).setUp()
self.flags(secure_proxy_ssl_header='HTTP_X_FORWARDED_PROTO')
def test_versions_without_headers(self):
req = wsgi.Request.blank('/')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(200, res.status_int)
href = _get_self_href(res)
self.assertTrue(href.startswith('http://'))
def test_versions_with_header(self):
req = wsgi.Request.blank('/')
req.accept = "application/json"
req.headers['X-Forwarded-Proto'] = 'https'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(200, res.status_int)
href = _get_self_href(res)
self.assertTrue(href.startswith('https://'))

View File

@ -51,6 +51,11 @@ wsgi_opts = [
'generate log lines. The following values can be formatted '
'into it: client_ip, date_time, request_line, status_code, '
'body_length, wall_seconds.'),
cfg.StrOpt('secure_proxy_ssl_header',
help='The HTTP header used to determine the scheme for the '
'original request, even if it was removed by an SSL '
'terminating proxy. Typical value is '
'"HTTP_X_FORWARDED_PROTO".'),
cfg.StrOpt('ssl_ca_file',
help="CA certificate file to use to verify "
"connecting clients"),
@ -274,7 +279,12 @@ class Server(service.ServiceBase):
class Request(webob.Request):
pass
def __init__(self, environ, *args, **kwargs):
if CONF.secure_proxy_ssl_header:
scheme = environ.get(CONF.secure_proxy_ssl_header)
if scheme:
environ['wsgi.url_scheme'] = scheme
super(Request, self).__init__(environ, *args, **kwargs)
class Application(object):