Use HTTPProxyToWSGI middleware from oslo
currently it is impossible to use ironic-api for both internal and public api at the same time when both of those are using (ssl terminating) proxies as there's only one config option to override the resource url's in responses ([api]public_endpoint). This patch adds the http_proxy_to_wsgi middleware from oslo.middleware to the ironic API service, which, with properly configured proxies, makes the choice of correct URL automatic, and thus makes such scenario possible. As this middleware may potentially not properly handle some endpoint URL schemas, leave the api.public_endpoint option as a backup, but it will be ignored when proxy headers parsing is enabled. Change-Id: I3ce6b0726b479c2835f8777957b2cb12d8098aec Story: #2006303 Task: #36019
This commit is contained in:
parent
de31b6ada3
commit
5e8c966a40
@ -1359,9 +1359,9 @@ function configure_ironic_api {
|
||||
configure_auth_token_middleware $IRONIC_CONF_FILE ironic $IRONIC_AUTH_CACHE_DIR/api
|
||||
|
||||
if [[ "$IRONIC_USE_WSGI" == "True" ]]; then
|
||||
iniset $IRONIC_CONF_FILE api public_endpoint $IRONIC_SERVICE_PROTOCOL://$IRONIC_HOSTPORT
|
||||
iniset $IRONIC_CONF_FILE oslo_middleware enable_proxy_headers_parsing True
|
||||
elif is_service_enabled tls-proxy; then
|
||||
iniset $IRONIC_CONF_FILE api public_endpoint $IRONIC_SERVICE_PROTOCOL://$IRONIC_HOSTPORT
|
||||
iniset $IRONIC_CONF_FILE oslo_middleware enable_proxy_headers_parsing True
|
||||
iniset $IRONIC_CONF_FILE api port $IRONIC_SERVICE_PORT_INT
|
||||
else
|
||||
iniset $IRONIC_CONF_FILE api port $IRONIC_SERVICE_PORT
|
||||
|
@ -19,6 +19,7 @@ import keystonemiddleware.audit as audit_middleware
|
||||
from oslo_config import cfg
|
||||
import oslo_middleware.cors as cors_middleware
|
||||
from oslo_middleware import healthcheck
|
||||
from oslo_middleware import http_proxy_to_wsgi
|
||||
import osprofiler.web as osprofiler_web
|
||||
import pecan
|
||||
|
||||
@ -104,6 +105,10 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
||||
if CONF.profiler.enabled:
|
||||
app = osprofiler_web.WsgiMiddleware(app)
|
||||
|
||||
# NOTE(pas-ha) this registers oslo_middleware.enable_proxy_headers_parsing
|
||||
# option, when disabled (default) this is noop middleware
|
||||
app = http_proxy_to_wsgi.HTTPProxyToWSGI(app, CONF)
|
||||
|
||||
# add in the healthcheck middleware if enabled
|
||||
# NOTE(jroll) this is after the auth token middleware as we don't want auth
|
||||
# in front of this, and WSGI works from the outside in. Requests to
|
||||
|
@ -175,5 +175,8 @@ class PublicUrlHook(hooks.PecanHook):
|
||||
"""
|
||||
|
||||
def before(self, state):
|
||||
state.request.public_url = (cfg.CONF.api.public_endpoint
|
||||
or state.request.host_url)
|
||||
if cfg.CONF.oslo_middleware.enable_proxy_headers_parsing:
|
||||
state.request.public_url = state.request.application_url
|
||||
else:
|
||||
state.request.public_url = (cfg.CONF.api.public_endpoint
|
||||
or state.request.host_url)
|
||||
|
@ -36,7 +36,10 @@ opts = [
|
||||
" If None the links will be built using the request's "
|
||||
"host URL. If the API is operating behind a proxy, you "
|
||||
"will want to change this to represent the proxy's URL. "
|
||||
"Defaults to None.")),
|
||||
"Defaults to None. "
|
||||
"Ignored when proxy headers parsing is enabled via "
|
||||
"[oslo_middleware]enable_proxy_headers_parsing option.")
|
||||
),
|
||||
cfg.IntOpt('api_workers',
|
||||
help=_('Number of workers for OpenStack Ironic API service. '
|
||||
'The default is equal to the number of CPUs available '
|
||||
@ -48,8 +51,10 @@ opts = [
|
||||
"requests via HTTPS instead of HTTP. If there is a "
|
||||
"front-end service performing HTTPS offloading from "
|
||||
"the service, this option should be False; note, you "
|
||||
"will want to change public API endpoint to represent "
|
||||
"SSL termination URL with 'public_endpoint' option.")),
|
||||
"will want to enable proxy headers parsing with "
|
||||
"[oslo_middleware]enable_proxy_headers_parsing "
|
||||
"option or configure [api]public_endpoint option "
|
||||
"to set URLs in responses to the SSL terminated one.")),
|
||||
cfg.BoolOpt('restrict_lookup',
|
||||
default=True,
|
||||
help=_('Whether to restrict the lookup API to only nodes '
|
||||
|
54
ironic/tests/unit/api/test_proxy_middleware.py
Normal file
54
ironic/tests/unit/api/test_proxy_middleware.py
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
# 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 to assert that proxy headers middleware works as expected.
|
||||
"""
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic.tests.unit.api import base
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestProxyHeadersMiddleware(base.BaseApiTest):
|
||||
"""Provide a basic smoke test to ensure proxy headers middleware works."""
|
||||
|
||||
def setUp(self):
|
||||
CONF.set_override('public_endpoint', 'http://spam.ham/eggs',
|
||||
group='api')
|
||||
self.proxy_headers = {"X-Forwarded-Proto": "https",
|
||||
"X-Forwarded-Host": "mycloud.com",
|
||||
"X-Forwarded-Prefix": "/ironic"}
|
||||
super(TestProxyHeadersMiddleware, self).setUp()
|
||||
|
||||
def test_proxy_headers_enabled(self):
|
||||
"""Test enabled proxy headers middleware overriding public_endpoint"""
|
||||
# NOTE(pas-ha) setting config option and re-creating app
|
||||
# as the middleware registers its config option on instantiation
|
||||
CONF.set_override('enable_proxy_headers_parsing', True,
|
||||
group='oslo_middleware')
|
||||
self.app = self._make_app()
|
||||
response = self.get_json('/', path_prefix="",
|
||||
headers=self.proxy_headers)
|
||||
href = response["default_version"]["links"][0]["href"]
|
||||
self.assertTrue(href.startswith("https://mycloud.com/ironic"))
|
||||
|
||||
def test_proxy_headers_disabled(self):
|
||||
"""Test proxy headers middleware disabled by default"""
|
||||
response = self.get_json('/', path_prefix="",
|
||||
headers=self.proxy_headers)
|
||||
href = response["default_version"]["links"][0]["href"]
|
||||
# check that [api]public_endpoint is used when proxy headers parsing
|
||||
# is disabled
|
||||
self.assertTrue(href.startswith("http://spam.ham/eggs"))
|
@ -0,0 +1,13 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Ironic API service now supports HTTP proxy headers parsing
|
||||
with the help of oslo.middleware package, enabled via new option
|
||||
``[oslo_middleware]/enable_proxy_headers_parsing`` (``False`` by default).
|
||||
|
||||
This enables more complex setups of Ironic API service, for example when
|
||||
the same service instance serves both internal and public API endpoints
|
||||
via separate proxies.
|
||||
|
||||
When proxy headers parsing is enabled, the value of
|
||||
``[api]/public_endpoint`` option is ignored.
|
@ -13,6 +13,7 @@ namespace = oslo.db
|
||||
namespace = oslo.messaging
|
||||
namespace = oslo.middleware.cors
|
||||
namespace = oslo.middleware.healthcheck
|
||||
namespace = oslo.middleware.http_proxy_to_wsgi
|
||||
namespace = oslo.concurrency
|
||||
namespace = oslo.policy
|
||||
namespace = oslo.log
|
||||
|
Loading…
Reference in New Issue
Block a user