From d9509e294fb700b6130e0a2405a6986737da7a7d Mon Sep 17 00:00:00 2001 From: Chris Dent <cdent@anticdent.org> Date: Wed, 2 Nov 2016 17:01:36 +0000 Subject: [PATCH] Add rudimentary CORS support to placement API If 'cors.allowed_origin' is set in the nova.conf, configure the placement API to use oslo_middleware.CORS. Simple gabbi tests are added which confirm the basic operation of the middleware, modeled on the tests in nova/tests/functional/test_middleware.py, as well as additional tests which confirm that when the middleware is not configured it is not present in the system. The cors config options are registered in deploy.py to ensure that the group is allowed to exist in conf (even if it doesn't). Without that, a deployment that tries to configure cors would not actually cause the middleware to run. Change-Id: I571bc675facaecb523dcf906f4bb44a51102b514 --- nova/api/openstack/placement/deploy.py | 19 ++++++-- .../api/openstack/placement/fixtures.py | 24 +++++++++- .../placement/gabbits/basic-http.yaml | 10 ++++ .../api/openstack/placement/gabbits/cors.yaml | 47 +++++++++++++++++++ .../openstack/placement/gabbits/non-cors.yaml | 25 ++++++++++ .../placement-cors-c7a83e8c63787736.yaml | 9 ++++ 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 nova/tests/functional/api/openstack/placement/gabbits/cors.yaml create mode 100644 nova/tests/functional/api/openstack/placement/gabbits/non-cors.yaml create mode 100644 releasenotes/notes/placement-cors-c7a83e8c63787736.yaml diff --git a/nova/api/openstack/placement/deploy.py b/nova/api/openstack/placement/deploy.py index 1edf9724b..34001f34a 100644 --- a/nova/api/openstack/placement/deploy.py +++ b/nova/api/openstack/placement/deploy.py @@ -12,7 +12,8 @@ """Deployment handling for Placmenent API.""" from keystonemiddleware import auth_token -from oslo_middleware import request_id +import oslo_middleware +from oslo_middleware import cors from nova.api import openstack as common_api from nova.api.openstack.placement import auth @@ -41,8 +42,18 @@ def deploy(conf, project_name): auth_middleware = auth_token.filter_factory( {}, oslo_config_project=project_name) + # Pass in our CORS config, if any, manually as that's a) + # explicit, b) makes testing more straightfoward, c) let's + # us control the use of cors by the presence of its config. + conf.register_opts(cors.CORS_OPTS, 'cors') + if conf.cors.allowed_origin: + cors_middleware = oslo_middleware.CORS.factory( + {}, **conf.cors) + else: + cors_middleware = None + context_middleware = auth.PlacementKeystoneContext - req_id_middleware = request_id.RequestId + req_id_middleware = oslo_middleware.RequestId microversion_middleware = microversion.MicroversionMiddleware fault_wrap = common_api.FaultWrapper request_log = requestlog.RequestLog @@ -62,9 +73,11 @@ def deploy(conf, project_name): request_log, context_middleware, auth_middleware, + cors_middleware, req_id_middleware, ): - application = middleware(application) + if middleware: + application = middleware(application) return application diff --git a/nova/tests/functional/api/openstack/placement/fixtures.py b/nova/tests/functional/api/openstack/placement/fixtures.py index 8a3d6106b..f766728ae 100644 --- a/nova/tests/functional/api/openstack/placement/fixtures.py +++ b/nova/tests/functional/api/openstack/placement/fixtures.py @@ -13,6 +13,7 @@ import os from gabbi import fixture +from oslo_middleware import cors from oslo_utils import uuidutils from nova.api.openstack.placement import deploy @@ -55,7 +56,16 @@ class APIFixture(fixture.GabbiFixture): group='api_database') self.conf.set_override('connection', "sqlite://", group='placement_database') - config.parse_args([], default_config_files=None, configure_db=False, + + # Register CORS opts, but do not set config. This has the + # effect of exercising the "don't use cors" path in + # deploy.py. Without setting some config the group will not + # be present. + self.conf.register_opts(cors.CORS_OPTS, 'cors') + + # Make sure default_config_files is an empty list, not None. + # If None /etc/nova/nova.conf is read and confuses results. + config.parse_args([], default_config_files=[], configure_db=False, init_rpc=False) # NOTE(cdent): api and main database are not used but we still need @@ -136,3 +146,15 @@ class AllocationFixture(APIFixture): # not been created in the Allocation fixture os.environ['ALT_RP_UUID'] = uuidutils.generate_uuid() os.environ['ALT_RP_NAME'] = uuidutils.generate_uuid() + + +class CORSFixture(APIFixture): + """An APIFixture that turns on CORS.""" + + def start_fixture(self): + super(CORSFixture, self).start_fixture() + # NOTE(cdent): If we remove this override, then the cors + # group ends up not existing in the conf, so when deploy.py + # wants to load the CORS middleware, it will not. + self.conf.set_override('allowed_origin', 'http://valid.example.com', + group='cors') diff --git a/nova/tests/functional/api/openstack/placement/gabbits/basic-http.yaml b/nova/tests/functional/api/openstack/placement/gabbits/basic-http.yaml index 00757ebba..fae0d2c24 100644 --- a/nova/tests/functional/api/openstack/placement/gabbits/basic-http.yaml +++ b/nova/tests/functional/api/openstack/placement/gabbits/basic-http.yaml @@ -52,6 +52,16 @@ tests: response_strings: - The method DELETE is not allowed for this resource. +- name: 405 on bad options method on app + OPTIONS: /resource_providers + status: 405 + response_headers: + allow: /(GET|POST), (POST|GET)/ + response_json_paths: + $.errors[0].title: Method Not Allowed + response_strings: + - The method OPTIONS is not allowed for this resource. + - name: bad accept resource providers GET: /resource_providers request_headers: diff --git a/nova/tests/functional/api/openstack/placement/gabbits/cors.yaml b/nova/tests/functional/api/openstack/placement/gabbits/cors.yaml new file mode 100644 index 000000000..291e1d5f7 --- /dev/null +++ b/nova/tests/functional/api/openstack/placement/gabbits/cors.yaml @@ -0,0 +1,47 @@ +# Confirm that CORS is present. No complex configuration is done so +# this just tests the basics. Borrowed, in spirit, from +# nova.tests.functional.test_middleware. + +fixtures: + - CORSFixture + +defaults: + request_headers: + x-auth-token: user + +tests: +- name: valid options request + OPTIONS: / + request_headers: + origin: http://valid.example.com + access-control-request-method: GET + status: 200 + response_headers: + access-control-allow-origin: http://valid.example.com + +- name: invalid options request + OPTIONS: / + request_headers: + origin: http://invalid.example.com + access-control-request-method: GET + status: 200 + response_forbidden_headers: + - access-control-allow-origin + +- name: valid get request + GET: / + request_headers: + origin: http://valid.example.com + access-control-request-method: GET + status: 200 + response_headers: + access-control-allow-origin: http://valid.example.com + +- name: invalid get request + GET: / + request_headers: + origin: http://invalid.example.com + access-control-request-method: GET + status: 200 + response_forbidden_headers: + - access-control-allow-origin diff --git a/nova/tests/functional/api/openstack/placement/gabbits/non-cors.yaml b/nova/tests/functional/api/openstack/placement/gabbits/non-cors.yaml new file mode 100644 index 000000000..b0b974cc5 --- /dev/null +++ b/nova/tests/functional/api/openstack/placement/gabbits/non-cors.yaml @@ -0,0 +1,25 @@ +# Confirm that things work as intended when CORS is not configured. + +fixtures: + - APIFixture + +defaults: + request_headers: + x-auth-token: user + +tests: +- name: options request not allowed + OPTIONS: / + request_headers: + origin: http://valid.example.com + access-control-request-method: GET + status: 405 + +- name: get request no cors headers + GET: / + request_headers: + origin: http://valid.example.com + access-control-request-method: GET + status: 200 + response_forbidden_headers: + - access-control-allow-origin diff --git a/releasenotes/notes/placement-cors-c7a83e8c63787736.yaml b/releasenotes/notes/placement-cors-c7a83e8c63787736.yaml new file mode 100644 index 000000000..6ea9af445 --- /dev/null +++ b/releasenotes/notes/placement-cors-c7a83e8c63787736.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + The placement API service can now be configured to support + `CORS <http://docs.openstack.org/developer/oslo.middleware/cors.html>`_. + If a `cors` configuration group is present in the service's configuration + file (currently `nova.conf`), with `allowed_origin` configured, the values + within will be used to configure the middleware. If `cors.allowed_origin` + is not set, the middleware will not be used.