Merge "Introduce HTTP Proxy to WSGI middleware"
This commit is contained in:
commit
50755b1212
70
oslo_middleware/http_proxy_to_wsgi.py
Normal file
70
oslo_middleware/http_proxy_to_wsgi.py
Normal file
@ -0,0 +1,70 @@
|
||||
# -*- 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_middleware import base
|
||||
|
||||
|
||||
class HTTPProxyToWSGIMiddleware(base.ConfigurableMiddleware):
|
||||
"""HTTP proxy to WSGI termination middleware.
|
||||
|
||||
This middleware overloads WSGI environment variables with the one provided
|
||||
by the remote HTTP reverse proxy.
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _parse_rfc7239_header(header):
|
||||
"""Parses RFC7239 Forward headers.
|
||||
|
||||
e.g. for=192.0.2.60;proto=http, for=192.0.2.60;by=203.0.113.43
|
||||
|
||||
"""
|
||||
result = []
|
||||
for proxy in header.split(","):
|
||||
entry = {}
|
||||
for d in proxy.split(";"):
|
||||
key, _, value = d.partition("=")
|
||||
entry[key.lower()] = value
|
||||
result.append(entry)
|
||||
return result
|
||||
|
||||
def process_request(self, req):
|
||||
fwd_hdr = req.environ.get("HTTP_FORWARDED")
|
||||
if fwd_hdr:
|
||||
proxies = self._parse_rfc7239_header(fwd_hdr)
|
||||
# Let's use the value from the first proxy
|
||||
if proxies:
|
||||
proxy = proxies[0]
|
||||
|
||||
forwarded_proto = proxy.get("proto")
|
||||
if forwarded_proto:
|
||||
req.environ['wsgi.url_scheme'] = forwarded_proto
|
||||
|
||||
forwarded_host = proxy.get("host")
|
||||
if forwarded_host:
|
||||
req.environ['HTTP_HOST'] = forwarded_host
|
||||
|
||||
else:
|
||||
# World before RFC7239
|
||||
forwarded_proto = req.environ.get("HTTP_X_FORWARDED_PROTO")
|
||||
if forwarded_proto:
|
||||
req.environ['wsgi.url_scheme'] = forwarded_proto
|
||||
|
||||
forwarded_host = req.environ.get("HTTP_X_FORWARDED_HOST")
|
||||
if forwarded_host:
|
||||
req.environ['HTTP_HOST'] = forwarded_host
|
||||
|
||||
v = req.environ.get("HTTP_X_FORWARDED_PREFIX")
|
||||
if v:
|
||||
req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME']
|
91
oslo_middleware/tests/test_http_proxy_to_wsgi.py
Normal file
91
oslo_middleware/tests/test_http_proxy_to_wsgi.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Copyright (c) 2015 Red Hat, Inc.
|
||||
#
|
||||
# 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 wsgiref import util
|
||||
|
||||
from oslotest import base as test_base
|
||||
import webob
|
||||
|
||||
from oslo_middleware import http_proxy_to_wsgi
|
||||
|
||||
|
||||
class TestHTTPProxyToWSGI(test_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHTTPProxyToWSGI, self).setUp()
|
||||
|
||||
@webob.dec.wsgify()
|
||||
def fake_app(req):
|
||||
return util.application_uri(req.environ)
|
||||
|
||||
self.middleware = http_proxy_to_wsgi.HTTPProxyToWSGIMiddleware(
|
||||
fake_app)
|
||||
self.request = webob.Request.blank('/foo/bar', method='POST')
|
||||
|
||||
def test_no_headers(self):
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"http://localhost:80/", response.body)
|
||||
|
||||
def test_url_translate_ssl(self):
|
||||
self.request.headers['X-Forwarded-Proto'] = "https"
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"https://localhost:80/", response.body)
|
||||
|
||||
def test_url_translate_ssl_port(self):
|
||||
self.request.headers['X-Forwarded-Proto'] = "https"
|
||||
self.request.headers['X-Forwarded-Host'] = "example.com:123"
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"https://example.com:123/", response.body)
|
||||
|
||||
def test_url_translate_host_ipv6(self):
|
||||
self.request.headers['X-Forwarded-Proto'] = "https"
|
||||
self.request.headers['X-Forwarded-Host'] = "[f00:b4d::1]:123"
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"https://[f00:b4d::1]:123/", response.body)
|
||||
|
||||
def test_url_translate_base(self):
|
||||
self.request.headers['X-Forwarded-Prefix'] = "/bla"
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"http://localhost:80/bla", response.body)
|
||||
|
||||
def test_url_translate_port_and_base_and_proto_and_host(self):
|
||||
self.request.headers['X-Forwarded-Proto'] = "https"
|
||||
self.request.headers['X-Forwarded-Prefix'] = "/bla"
|
||||
self.request.headers['X-Forwarded-Host'] = "example.com:8043"
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"https://example.com:8043/bla", response.body)
|
||||
|
||||
def test_rfc7239_invalid(self):
|
||||
self.request.headers['Forwarded'] = (
|
||||
"iam=anattacker;metoo, I will crash you!!P;m,xx")
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"http://localhost:80/", response.body)
|
||||
|
||||
def test_rfc7239_proto(self):
|
||||
self.request.headers['Forwarded'] = (
|
||||
"for=foobar;proto=https, for=foobaz;proto=http")
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"https://localhost:80/", response.body)
|
||||
|
||||
def test_rfc7239_proto_host(self):
|
||||
self.request.headers['Forwarded'] = (
|
||||
"for=foobar;proto=https;host=example.com, for=foobaz;proto=http")
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"https://example.com/", response.body)
|
||||
|
||||
def test_rfc7239_proto_host_base(self):
|
||||
self.request.headers['Forwarded'] = (
|
||||
"for=foobar;proto=https;host=example.com:8043, for=foobaz")
|
||||
self.request.headers['X-Forwarded-Prefix'] = "/bla"
|
||||
response = self.request.get_response(self.middleware)
|
||||
self.assertEqual(b"https://example.com:8043/bla", response.body)
|
Loading…
Reference in New Issue
Block a user