diff options
author | Jenkins <jenkins@review.openstack.org> | 2015-09-29 12:02:33 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2015-09-29 12:02:33 +0000 |
commit | 50755b121205e2a2663729ec2c0b278cf8ae1ac2 (patch) | |
tree | e2fc79ba5f223b36b28b2d2dff777632e9dc8946 | |
parent | 68a97abff9d39206d8e373991b0b37ca92fa7d8f (diff) | |
parent | 89b11a1c731bea4c74498c46a5e4272eef55f2f6 (diff) | |
download | oslo-middleware-50755b121205e2a2663729ec2c0b278cf8ae1ac2.tar.gz |
Merge "Introduce HTTP Proxy to WSGI middleware"
-rw-r--r-- | oslo_middleware/http_proxy_to_wsgi.py | 70 | ||||
-rw-r--r-- | oslo_middleware/tests/test_http_proxy_to_wsgi.py | 91 |
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) |