Correct generated links when behind an SSL terminating proxy

Adds SSLMiddleware that checks a configurable header to set
the correct url_scheme used to generate links when host headers
are in use.

Change-Id: Id864bb53d175c868fdee58c04fd2ea27ee188e08
Closes-Bug: #1490034
This commit is contained in:
TimSimmons 2015-08-28 16:39:10 -05:00
parent 7ac4cb4759
commit 0f183cf2e8
3 changed files with 85 additions and 7 deletions

View File

@ -41,6 +41,16 @@ cfg.CONF.register_opts([
help='Enable API Maintenance Mode'),
cfg.StrOpt('maintenance-mode-role', default='admin',
help='Role allowed to bypass maintaince mode'),
cfg.StrOpt('secure-proxy-ssl-header',
default='X-Forwarded-Proto',
help="The HTTP Header that will be used to determine which "
"the original request protocol scheme was, even if it was "
"removed by an SSL terminating proxy."),
cfg.StrOpt('override-proto',
default=None,
help="A scheme that will be used to override "
"the request protocol scheme, even if it was "
"set by an SSL terminating proxy.")
], group='service:api')
@ -355,3 +365,26 @@ class APIv2ValidationErrorMiddleware(ValidationErrorMiddleware):
def __init__(self, application):
super(APIv2ValidationErrorMiddleware, self).__init__(application)
self.api_version = 'API_v2'
class SSLMiddleware(base.Middleware):
"""A middleware that replaces the request wsgi.url_scheme environment
variable with the value of HTTP header configured in
secure_proxy_ssl_header if exists in the incoming request.
This is useful if the server is behind a SSL termination proxy.
Code nabbed from Heat.
"""
def __init__(self, application):
super(SSLMiddleware, self).__init__(application)
LOG.info(_LI('Starting designate ssl middleware'))
self.secure_proxy_ssl_header = 'HTTP_{0}'.format(
cfg.CONF['service:api'].secure_proxy_ssl_header.upper().
replace('-', '_'))
self.override = cfg.CONF['service:api'].override_proto
def process_request(self, request):
request.environ['wsgi.url_scheme'] = request.environ.get(
self.secure_proxy_ssl_header, request.environ['wsgi.url_scheme'])
if self.override:
request.environ['wsgi.url_scheme'] = self.override

View File

@ -143,3 +143,42 @@ class KeystoneContextMiddlewareTest(oslotest.base.BaseTestCase):
self.app(self.request)
self.assertFalse(self.ctxt.edit_managed_records)
class SSLMiddlewareTest(oslotest.base.BaseTestCase):
def setUp(self):
super(SSLMiddlewareTest, self).setUp()
self.app = middleware.SSLMiddleware({})
self.request = FakeRequest()
def test_bogus_header(self):
self.request.environ['wsgi.url_scheme'] = 'http'
# If someone sends something bogus, it will infect their self links
self.request.environ['HTTP_X_FORWARDED_PROTO'] = 'poo'
self.app(self.request)
self.assertEqual(self.request.environ['wsgi.url_scheme'], 'poo')
def test_http_header(self):
self.request.environ['wsgi.url_scheme'] = ''
self.request.environ['HTTP_X_FORWARDED_PROTO'] = 'http'
self.app(self.request)
self.assertEqual(self.request.environ['wsgi.url_scheme'], 'http')
def test_https_header(self):
self.request.environ['wsgi.url_scheme'] = 'http'
self.request.environ['HTTP_X_FORWARDED_PROTO'] = 'https'
self.app(self.request)
self.assertEqual(self.request.environ['wsgi.url_scheme'], 'https')
def test_override_proto(self):
self.request.environ['wsgi.url_scheme'] = 'http'
self.request.environ['HTTP_X_FORWARDED_PROTO'] = 'https'
self.app.override = 'poo'
self.app(self.request)
self.assertEqual(self.request.environ['wsgi.url_scheme'], 'poo')

View File

@ -1,33 +1,36 @@
[composite:osapi_dns]
use = egg:Paste#urlmap
/: osapi_dns_app_versions
/: osapi_dns_versions
/v1: osapi_dns_v1
/v2: osapi_dns_v2
/admin: osapi_dns_admin
[pipeline:osapi_dns_versions]
pipeline = maintenance faultwrapper ssl osapi_dns_app_versions
[app:osapi_dns_app_versions]
paste.app_factory = designate.api.versions:factory
[composite:osapi_dns_v1]
use = call:designate.api.middleware:auth_pipeline_factory
noauth = request_id noauthcontext maintenance validation_API_v1 faultwrapper normalizeuri osapi_dns_app_v1
keystone = request_id authtoken keystonecontext maintenance validation_API_v1 faultwrapper normalizeuri osapi_dns_app_v1
noauth = request_id noauthcontext maintenance validation_API_v1 faultwrapper ssl normalizeuri osapi_dns_app_v1
keystone = request_id authtoken keystonecontext maintenance validation_API_v1 faultwrapper ssl normalizeuri osapi_dns_app_v1
[app:osapi_dns_app_v1]
paste.app_factory = designate.api.v1:factory
[composite:osapi_dns_v2]
use = call:designate.api.middleware:auth_pipeline_factory
noauth = request_id faultwrapper validation_API_v2 noauthcontext maintenance normalizeuri osapi_dns_app_v2
keystone = request_id faultwrapper validation_API_v2 authtoken keystonecontext maintenance normalizeuri osapi_dns_app_v2
noauth = request_id faultwrapper ssl validation_API_v2 noauthcontext maintenance normalizeuri osapi_dns_app_v2
keystone = request_id faultwrapper ssl validation_API_v2 authtoken keystonecontext maintenance normalizeuri osapi_dns_app_v2
[app:osapi_dns_app_v2]
paste.app_factory = designate.api.v2:factory
[composite:osapi_dns_admin]
use = call:designate.api.middleware:auth_pipeline_factory
noauth = request_id faultwrapper noauthcontext maintenance normalizeuri osapi_dns_app_admin
keystone = request_id faultwrapper authtoken keystonecontext maintenance normalizeuri osapi_dns_app_admin
noauth = request_id faultwrapper ssl noauthcontext maintenance normalizeuri osapi_dns_app_admin
keystone = request_id faultwrapper ssl authtoken keystonecontext maintenance normalizeuri osapi_dns_app_admin
[app:osapi_dns_app_admin]
paste.app_factory = designate.api.admin:factory
@ -58,3 +61,6 @@ paste.filter_factory = designate.api.middleware:APIv1ValidationErrorMiddleware.f
[filter:validation_API_v2]
paste.filter_factory = designate.api.middleware:APIv2ValidationErrorMiddleware.factory
[filter:ssl]
paste.filter_factory = designate.api.middleware:SSLMiddleware.factory