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)