summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-09-29 12:02:33 +0000
committerGerrit Code Review <review@openstack.org>2015-09-29 12:02:33 +0000
commit50755b121205e2a2663729ec2c0b278cf8ae1ac2 (patch)
treee2fc79ba5f223b36b28b2d2dff777632e9dc8946
parent68a97abff9d39206d8e373991b0b37ca92fa7d8f (diff)
parent89b11a1c731bea4c74498c46a5e4272eef55f2f6 (diff)
downloadoslo-middleware-50755b121205e2a2663729ec2c0b278cf8ae1ac2.tar.gz
Merge "Introduce HTTP Proxy to WSGI middleware"
-rw-r--r--oslo_middleware/http_proxy_to_wsgi.py70
-rw-r--r--oslo_middleware/tests/test_http_proxy_to_wsgi.py91
2 files changed, 161 insertions, 0 deletions
diff --git a/oslo_middleware/http_proxy_to_wsgi.py b/oslo_middleware/http_proxy_to_wsgi.py
new file mode 100644
index 0000000..425f414
--- /dev/null
+++ b/oslo_middleware/http_proxy_to_wsgi.py
@@ -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']
diff --git a/oslo_middleware/tests/test_http_proxy_to_wsgi.py b/oslo_middleware/tests/test_http_proxy_to_wsgi.py
new file mode 100644
index 0000000..bbd3601
--- /dev/null
+++ b/oslo_middleware/tests/test_http_proxy_to_wsgi.py
@@ -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)