From ebb06fb49f7addb93e4c0b2755aa94622e50e6e9 Mon Sep 17 00:00:00 2001 From: Michael Krotscheck Date: Mon, 19 Oct 2015 11:33:30 -0700 Subject: [PATCH] Added CORS support to Cue This adds the CORS support middleware to Cue, allowing a deployer to optionally configure rules under which a javascript client may break the single-origin policy and access the API directly. For cue, the middleware was added directly to the application factory, including various default headers usually required by keystone. Note that the CORS middleware must be the first one in the middleware chain, so that it can annotate all responses - even error responses - generated by other middleware. oslo_config initialization was added to the functional test initialization, in order to ensure that a fully initialized config instance is available during tests. OpenStack CrossProject Spec: http://specs.openstack.org/openstack/openstack-specs/specs/cors-support.html Oslo_Middleware Docs: http://docs.openstack.org/developer/oslo.middleware/cors.html OpenStack Cloud Admin Guide: http://docs.openstack.org/admin-guide-cloud/cross_project_cors.html Change-Id: I7ff60a96f545ff991de06073a80a5750512ebd31 --- cue/api/app.py | 21 +++++++--- cue/tests/functional/api/__init__.py | 3 ++ cue/tests/functional/api/test_policy.py | 3 +- etc/cue/cue.conf.sample | 56 +++++++++++++++++++++++++ requirements.txt | 1 + tools/config/config-generator-cue.conf | 1 + 6 files changed, 78 insertions(+), 7 deletions(-) diff --git a/cue/api/app.py b/cue/api/app.py index 1613a6d4..aba43904 100644 --- a/cue/api/app.py +++ b/cue/api/app.py @@ -16,6 +16,7 @@ # under the License. from oslo_config import cfg +from oslo_middleware import cors as cors_middleware import pecan from cue.api import acl @@ -35,9 +36,8 @@ API_OPTS = [ help='Pecan HTML Debug Interface'), ] -CONF = cfg.CONF -CONF.register_opts(auth_opts) -CONF.register_opts(API_OPTS, group='api') +cfg.CONF.register_opts(auth_opts) +cfg.CONF.register_opts(API_OPTS, group='api') def list_opts(): @@ -68,14 +68,23 @@ def setup_app(pecan_config=None, extra_hooks=None): app = pecan.make_app( pecan_config.app.root, static_root=pecan_config.app.static_root, - debug=CONF.api.pecan_debug, + debug=cfg.CONF.api.pecan_debug, force_canonical=getattr(pecan_config.app, 'force_canonical', True), hooks=app_hooks, wrap_app=middleware.ParsableErrorMiddleware, ) if pecan_config.app.enable_acl: - return acl.install(app, cfg.CONF, pecan_config.app.acl_public_routes) + app = acl.install(app, cfg.CONF, pecan_config.app.acl_public_routes) + + # Create a CORS wrapper, and attach ironic-specific defaults that must be + # included in all CORS responses. + app = cors_middleware.CORS(app, cfg.CONF) + app.set_latent( + allow_headers=['X-Auth-Token', 'X-Server-Management-Url'], + allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'], + expose_headers=['X-Auth-Token', 'X-Server-Management-Url'] + ) return app @@ -83,7 +92,7 @@ def setup_app(pecan_config=None, extra_hooks=None): class VersionSelectorApplication(object): def __init__(self): pc = get_pecan_config() - pc.app.enable_acl = (CONF.auth_strategy == 'keystone') + pc.app.enable_acl = (cfg.CONF.auth_strategy == 'keystone') self.v1 = setup_app(pecan_config=pc) def __call__(self, environ, start_response): diff --git a/cue/tests/functional/api/__init__.py b/cue/tests/functional/api/__init__.py index 9f745dba..f3cdfe50 100644 --- a/cue/tests/functional/api/__init__.py +++ b/cue/tests/functional/api/__init__.py @@ -64,6 +64,9 @@ class APITest(base.FunctionalTestCase): }, } + # Bootstrap the configuration, to ensure parsing has been handled. + cfg.CONF(project='cue', args=[]) + return pecan.testing.load_test_app(self.config) def tearDown(self): diff --git a/cue/tests/functional/api/test_policy.py b/cue/tests/functional/api/test_policy.py index 5cd38ef3..d716797f 100644 --- a/cue/tests/functional/api/test_policy.py +++ b/cue/tests/functional/api/test_policy.py @@ -18,6 +18,7 @@ from oslo_config import cfg from cue.common import policy from cue.tests.functional import api +from cue.tests.functional import base class TestPolicyHarness(api.APITest): @@ -33,7 +34,7 @@ class TestPolicyHarness(api.APITest): self.assertEqual(1, self.mock_init.call_count) -class TestPolicyOsloConfig(api.APITest): +class TestPolicyOsloConfig(base.FunctionalTestCase): """Test the policy harness initialization configuration states.""" def test_init_with_uninitialized_config_dir(self): diff --git a/etc/cue/cue.conf.sample b/etc/cue/cue.conf.sample index f925214f..2a79cf40 100644 --- a/etc/cue/cue.conf.sample +++ b/etc/cue/cue.conf.sample @@ -201,6 +201,62 @@ #pecan_debug = false +[cors] + +# +# From oslo.middleware.cors +# + +# Indicate whether this resource may be shared with the domain received in the +# requests "origin" header. (string value) +#allowed_origin = + +# Indicate that the actual request can include user credentials (boolean value) +#allow_credentials = true + +# Indicate which headers are safe to expose to the API. Defaults to HTTP Simple +# Headers. (list value) +#expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma + +# Maximum cache age of CORS preflight requests. (integer value) +#max_age = 3600 + +# Indicate which methods can be used during the actual request. (list value) +#allow_methods = GET,POST,PUT,DELETE,OPTIONS + +# Indicate which header field names may be used during the actual request. +# (list value) +#allow_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma + + +[cors.subdomain] + +# +# From oslo.middleware.cors +# + +# Indicate whether this resource may be shared with the domain received in the +# requests "origin" header. (string value) +#allowed_origin = + +# Indicate that the actual request can include user credentials (boolean value) +#allow_credentials = true + +# Indicate which headers are safe to expose to the API. Defaults to HTTP Simple +# Headers. (list value) +#expose_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma + +# Maximum cache age of CORS preflight requests. (integer value) +#max_age = 3600 + +# Indicate which methods can be used during the actual request. (list value) +#allow_methods = GET,POST,PUT,DELETE,OPTIONS + +# Indicate which header field names may be used during the actual request. +# (list value) +#allow_headers = Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma + + [cue_monitor] # diff --git a/requirements.txt b/requirements.txt index e3050572..4fe949cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ stevedore>=1.5.0 # Apache-2.0 oslo.config>=2.7.0 # Apache-2.0 oslo.db>=4.1.0 # Apache-2.0 oslo.log>=1.12.0 # Apache-2.0 +oslo.middleware>=3.0.0 # Apache-2.0 oslo.policy>=0.5.0 # Apache-2.0 oslo.rootwrap>=2.0.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 diff --git a/tools/config/config-generator-cue.conf b/tools/config/config-generator-cue.conf index 1d079568..085e20e7 100644 --- a/tools/config/config-generator-cue.conf +++ b/tools/config/config-generator-cue.conf @@ -10,6 +10,7 @@ namespace = cue.taskflow namespace = keystonemiddleware.auth_token namespace = oslo.db namespace = oslo.messaging +namespace = oslo.middleware.cors namespace = oslo.log namespace = oslo.policy namespace = oslo.service.service