Use oslo_middleware to support CORS configuration

Currently there is no way in Qinling configuration file
to configure the CORS.

oslo_middleware should be implemented to be able to
set [cors] section in qinling.conf.

Change-Id: Ib4664ba7cbda28a9dfda1c1dd9f0e7457c0ee7de
Story: 2005788
Task: 33515
This commit is contained in:
Gaetan Trellu 2019-05-28 17:58:40 -04:00
parent 02b957f92e
commit e18f80345a
13 changed files with 242 additions and 8 deletions

View File

@ -1,7 +1,10 @@
# The order of packages is significant, because pip processes them in the order # The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration # of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later. # process, which may cause wedges in the gate later.
# this is required for the docs build jobs
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD sphinx>=1.6.2,<2.0.0;python_version=='2.7' # BSD
openstackdocstheme>=1.24.0 # Apache-2.0 sphinx>=1.6.2;python_version>='3.4' # BSD
reno>=2.5.0 # Apache-2.0 sphinxcontrib-apidoc>=0.2.0 # BSD
openstackdocstheme>=1.11.0 # Apache-2.0
reno>=1.8.0 # Apache-2.0
os-api-ref>=1.0.0 # Apache-2.0

View File

@ -67,6 +67,7 @@ oslo.serialization==2.18.0
oslo.service==1.24.0 oslo.service==1.24.0
oslo.upgradecheck==0.1.0 oslo.upgradecheck==0.1.0
oslo.utils==3.33.0 oslo.utils==3.33.0
oslosphinx==4.7.0
oslotest==3.2.0 oslotest==3.2.0
paramiko==2.4.1 paramiko==2.4.1
Paste==2.0.3 Paste==2.0.3
@ -106,6 +107,10 @@ setproctitle==1.1.10
setuptools==21.0.0 setuptools==21.0.0
simplegeneric==0.8.1 simplegeneric==0.8.1
six==1.10.0 six==1.10.0
Sphinx==1.6.2
sphinxcontrib-httpdomain==1.3.0
sphinxcontrib-pecanwsme==0.10.0
sphinxcontrib-websupport==1.0.1
SQLAlchemy==1.0.10 SQLAlchemy==1.0.10
sqlalchemy-migrate==0.11.0 sqlalchemy-migrate==0.11.0
sqlparse==0.2.4 sqlparse==0.2.4

View File

@ -14,9 +14,12 @@
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import oslo_middleware.cors as cors_middleware
import oslo_middleware.http_proxy_to_wsgi as http_proxy_to_wsgi_middleware
import pecan import pecan
from qinling.api import access_control from qinling.api import access_control
from qinling import config as q_config
from qinling import context as ctx from qinling import context as ctx
from qinling.db import api as db_api from qinling.db import api as db_api
from qinling.services import periodics from qinling.services import periodics
@ -43,6 +46,9 @@ def get_pecan_config():
def setup_app(config=None): def setup_app(config=None):
if not config: if not config:
config = get_pecan_config() config = get_pecan_config()
q_config.set_config_defaults()
app_conf = dict(config.app) app_conf = dict(config.app)
db_api.setup_db() db_api.setup_db()
@ -61,4 +67,18 @@ def setup_app(config=None):
# Set up access control. # Set up access control.
app = access_control.setup(app) app = access_control.setup(app)
return app # 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
# included in all CORS responses.
return cors_middleware.CORS(app, cfg.CONF)
def init_wsgi():
# By default, oslo.config parses the CLI args if no args is provided.
# As a result, invoking this wsgi script from gunicorn leads to the error
# with argparse complaining that the CLI options have already been parsed.
q_config.parse_args(args=[])
return setup_app()

View File

@ -67,7 +67,7 @@ class RootController(object):
def index(self): def index(self):
LOG.info("Fetching API versions.") LOG.info("Fetching API versions.")
host_url_v1 = '%s/%s' % (pecan.request.host_url, 'v1') host_url_v1 = '%s/%s' % (pecan.request.application_url, 'v1')
api_v1 = APIVersion( api_v1 = APIVersion(
id='v1.0', id='v1.0',
status='CURRENT', status='CURRENT',

View File

@ -43,4 +43,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, 'v1')) return RootResource(uri='%s/%s' % (pecan.request.application_url,
'v1'))

17
qinling/api/wsgi.py Normal file
View File

@ -0,0 +1,17 @@
# Copyright 2019 - Ormuco, 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 qinling.api import app
application = app.init_wsgi()

View File

@ -16,6 +16,7 @@ from keystonemiddleware import auth_token
from oslo_concurrency import processutils from oslo_concurrency import processutils
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
from oslo_middleware import cors
from qinling import version from qinling import version
@ -335,3 +336,34 @@ def parse_args(args=None, usage=None, default_config_files=None):
usage=usage, usage=usage,
default_config_files=default_config_files default_config_files=default_config_files
) )
def set_config_defaults():
"""This method updates all configuration default values."""
set_cors_middleware_defaults()
def set_cors_middleware_defaults():
"""Update default configuration options for oslo.middleware."""
cors.set_defaults(
allow_headers=['X-Auth-Token',
'X-Identity-Status',
'X-Roles',
'X-Service-Catalog',
'X-User-Id',
'X-Tenant-Id',
'X-Project-Id',
'X-User-Name',
'X-Project-Name'],
allow_methods=['GET',
'PUT',
'POST',
'DELETE',
'PATCH'],
expose_headers=['X-Auth-Token',
'X-Subject-Token',
'X-Service-Token',
'X-Project-Id',
'X-User-Name',
'X-Project-Name']
)

View File

@ -0,0 +1,96 @@
# 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 cors middleware."""
from oslo_config import cfg
from oslo_middleware import cors as cors_middleware
from qinling.tests.unit.api import base
class TestCORSMiddleware(base.APITest):
"""Provide a basic smoke test to ensure CORS middleware is active.
The tests below provide minimal confirmation that the CORS middleware
is active, and may be configured. For comprehensive tests, please consult
the test suite in oslo_middleware.
"""
def setUp(self):
# Make sure the CORS options are registered
cfg.CONF.register_opts(cors_middleware.CORS_OPTS, 'cors')
# Load up our valid domain values before the application is created.
self.override_config(
"allowed_origin",
"http://valid.example.com",
group='cors'
)
# Create the application.
super(TestCORSMiddleware, self).setUp()
def test_valid_cors_options_request(self):
response = self.app.options(
'/',
headers={
'Origin': 'http://valid.example.com',
'Access-Control-Request-Method': 'GET'
}
)
self.assertEqual(200, response.status_code)
self.assertIn('access-control-allow-origin', response.headers)
self.assertEqual(
'http://valid.example.com',
response.headers['access-control-allow-origin']
)
def test_invalid_cors_options_request(self):
response = self.app.options(
'/',
headers={
'Origin': 'http://invalid.example.com',
'Access-Control-Request-Method': 'GET'
}
)
self.assertEqual(200, response.status_code)
self.assertNotIn('access-control-allow-origin', response.headers)
def test_valid_cors_get_request(self):
response = self.app.get(
'/',
headers={
'Origin': 'http://valid.example.com'
}
)
self.assertEqual(200, response.status_code)
self.assertIn('access-control-allow-origin', response.headers)
self.assertEqual(
'http://valid.example.com',
response.headers['access-control-allow-origin']
)
def test_invalid_cors_get_request(self):
response = self.app.get(
'/',
headers={
'Origin': 'http://invalid.example.com'
}
)
self.assertEqual(200, response.status_code)
self.assertNotIn('access-control-allow-origin', response.headers)

View 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 oslo_config import cfg
from oslo_middleware import http_proxy_to_wsgi as http_proxy_to_wsgi_middleware
from qinling.tests.unit.api import base
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()

View File

@ -0,0 +1,9 @@
---
features:
- |
Add CORS and HTTPProxyToWSGI support based on oslo_middleware in front
of the Qinling 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 Qinling API.
The HTTPProxyToWSGI is off by default and needs to be enabled via a
configuration value.

View File

@ -10,6 +10,7 @@ oslo.concurrency>=3.26.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0
oslo.db>=4.27.0 # Apache-2.0 oslo.db>=4.27.0 # Apache-2.0
oslo.messaging>=5.29.0 # Apache-2.0 oslo.messaging>=5.29.0 # Apache-2.0
oslo.middleware>=3.35.0 # Apache-2.0
oslo.policy>=1.30.0 # Apache-2.0 oslo.policy>=1.30.0 # Apache-2.0
oslo.upgradecheck>=0.1.0 # Apache-2.0 oslo.upgradecheck>=0.1.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0

View File

@ -30,8 +30,11 @@ console_scripts =
qinling-db-manage = qinling.db.sqlalchemy.migration.cli:main qinling-db-manage = qinling.db.sqlalchemy.migration.cli:main
qinling-status = qinling.cmd.status:main qinling-status = qinling.cmd.status:main
wsgi_scripts =
qinling-wsgi-api = qinling.api.app:init_wsgi
qinling.storage.provider: qinling.storage.provider:
local = qinling.storage.file_system:FileSystemStorage local = qinling.storage.file_system:FileSystemStorage
qinling.orchestrator = qinling.orchestrator =
kubernetes = qinling.orchestrator.kubernetes.manager:KubernetesManager kubernetes = qinling.orchestrator.kubernetes.manager:KubernetesManager
@ -39,6 +42,9 @@ qinling.orchestrator =
oslo.config.opts = oslo.config.opts =
qinling.config = qinling.config:list_opts qinling.config = qinling.config:list_opts
oslo.config.opts.defaults =
qinling.config = qinling.config:set_cors_middleware_defaults
tempest.test_plugins = tempest.test_plugins =
qinling_test = qinling_tempest_plugin.plugin:QinlingTempestPlugin qinling_test = qinling_tempest_plugin.plugin:QinlingTempestPlugin

View File

@ -2,6 +2,8 @@
namespace = qinling.config namespace = qinling.config
namespace = keystonemiddleware.auth_token namespace = keystonemiddleware.auth_token
namespace = oslo.messaging namespace = oslo.messaging
namespace = oslo.middleware.cors
namespace = oslo.middleware.http_proxy_to_wsgi
namespace = oslo.log namespace = oslo.log
namespace = oslo.policy namespace = oslo.policy
namespace = oslo.db namespace = oslo.db