Merge "New middleware to handle SSL termination proxies"
This commit is contained in:
commit
1b723d0c00
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
# heat-api pipeline
|
# heat-api pipeline
|
||||||
[pipeline:heat-api]
|
[pipeline:heat-api]
|
||||||
pipeline = faultwrap versionnegotiation authtoken context apiv1app
|
pipeline = faultwrap ssl versionnegotiation authtoken context apiv1app
|
||||||
|
|
||||||
# heat-api pipeline for standalone heat
|
# heat-api pipeline for standalone heat
|
||||||
# ie. uses alternative auth backend that authenticates users against keystone
|
# ie. uses alternative auth backend that authenticates users against keystone
|
||||||
@ -12,7 +12,7 @@ pipeline = faultwrap versionnegotiation authtoken context apiv1app
|
|||||||
# flavor = standalone
|
# flavor = standalone
|
||||||
#
|
#
|
||||||
[pipeline:heat-api-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
|
# heat-api pipeline for custom cloud backends
|
||||||
# i.e. in heat.conf:
|
# i.e. in heat.conf:
|
||||||
@ -74,6 +74,10 @@ paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory
|
|||||||
[filter:ec2authtoken]
|
[filter:ec2authtoken]
|
||||||
paste.filter_factory = heat.api.aws.ec2token:EC2Token_filter_factory
|
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
|
# Auth middleware that validates token against keystone
|
||||||
[filter:authtoken]
|
[filter:authtoken]
|
||||||
paste.filter_factory = heat.common.auth_token:filter_factory
|
paste.filter_factory = heat.common.auth_token:filter_factory
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
[DEFAULT]
|
[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
|
# Options defined in heat.common.config
|
||||||
#
|
#
|
||||||
|
41
heat/api/middleware/ssl.py
Normal file
41
heat/api/middleware/ssl.py
Normal 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'])
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
from heat.api.middleware.version_negotiation import VersionNegotiationFilter
|
from heat.api.middleware.version_negotiation import VersionNegotiationFilter
|
||||||
from heat.api.middleware.fault import FaultWrapper
|
from heat.api.middleware.fault import FaultWrapper
|
||||||
|
from heat.api.middleware.ssl import SSLMiddleware
|
||||||
from heat.api.openstack import versions
|
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):
|
def faultwrap_filter(app, conf, **local_conf):
|
||||||
return FaultWrapper(app)
|
return FaultWrapper(app)
|
||||||
|
|
||||||
|
|
||||||
|
def sslmiddleware_filter(app, conf, **local_conf):
|
||||||
|
return SSLMiddleware(app)
|
||||||
|
46
heat/tests/test_ssl_middleware.py
Normal file
46
heat/tests/test_ssl_middleware.py
Normal 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'])
|
Loading…
Reference in New Issue
Block a user