diff --git a/etc/glance-api-paste.ini b/etc/glance-api-paste.ini index 5b0e6b4bdf..543c8ebb0d 100644 --- a/etc/glance-api-paste.ini +++ b/etc/glance-api-paste.ini @@ -1,38 +1,38 @@ # Use this pipeline for no auth or image caching - DEFAULT [pipeline:glance-api] -pipeline = healthcheck versionnegotiation osprofiler unauthenticated-context rootapp +pipeline = cors healthcheck versionnegotiation osprofiler unauthenticated-context rootapp # Use this pipeline for image caching and no auth [pipeline:glance-api-caching] -pipeline = healthcheck versionnegotiation osprofiler unauthenticated-context cache rootapp +pipeline = cors healthcheck versionnegotiation osprofiler unauthenticated-context cache rootapp # Use this pipeline for caching w/ management interface but no auth [pipeline:glance-api-cachemanagement] -pipeline = healthcheck versionnegotiation osprofiler unauthenticated-context cache cachemanage rootapp +pipeline = cors healthcheck versionnegotiation osprofiler unauthenticated-context cache cachemanage rootapp # Use this pipeline for keystone auth [pipeline:glance-api-keystone] -pipeline = healthcheck versionnegotiation osprofiler authtoken context rootapp +pipeline = cors healthcheck versionnegotiation osprofiler authtoken context rootapp # Use this pipeline for keystone auth with image caching [pipeline:glance-api-keystone+caching] -pipeline = healthcheck versionnegotiation osprofiler authtoken context cache rootapp +pipeline = cors healthcheck versionnegotiation osprofiler authtoken context cache rootapp # Use this pipeline for keystone auth with caching and cache management [pipeline:glance-api-keystone+cachemanagement] -pipeline = healthcheck versionnegotiation osprofiler authtoken context cache cachemanage rootapp +pipeline = cors healthcheck versionnegotiation osprofiler authtoken context cache cachemanage rootapp # Use this pipeline for authZ only. This means that the registry will treat a # user as authenticated without making requests to keystone to reauthenticate # the user. [pipeline:glance-api-trusted-auth] -pipeline = healthcheck versionnegotiation osprofiler context rootapp +pipeline = cors healthcheck versionnegotiation osprofiler context rootapp # Use this pipeline for authZ only. This means that the registry will treat a # user as authenticated without making requests to keystone to reauthenticate # the user and uses cache management [pipeline:glance-api-trusted-auth+cachemanagement] -pipeline = healthcheck versionnegotiation osprofiler context cache cachemanage rootapp +pipeline = cors healthcheck versionnegotiation osprofiler context cache cachemanage rootapp [composite:rootapp] paste.composite_factory = glance.api:root_app_factory @@ -84,3 +84,25 @@ paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory paste.filter_factory = osprofiler.web:WsgiMiddleware.factory hmac_keys = SECRET_KEY enabled = yes + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = glance +oslo_config_program = glance-api +# Basic Headers (Automatic) +# Accept = Origin, Accept, Accept-Language, Content-Type, Cache-Control, Content-Language, Expires, Last-Modified, Pragma +# Expose = Origin, Accept, Accept-Language, Content-Type, Cache-Control, Content-Language, Expires, Last-Modified, Pragma + +# Glance Headers +# Accept = Content-MD5, X-Image-Meta-Checksum, X-Storage-Token, Accept-Encoding +# Expose = X-Image-Meta-Checksum + +# Keystone Headers +# Accept = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id +# Expose = X-Auth-Token, X-Subject-Token, X-Service-Token + +# Request ID Middleware Headers +# Accept = X-OpenStack-Request-ID +# Expose = X-OpenStack-Request-ID +latent_allow_headers = Content-MD5, X-Image-Meta-Checksum, X-Storage-Token, Accept-Encoding, X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID +latent_expose_headers = X-Image-Meta-Checksum, X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID \ No newline at end of file diff --git a/etc/glance-api.conf b/etc/glance-api.conf index ce44c4243e..36669addf9 100644 --- a/etc/glance-api.conf +++ b/etc/glance-api.conf @@ -627,6 +627,65 @@ #use_tpool = 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 = <None> + +# 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 = <None> + +# 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 + [glance_store] # diff --git a/etc/oslo-config-generator/glance-api.conf b/etc/oslo-config-generator/glance-api.conf index 3f24718f7f..1a8d4aea27 100644 --- a/etc/oslo-config-generator/glance-api.conf +++ b/etc/oslo-config-generator/glance-api.conf @@ -9,3 +9,4 @@ namespace = oslo.db.concurrency namespace = oslo.policy namespace = keystonemiddleware.auth_token namespace = oslo.log +namespace = oslo.middleware.cors diff --git a/glance/tests/functional/__init__.py b/glance/tests/functional/__init__.py index dcea47aa47..d09d4001d2 100644 --- a/glance/tests/functional/__init__.py +++ b/glance/tests/functional/__init__.py @@ -370,14 +370,21 @@ filesystem_store_datadir=%(image_dir)s default_store = %(default_store)s """ self.paste_conf_base = """[pipeline:glance-api] -pipeline = healthcheck versionnegotiation gzip unauthenticated-context rootapp +pipeline = + cors + healthcheck + versionnegotiation + gzip + unauthenticated-context + rootapp [pipeline:glance-api-caching] -pipeline = healthcheck versionnegotiation gzip unauthenticated-context +pipeline = cors healthcheck versionnegotiation gzip unauthenticated-context cache rootapp [pipeline:glance-api-cachemanagement] pipeline = + cors healthcheck versionnegotiation gzip @@ -387,10 +394,10 @@ pipeline = rootapp [pipeline:glance-api-fakeauth] -pipeline = healthcheck versionnegotiation gzip fakeauth context rootapp +pipeline = cors healthcheck versionnegotiation gzip fakeauth context rootapp [pipeline:glance-api-noauth] -pipeline = healthcheck versionnegotiation gzip context rootapp +pipeline = cors healthcheck versionnegotiation gzip context rootapp [composite:rootapp] paste.composite_factory = glance.api:root_app_factory @@ -439,6 +446,10 @@ paste.filter_factory = [filter:fakeauth] paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +allowed_origin=http://valid.example.com """ diff --git a/glance/tests/functional/test_cors_middleware.py b/glance/tests/functional/test_cors_middleware.py new file mode 100644 index 0000000000..d2a1156297 --- /dev/null +++ b/glance/tests/functional/test_cors_middleware.py @@ -0,0 +1,85 @@ +# 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.""" + +import httplib2 + +from glance.tests import functional + + +class TestCORSMiddleware(functional.FunctionalTest): + '''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): + super(TestCORSMiddleware, self).setUp() + # Cleanup is handled in teardown of the parent class. + self.start_servers(**self.__dict__.copy()) + self.http = httplib2.Http() + self.api_path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port) + + def test_valid_cors_options_request(self): + (r_headers, content) = self.http.request( + self.api_path, + 'OPTIONS', + headers={ + 'Origin': 'http://valid.example.com', + 'Access-Control-Request-Method': 'GET' + }) + + self.assertEqual(r_headers.status, 200) + self.assertIn('access-control-allow-origin', r_headers) + self.assertEqual('http://valid.example.com', + r_headers['access-control-allow-origin']) + + def test_invalid_cors_options_request(self): + (r_headers, content) = self.http.request( + self.api_path, + 'OPTIONS', + headers={ + 'Origin': 'http://invalid.example.com', + 'Access-Control-Request-Method': 'GET' + }) + + self.assertEqual(r_headers.status, 200) + self.assertNotIn('access-control-allow-origin', r_headers) + + def test_valid_cors_get_request(self): + (r_headers, content) = self.http.request( + self.api_path, + 'GET', + headers={ + 'Origin': 'http://valid.example.com' + }) + + self.assertEqual(r_headers.status, 200) + self.assertIn('access-control-allow-origin', r_headers) + self.assertEqual('http://valid.example.com', + r_headers['access-control-allow-origin']) + + def test_invalid_cors_get_request(self): + (r_headers, content) = self.http.request( + self.api_path, + 'GET', + headers={ + 'Origin': 'http://invalid.example.com' + }) + + self.assertEqual(r_headers.status, 200) + self.assertNotIn('access-control-allow-origin', r_headers)