Merge "New middleware to handle SSL termination proxies"

This commit is contained in:
Jenkins 2014-01-18 22:43:04 +00:00 committed by Gerrit Code Review
commit 1b723d0c00
5 changed files with 108 additions and 2 deletions

View File

@ -1,7 +1,7 @@
# heat-api pipeline
[pipeline:heat-api]
pipeline = faultwrap versionnegotiation authtoken context apiv1app
pipeline = faultwrap ssl versionnegotiation authtoken context apiv1app
# heat-api pipeline for standalone heat
# ie. uses alternative auth backend that authenticates users against keystone
@ -12,7 +12,7 @@ pipeline = faultwrap versionnegotiation authtoken context apiv1app
# flavor = standalone
#
[pipeline:heat-api-standalone]
pipeline = faultwrap versionnegotiation authpassword context apiv1app
pipeline = faultwrap ssl versionnegotiation authpassword context apiv1app
# heat-api pipeline for custom cloud backends
# i.e. in heat.conf:
@ -74,6 +74,10 @@ paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory
[filter:ec2authtoken]
paste.filter_factory = heat.api.aws.ec2token:EC2Token_filter_factory
[filter:ssl]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.openstack:sslmiddleware_filter
# Auth middleware that validates token against keystone
[filter:authtoken]
paste.filter_factory = heat.common.auth_token:filter_factory

View File

@ -1,5 +1,15 @@
[DEFAULT]
#
# Options defined in heat.api.middleware.ssl
#
# The HTTP Header that will be used to determine which the
# original request protocol scheme was, even if it was removed
# by an SSL terminator proxy. (string value)
#secure_proxy_ssl_header=X-Forwarded-Proto
#
# Options defined in heat.common.config
#

View File

@ -0,0 +1,41 @@
# -*- encoding: utf-8 -*-
#
# 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 oslo.config import cfg
from heat.common import wsgi
ssl_middleware_opts = [
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 terminator proxy.")
]
cfg.CONF.register_opts(ssl_middleware_opts)
class SSLMiddleware(wsgi.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.
"""
def __init__(self, application):
super(SSLMiddleware, self).__init__(application)
self.secure_proxy_ssl_header = 'HTTP_{0}'.format(
cfg.CONF.secure_proxy_ssl_header.upper().replace('-', '_'))
def process_request(self, req):
req.environ['wsgi.url_scheme'] = req.environ.get(
self.secure_proxy_ssl_header, req.environ['wsgi.url_scheme'])

View File

@ -15,6 +15,7 @@
from heat.api.middleware.version_negotiation import VersionNegotiationFilter
from heat.api.middleware.fault import FaultWrapper
from heat.api.middleware.ssl import SSLMiddleware
from heat.api.openstack import versions
@ -25,3 +26,7 @@ def version_negotiation_filter(app, conf, **local_conf):
def faultwrap_filter(app, conf, **local_conf):
return FaultWrapper(app)
def sslmiddleware_filter(app, conf, **local_conf):
return SSLMiddleware(app)

View File

@ -0,0 +1,46 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.
import webob
from heat.api.middleware import ssl
from oslo.config import cfg
from heat.tests.common import HeatTestCase
class SSLMiddlewareTest(HeatTestCase):
scenarios = [('with_forwarded_proto_default_header',
dict(forwarded_protocol='https',
secure_proxy_ssl_header=None,
headers={'X-Forwarded-Proto': 'https'})),
('with_forwarded_proto_non_default_header',
dict(forwarded_protocol='http',
secure_proxy_ssl_header='X-My-Forwarded-Proto',
headers={})),
('without_forwarded_proto',
dict(forwarded_protocol='http',
secure_proxy_ssl_header=None,
headers={}))]
def test_ssl_middleware(self):
if self.secure_proxy_ssl_header:
cfg.CONF.set_override('secure_proxy_ssl_header',
self.secure_proxy_ssl_header)
middleware = ssl.SSLMiddleware(None)
request = webob.Request.blank('/stacks', headers=self.headers)
self.assertIsNone(middleware.process_request(request))
self.assertEqual(self.forwarded_protocol,
request.environ['wsgi.url_scheme'])