diff --git a/nova/api/openstack/placement/deploy.py b/nova/api/openstack/placement/deploy.py index 1edf9724b09b..34001f34a018 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 8a3d6106bd53..f766728ae817 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 00757ebba5c5..fae0d2c24a6d 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 000000000000..291e1d5f7085 --- /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 000000000000..b0b974cc52f7 --- /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 000000000000..6ea9af445688 --- /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 `_. + 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.