Merge "Introduce HTTP Proxy to WSGI middleware"

This commit is contained in:
Jenkins 2015-09-29 12:02:33 +00:00 committed by Gerrit Code Review
commit 50755b1212
2 changed files with 161 additions and 0 deletions

View 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']

View 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)