Add http_proxy_to_wsgi middleware
This sets up the HTTPProxyToWSGI middleware in front of Mistral API. The purpose of this middleware is to set up the request URL correctly in the case there is a proxy (for instance, a loadbalancer such as HAProxy) in front of the Mistral API. The HTTPProxyToWSGI is off by default and needs to be enabled via a configuration value. It can be enabled with the option in mistral.conf: [oslo_middleware] enable_proxy_headers_parsing=True Closes-Bug: #1590608 Closes-Bug: #1816364 Change-Id: I04ba85488b27cb05c3b81ad8c973c3cc3fe56d36
This commit is contained in:
parent
608367f28d
commit
ca1acb656c
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import oslo_middleware.cors as cors_middleware
|
import oslo_middleware.cors as cors_middleware
|
||||||
|
import oslo_middleware.http_proxy_to_wsgi as http_proxy_to_wsgi_middleware
|
||||||
import osprofiler.web
|
import osprofiler.web
|
||||||
import pecan
|
import pecan
|
||||||
|
|
||||||
@ -82,6 +83,9 @@ def setup_app(config=None):
|
|||||||
enabled=cfg.CONF.profiler.enabled
|
enabled=cfg.CONF.profiler.enabled
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create HTTPProxyToWSGI wrapper
|
||||||
|
app = http_proxy_to_wsgi_middleware.HTTPProxyToWSGI(app, cfg.CONF)
|
||||||
|
|
||||||
# Create a CORS wrapper, and attach mistral-specific defaults that must be
|
# Create a CORS wrapper, and attach mistral-specific defaults that must be
|
||||||
# included in all CORS responses.
|
# included in all CORS responses.
|
||||||
return cors_middleware.CORS(app, cfg.CONF)
|
return cors_middleware.CORS(app, cfg.CONF)
|
||||||
|
@ -67,7 +67,7 @@ class RootController(object):
|
|||||||
def index(self):
|
def index(self):
|
||||||
LOG.debug("Fetching API versions.")
|
LOG.debug("Fetching API versions.")
|
||||||
|
|
||||||
host_url_v2 = '%s/%s' % (pecan.request.host_url, 'v2')
|
host_url_v2 = '%s/%s' % (pecan.request.application_url, 'v2')
|
||||||
api_v2 = APIVersion(
|
api_v2 = APIVersion(
|
||||||
id='v2.0',
|
id='v2.0',
|
||||||
status='CURRENT',
|
status='CURRENT',
|
||||||
|
@ -59,4 +59,5 @@ class Controller(object):
|
|||||||
|
|
||||||
@wsme_pecan.wsexpose(RootResource)
|
@wsme_pecan.wsexpose(RootResource)
|
||||||
def index(self):
|
def index(self):
|
||||||
return RootResource(uri='%s/%s' % (pecan.request.host_url, 'v2'))
|
return RootResource(uri='%s/%s' % (pecan.request.application_url,
|
||||||
|
'v2'))
|
||||||
|
@ -31,7 +31,7 @@ from mistral import utils
|
|||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
_CTX_THREAD_LOCAL_NAME = "MISTRAL_APP_CTX_THREAD_LOCAL"
|
_CTX_THREAD_LOCAL_NAME = "MISTRAL_APP_CTX_THREAD_LOCAL"
|
||||||
ALLOWED_WITHOUT_AUTH = ['/', '/v2/']
|
ALLOWED_WITHOUT_AUTH = ['/', '/v2/', '/workflowv2/', '/workflowv2/v2/']
|
||||||
|
|
||||||
|
|
||||||
class MistralContext(oslo_context.RequestContext):
|
class MistralContext(oslo_context.RequestContext):
|
||||||
|
42
mistral/tests/unit/api/test_oslo_middleware.py
Normal file
42
mistral/tests/unit/api/test_oslo_middleware.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Tests http_proxy_to_wsgi middleware."""
|
||||||
|
|
||||||
|
from mistral.tests.unit.api import base
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_middleware import http_proxy_to_wsgi as http_proxy_to_wsgi_middleware
|
||||||
|
|
||||||
|
|
||||||
|
class TestHTTPProxyToWSGIMiddleware(base.APITest):
|
||||||
|
"""Test oslo_middleware HTTPProxyToWSGI.
|
||||||
|
|
||||||
|
It checks that oslo_middleware middleware HTTPProxyToWSGI is executed
|
||||||
|
when enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Make sure the HTTPProxyToWSGI options are registered
|
||||||
|
cfg.CONF.register_opts(http_proxy_to_wsgi_middleware.OPTS,
|
||||||
|
'oslo_middleware')
|
||||||
|
|
||||||
|
# Enable proxy headers parsing in HTTPProxyToWSGI middleware.
|
||||||
|
self.override_config(
|
||||||
|
"enable_proxy_headers_parsing",
|
||||||
|
"True",
|
||||||
|
group='oslo_middleware'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the application.
|
||||||
|
super(TestHTTPProxyToWSGIMiddleware, self).setUp()
|
@ -15,6 +15,7 @@ from oslo_serialization import jsonutils
|
|||||||
|
|
||||||
from mistral.tests.unit.api import base
|
from mistral.tests.unit.api import base
|
||||||
from mistral.tests.unit.api import test_auth
|
from mistral.tests.unit.api import test_auth
|
||||||
|
from mistral.tests.unit.api import test_oslo_middleware
|
||||||
|
|
||||||
|
|
||||||
class TestRootController(base.APITest):
|
class TestRootController(base.APITest):
|
||||||
@ -71,3 +72,69 @@ class TestRootControllerWithAuth(test_auth.TestKeystoneMiddleware):
|
|||||||
'http://localhost/v2',
|
'http://localhost/v2',
|
||||||
data['uri']
|
data['uri']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRootControllerWithHTTPProxyToWSGI(test_oslo_middleware.
|
||||||
|
TestHTTPProxyToWSGIMiddleware):
|
||||||
|
def test_index(self):
|
||||||
|
resp = self.app.get('/', headers={'Accept': 'application/json',
|
||||||
|
'Host': 'localhost'})
|
||||||
|
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
|
||||||
|
data = jsonutils.loads(resp.body.decode())
|
||||||
|
data = data['versions']
|
||||||
|
self.assertEqual('v2.0', data[0]['id'])
|
||||||
|
self.assertEqual('CURRENT', data[0]['status'])
|
||||||
|
self.assertEqual(
|
||||||
|
[{'href': 'http://localhost/v2', 'rel': 'self', 'target': 'v2'}],
|
||||||
|
data[0]['links']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_v2_root(self):
|
||||||
|
resp = self.app.get('/v2/', headers={'Accept': 'application/json',
|
||||||
|
'Host': 'localhost'})
|
||||||
|
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
|
||||||
|
data = jsonutils.loads(resp.body.decode())
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
'http://localhost/v2',
|
||||||
|
data['uri']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_index_with_prefix(self):
|
||||||
|
resp = self.app.get('/',
|
||||||
|
headers={'Accept': 'application/json',
|
||||||
|
'Host': 'openstack',
|
||||||
|
'X-Forwarded-Proto': 'https',
|
||||||
|
'X-Forwarded-Prefix': '/workflowv2'})
|
||||||
|
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
|
||||||
|
data = jsonutils.loads(resp.body.decode())
|
||||||
|
data = data['versions']
|
||||||
|
self.assertEqual('v2.0', data[0]['id'])
|
||||||
|
self.assertEqual('CURRENT', data[0]['status'])
|
||||||
|
self.assertEqual(
|
||||||
|
[{'href': 'https://openstack/workflowv2/v2', 'rel': 'self',
|
||||||
|
'target': 'v2'}],
|
||||||
|
data[0]['links']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_v2_root_with_prefix(self):
|
||||||
|
resp = self.app.get('/v2/',
|
||||||
|
headers={'Accept': 'application/json',
|
||||||
|
'Host': 'openstack',
|
||||||
|
'X-Forwarded-Proto': 'https',
|
||||||
|
'X-Forwarded-Prefix': '/workflowv2'})
|
||||||
|
|
||||||
|
self.assertEqual(200, resp.status_int)
|
||||||
|
|
||||||
|
data = jsonutils.loads(resp.body.decode())
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
'https://openstack/workflowv2/v2',
|
||||||
|
data['uri']
|
||||||
|
)
|
||||||
|
@ -235,7 +235,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
|
|||||||
return list_cls.convert_with_links(
|
return list_cls.convert_with_links(
|
||||||
rest_resources,
|
rest_resources,
|
||||||
limit,
|
limit,
|
||||||
pecan.request.host_url,
|
pecan.request.application_url,
|
||||||
sort_keys=','.join(sort_keys),
|
sort_keys=','.join(sort_keys),
|
||||||
sort_dirs=','.join(sort_dirs),
|
sort_dirs=','.join(sort_dirs),
|
||||||
fields=','.join(fields) if fields else '',
|
fields=','.join(fields) if fields else '',
|
||||||
|
@ -3,6 +3,7 @@ namespace = mistral.config
|
|||||||
namespace = oslo.db
|
namespace = oslo.db
|
||||||
namespace = oslo.messaging
|
namespace = oslo.messaging
|
||||||
namespace = oslo.middleware.cors
|
namespace = oslo.middleware.cors
|
||||||
|
namespace = oslo.middleware.http_proxy_to_wsgi
|
||||||
namespace = keystonemiddleware.auth_token
|
namespace = keystonemiddleware.auth_token
|
||||||
namespace = periodic.config
|
namespace = periodic.config
|
||||||
namespace = oslo.log
|
namespace = oslo.log
|
||||||
|
Loading…
Reference in New Issue
Block a user