Skip to content

Commit

Permalink
Switch from oslo.middleware to internal proxy converter
Browse files Browse the repository at this point in the history
  • Loading branch information
jd authored and mergify[bot] committed Oct 27, 2018
1 parent c1c1e28 commit bcd9a8c
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 10 deletions.
1 change: 0 additions & 1 deletion gnocchi/gnocchi-config-generator.conf
Expand Up @@ -3,7 +3,6 @@ wrap_width = 79
namespace = gnocchi
namespace = oslo.middleware.cors
namespace = oslo.middleware.healthcheck
namespace = oslo.middleware.http_proxy_to_wsgi
namespace = oslo.policy
namespace = cotyledon
namespace = keystonemiddleware.auth_token
3 changes: 2 additions & 1 deletion gnocchi/opts.py
Expand Up @@ -22,6 +22,7 @@
import gnocchi.archive_policy
import gnocchi.common.redis
import gnocchi.indexer
import gnocchi.rest.http_proxy_to_wsgi
import gnocchi.storage
import gnocchi.storage.ceph
import gnocchi.storage.file
Expand Down Expand Up @@ -192,7 +193,7 @@ def list_opts():
default=10, min=0,
help='Number of seconds before timeout when attempting '
'to do some operations.'),
) + API_OPTS,
) + API_OPTS + gnocchi.rest.http_proxy_to_wsgi.OPTS,
),
("storage", _STORAGE_OPTS),
("incoming", _INCOMING_OPTS),
Expand Down
10 changes: 3 additions & 7 deletions gnocchi/rest/api-paste.ini
Expand Up @@ -17,13 +17,13 @@ use = egg:Paste#urlmap
/healthcheck = healthcheck

[pipeline:gnocchiv1+noauth]
pipeline = http_proxy_to_wsgi gnocchiv1
pipeline = gnocchiv1

[pipeline:gnocchiv1+keystone]
pipeline = http_proxy_to_wsgi keystone_authtoken gnocchiv1
pipeline = keystone_authtoken gnocchiv1

[pipeline:gnocchiversions_pipeline]
pipeline = http_proxy_to_wsgi gnocchiversions
pipeline = gnocchiversions

[app:gnocchiversions]
paste.app_factory = gnocchi.rest.app:app_factory
Expand All @@ -37,10 +37,6 @@ root = gnocchi.rest.api.V1Controller
use = egg:keystonemiddleware#auth_token
oslo_config_project = gnocchi

[filter:http_proxy_to_wsgi]
use = egg:oslo.middleware#http_proxy_to_wsgi
oslo_config_project = gnocchi

[app:healthcheck]
use = egg:oslo.middleware#healthcheck
oslo_config_project = gnocchi
4 changes: 3 additions & 1 deletion gnocchi/rest/app.py
Expand Up @@ -35,6 +35,7 @@
from gnocchi import incoming as gnocchi_incoming
from gnocchi import indexer as gnocchi_indexer
from gnocchi import json
from gnocchi.rest import http_proxy_to_wsgi
from gnocchi import storage as gnocchi_storage


Expand Down Expand Up @@ -178,7 +179,8 @@ def load_app(conf, not_implemented_middleware=True):
appname = "gnocchi+" + conf.api.auth_mode
app = deploy.loadapp("config:" + cfg_path, name=appname,
global_conf={'configkey': configkey})
return cors.CORS(app, conf=conf)
return http_proxy_to_wsgi.HTTPProxyToWSGI(
cors.CORS(app, conf=conf), conf=conf)


def _setup_app(root, conf, not_implemented_middleware):
Expand Down
116 changes: 116 additions & 0 deletions gnocchi/rest/http_proxy_to_wsgi.py
@@ -0,0 +1,116 @@
# -*- 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

import webob.dec
import webob.request
import webob.response


OPTS = (
cfg.BoolOpt('enable_proxy_headers_parsing',
deprecated_group="oslo_middleware",
default=False,
help="Whether the application is behind a proxy or not. "
"This determines if the middleware should parse the "
"headers or not."),
)


class NoContentTypeResponse(webob.response.Response):

default_content_type = None # prevents webob assigning content type


class NoContentTypeRequest(webob.request.Request):

ResponseClass = NoContentTypeResponse


class HTTPProxyToWSGI(object):
"""HTTP proxy to WSGI termination middleware.
This middleware overloads WSGI environment variables with the one provided
by the remote HTTP reverse proxy.
"""

def __init__(self, application, conf=None):
"""Base middleware constructor
:param conf: a cfg.ConfigOpts object
"""
self.application = application
self.oslo_conf = conf

@webob.dec.wsgify(RequestClass=NoContentTypeRequest)
def __call__(self, req):
self.process_request(req)
return req.get_response(self.application)

@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().strip()] = value.strip()
result.append(entry)
return result

def process_request(self, req):
if not self.oslo_conf.api.enable_proxy_headers_parsing:
return
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

forwarded_for = proxy.get("for")
if forwarded_for:
req.environ['REMOTE_ADDR'] = forwarded_for

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

forwarded_for = req.environ.get("HTTP_X_FORWARDED_FOR")
if forwarded_for:
req.environ['REMOTE_ADDR'] = forwarded_for

v = req.environ.get("HTTP_X_FORWARDED_PREFIX")
if v:
req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME']
2 changes: 2 additions & 0 deletions gnocchi/tests/functional/fixtures.py
Expand Up @@ -171,6 +171,8 @@ def start_fixture(self):
# Set pagination to a testable value
conf.set_override('max_limit', 7, 'api')

conf.set_override('enable_proxy_headers_parsing', True, group="api")

self.index = index

self.coord = metricd.get_coordinator_and_start(str(uuid.uuid4()),
Expand Down
16 changes: 16 additions & 0 deletions gnocchi/tests/functional/gabbits/http-proxy-to-wsgi.yaml
@@ -0,0 +1,16 @@
fixtures:
- ConfigFixture

defaults:
request_headers:
content-type: application/json
# User foobar
authorization: "basic Zm9vYmFyOg=="

tests:
- name: test HTTP proxy headers
GET: /
request_headers:
Forwarded: for=192.0.2.60;proto=http;host=foobar
response_json_paths:
$.versions[0].links[0].href: http://foobar/gnocchi/v1/

0 comments on commit bcd9a8c

Please sign in to comment.