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 glance-api knows
which protocol to use in the URLs in response.

This patch is largely based on the equivalent
nova patch: https://review.openstack.org/#/c/206479.

Partial-bug: 1558683

Change-Id: I9a9c0e42a6ad3c18d197f10095958b48d5cb879a
This commit is contained in:
Stuart McLaren 2016-03-18 15:42:15 +00:00
parent 35f134e214
commit 513d717d28
2 changed files with 114 additions and 0 deletions

View File

@ -104,6 +104,14 @@ eventlet_opts = [
'wait forever.')),
]
wsgi_opts = [
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".')),
]
profiler_opts = [
cfg.BoolOpt("enabled", default=False,
help=_('If False fully disable profiling feature.')),
@ -121,6 +129,7 @@ CONF = cfg.CONF
CONF.register_opts(bind_opts)
CONF.register_opts(socket_opts)
CONF.register_opts(eventlet_opts)
CONF.register_opts(wsgi_opts)
CONF.register_opts(profiler_opts, group="profiler")
ASYNC_EVENTLET_THREAD_POOL_LIST = []
@ -732,6 +741,13 @@ class Router(object):
class Request(webob.Request):
"""Add some OpenStack API-specific logic to the base webob.Request."""
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)
def best_match_content_type(self):
"""Determine the requested response content-type."""
supported = ('application/json',)

View File

@ -18,6 +18,7 @@ import webob
from glance.api.middleware import version_negotiation
from glance.api import versions
from glance.common.wsgi import Request as WsgiRequest
from glance.tests.unit import base
@ -122,6 +123,103 @@ class VersionsTest(base.IsolatedUnitTest):
]
self.assertEqual(expected, results)
def test_get_version_list_secure_proxy_ssl_header(self):
self.config(secure_proxy_ssl_header='HTTP_X_FORWARDED_PROTO')
environ = webob.request.environ_from_url('http://localhost:9292')
req = WsgiRequest(environ)
res = versions.Controller().index(req)
self.assertEqual(300, res.status_int)
self.assertEqual('application/json', res.content_type)
results = jsonutils.loads(res.body)['versions']
expected = [
{
'id': 'v2.3',
'status': 'CURRENT',
'links': [{'rel': 'self',
'href': 'http://localhost:9292/v2/'}],
},
{
'id': 'v2.2',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'http://localhost:9292/v2/'}],
},
{
'id': 'v2.1',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'http://localhost:9292/v2/'}],
},
{
'id': 'v2.0',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'http://localhost:9292/v2/'}],
},
{
'id': 'v1.1',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'http://localhost:9292/v1/'}],
},
{
'id': 'v1.0',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'http://localhost:9292/v1/'}],
},
]
self.assertEqual(expected, results)
def test_get_version_list_secure_proxy_ssl_header_https(self):
self.config(secure_proxy_ssl_header='HTTP_X_FORWARDED_PROTO')
environ = webob.request.environ_from_url('http://localhost:9292')
environ['HTTP_X_FORWARDED_PROTO'] = "https"
req = WsgiRequest(environ)
res = versions.Controller().index(req)
self.assertEqual(300, res.status_int)
self.assertEqual('application/json', res.content_type)
results = jsonutils.loads(res.body)['versions']
expected = [
{
'id': 'v2.3',
'status': 'CURRENT',
'links': [{'rel': 'self',
'href': 'https://localhost:9292/v2/'}],
},
{
'id': 'v2.2',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'https://localhost:9292/v2/'}],
},
{
'id': 'v2.1',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'https://localhost:9292/v2/'}],
},
{
'id': 'v2.0',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'https://localhost:9292/v2/'}],
},
{
'id': 'v1.1',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'https://localhost:9292/v1/'}],
},
{
'id': 'v1.0',
'status': 'SUPPORTED',
'links': [{'rel': 'self',
'href': 'https://localhost:9292/v1/'}],
},
]
self.assertEqual(expected, results)
class VersionNegotiationTest(base.IsolatedUnitTest):