From e501f4013e0f902c995e510f4224d59bbd902c41 Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Tue, 21 Feb 2017 15:39:20 -0500 Subject: [PATCH] Support CORS Add the new middleware CORS for Zaqar It only supports for WSGI. Websocket doesn't need this feature. Change-Id: Ifc6d2d1c5dde5152cab6e3aa2f3cf9f207481267 Implements: blueprint support-cors --- devstack/plugin.sh | 5 + devstack/settings | 2 + doc/source/CORS.rst | 125 ++++++++++++++++++ doc/source/OSprofiler.rst | 4 +- doc/source/index.rst | 1 + etc/oslo-config-generator/zaqar.conf | 1 + .../notes/support-cors-af8349382a44aa0d.yaml | 4 + zaqar/common/configs.py | 13 +- zaqar/transport/middleware/cors.py | 55 ++++++++ zaqar/transport/wsgi/driver.py | 6 + 10 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 doc/source/CORS.rst create mode 100644 releasenotes/notes/support-cors-af8349382a44aa0d.yaml create mode 100644 zaqar/transport/middleware/cors.py diff --git a/devstack/plugin.sh b/devstack/plugin.sh index a7cd9f73f..aefe676e0 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -111,6 +111,11 @@ function configure_zaqar { iniset $ZAQAR_CONF DEFAULT pooling True iniset $ZAQAR_CONF 'pooling:catalog' enable_virtual_pool True + if [ "CORS_ENABLED" == 'true'] ; then + iniset $ZAQAR_CONF cors + iniset $ZAQAR_CONF 'cors' enabled True + fi + # NOTE(flaper87): Configure mongodb regardless so we can use it as a pool # in tests. configure_mongodb diff --git a/devstack/settings b/devstack/settings index 1705c4d99..d115bec5a 100644 --- a/devstack/settings +++ b/devstack/settings @@ -45,5 +45,7 @@ ZAQAR_TRUSTEE_DOMAIN=${ZAQAR_TRUSTEE_DOMAIN:-default} # Tell Tempest this project is present TEMPEST_SERVICES+=,zaqar +# CORS +CORS_ENABLED=${CORS_ENABLED:-false} enable_service zaqar-websocket zaqar-wsgi diff --git a/doc/source/CORS.rst b/doc/source/CORS.rst new file mode 100644 index 000000000..9a6b601c3 --- /dev/null +++ b/doc/source/CORS.rst @@ -0,0 +1,125 @@ +.. + 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. + +========== +CORS Guide +========== + +Zaqar supports Cross-Origin Resource Sharing (CORS) now. The function is +provided by oslo.middleware. Please see `Official Doc`_ and `OpenStack Spec`_ +for more detail. This guide is mainly tell users how to use it in Zaqar. + + +New Config Options +------------------ + +There are some new config options. + +**enabled** + +Enables CORS functions for Zaqar. The default value is "False" at this moment. +It will be turn to "True" in the future once it's stable enough. + +**allowed_origin** + +Indicate whether this resource may be shared with the domain received in the +requests "origin" header. Format: "://[:]", no trailing +slash. Example: https://horizon.example.com'. + +**allow_credentials** + +Indicate that the actual request can include user credentials. The default +value is True. + +**expose_headers** + +Indicate which headers are safe to expose to the API. Defaults to HTTP Simple +Headers. The default value is []. + +**max_age** + +Maximum cache age of CORS preflight requests. The default value is 3600. + +**allow_methods** + +Indicate which methods can be used during the actual request. The default value +is ['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'PATCH']. + +**allow_headers** + +Indicate which header field names may be used during the actual request. The +default value is []. + + +Request and Response example +---------------------------- +To use CORS, you should make sure that the feature is enabled:: + + [cors] + enabled = true + allowed_origin = http://example + allow_methods = GET + +the above example config options mean that Zaqar only receive the GET request +from http://example domain. Here are some example request: +1. Zaqar will do nothing if the request doesn't contain "Origin" header:: + + # curl -I -X GET http://10.229.47.217:8888 -H "Accept: application/json" + + HTTP/1.1 300 Multiple Choices + content-length: 668 + content-type: application/json; charset=UTF-8 + Connection: close + +2. Zaqar will return nothing in response headers if the "Origin" is not in +``allowed_origin``:: + + # curl -I -X GET http://10.229.47.217:8888 -H "Accept: application/json" -H "Origin: http://" + + HTTP/1.1 300 Multiple Choices + content-length: 668 + content-type: application/json; charset=UTF-8 + Connection: close + +In the Zaqar log, we can see a message:: + + CORS request from origin 'http://' not permitted. + +3. Zaqar will return CORS information if the "Origin" header is in +``allowed_origin``:: + + # curl -I -X GET http://10.229.47.217:8888 -H "Accept: application/json" -H "Origin: http://example" + + HTTP/1.1 300 Multiple Choices + content-length: 668 + content-type: application/json; charset=UTF-8 + Vary: Origin + Access-Control-Allow-Origin: http://example + Access-Control-Allow-Credentials: true + Connection: close + +4. Zaqar will return more information if the request doesn't follow Zaqar's\ +CORS rule:: + + # curl -I -X PUT http://10.229.47.217:8888 -H "Accept: application/json" -H "Origin: http://example" + HTTP/1.1 405 Method Not Allowed + content-length: 0 + content-type: application/json; charset=UTF-8 + allow: GET, OPTIONS + Vary: Origin + Access-Control-Allow-Origin: http://example + Access-Control-Allow-Credentials: true + Connection: close + +.. _Official Doc: http://docs.openstack.org/developer/osprofiler/background.html +.. _OpenStack Spec: http://specs.openstack.org/openstack/openstack-specs/specs/cors-support.html diff --git a/doc/source/OSprofiler.rst b/doc/source/OSprofiler.rst index 661b2d270..46e567012 100644 --- a/doc/source/OSprofiler.rst +++ b/doc/source/OSprofiler.rst @@ -16,7 +16,7 @@ OSprofiler Guide ================ OSprofiler is a library from oslo. It's used for performance analysis. Please -see `Office Doc`_ for more detail. +see `Official Doc`_ for more detail. Preparation ----------- @@ -121,4 +121,4 @@ Then you can open the file "list_test" to get the result. .. note:: If you used MQ for data transfer, the "--connection-string" here could be ignored or set it to your Ceilometer endpoint. -.. _Office Doc: http://docs.openstack.org/developer/osprofiler/background.html +.. _Official Doc: http://docs.openstack.org/developer/osprofiler/background.html diff --git a/doc/source/index.rst b/doc/source/index.rst index 5bed22b7f..09053ec5f 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -189,6 +189,7 @@ Feature Guide subscription_confirm OSprofiler + CORS Other resources =============== diff --git a/etc/oslo-config-generator/zaqar.conf b/etc/oslo-config-generator/zaqar.conf index 665fec25b..b2700d5a4 100644 --- a/etc/oslo-config-generator/zaqar.conf +++ b/etc/oslo-config-generator/zaqar.conf @@ -15,5 +15,6 @@ namespace = zaqar.transport.validation namespace = keystonemiddleware.auth_token namespace = oslo.cache namespace = oslo.messaging +namespace = oslo.middleware.cors namespace = osprofiler namespace = oslo.reports diff --git a/releasenotes/notes/support-cors-af8349382a44aa0d.yaml b/releasenotes/notes/support-cors-af8349382a44aa0d.yaml new file mode 100644 index 000000000..1ba4d49f8 --- /dev/null +++ b/releasenotes/notes/support-cors-af8349382a44aa0d.yaml @@ -0,0 +1,4 @@ +--- +features: + - Zaqar now supports Cross-Origin Resource Sharing (CORS). It turns off by + default. Use "enable=True" in [cors] section from zaqar.conf to use it. \ No newline at end of file diff --git a/zaqar/common/configs.py b/zaqar/common/configs.py index c8886de3b..5e366e4d7 100644 --- a/zaqar/common/configs.py +++ b/zaqar/common/configs.py @@ -104,6 +104,7 @@ _NOTIFICATION_OPTIONS = ( _NOTIFICATION_GROUP = 'notification' + _PROFILER_OPTIONS = [ cfg.BoolOpt("trace_wsgi_transport", default=False, help="If False doesn't trace any transport requests." @@ -117,9 +118,19 @@ _PROFILER_OPTIONS = [ _PROFILER_GROUP = "profiler" +_CORS_OPTIONS = [ + cfg.BoolOpt("enabled", default=False, + help="Whether enable Cross Origin Resource Sharing(CORS) " + "function from oslo.middleware"), +] + +_CORS_GROUP = "cors" + + def _config_options(): return [(None, _GENERAL_OPTIONS), (_DRIVER_GROUP, _DRIVER_OPTIONS), (_SIGNED_URL_GROUP, _SIGNED_URL_OPTIONS), (_NOTIFICATION_GROUP, _NOTIFICATION_OPTIONS), - (_PROFILER_GROUP, _PROFILER_OPTIONS)] + (_PROFILER_GROUP, _PROFILER_OPTIONS), + (_CORS_GROUP, _CORS_OPTIONS)] diff --git a/zaqar/transport/middleware/cors.py b/zaqar/transport/middleware/cors.py new file mode 100644 index 000000000..66c342850 --- /dev/null +++ b/zaqar/transport/middleware/cors.py @@ -0,0 +1,55 @@ +# Copyright 2017 OpenStack, Inc. +# 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. + +from oslo_log import log +from oslo_middleware import cors + +LOG = log.getLogger(__name__) + + +class CORSMiddleware(object): + + @classmethod + def install(cls, app, conf): + + LOG.debug(u'Installing CORS middleware.') + cors.set_defaults( + allow_headers=['X-Auth-Token', + 'X-Identity-Status', + 'X-Roles', + 'X-Service-Catalog', + 'X-User-Id', + 'X-Tenant-Id', + 'X-OpenStack-Request-ID', + 'X-Trace-Info', + 'X-Trace-HMAC', + 'Client-id'], + expose_headers=['X-Auth-Token', + 'X-Subject-Token', + 'X-Service-Token', + 'X-OpenStack-Request-ID'], + allow_methods=['GET', + 'PUT', + 'POST', + 'DELETE', + 'PATCH', + 'HEAD'] + ) + + return cors.CORS(app, conf) + + +def install_cors(app, conf): + return CORSMiddleware.install(app, conf) diff --git a/zaqar/transport/wsgi/driver.py b/zaqar/transport/wsgi/driver.py index 96177f300..5b29a0cdc 100644 --- a/zaqar/transport/wsgi/driver.py +++ b/zaqar/transport/wsgi/driver.py @@ -29,6 +29,7 @@ from zaqar.i18n import _ from zaqar import transport from zaqar.transport import acl from zaqar.transport.middleware import auth +from zaqar.transport.middleware import cors from zaqar.transport.middleware import profile from zaqar.transport import validation from zaqar.transport.wsgi import v1_0 @@ -153,6 +154,11 @@ class Driver(transport.DriverBase): self.app = auth.SignedHeadersAuth(self.app, auth_app) + # NOTE(wangxiyuan): Install CORS, this middleware should be called + # before Keystone auth. + if self._conf.cors.enabled: + self.app = cors.install_cors(self.app, self._conf) + acl.setup_policy(self._conf) def _error_handler(self, exc, request, response, params):