Added CORS support to Ceilometer

This adds the CORS support middleware to Ceilometer, allowing a deployer
to optionally configure rules under which a javascript client may
break the single-origin policy and access the API directly. Included
are Ceilometer's custom headers, so that anyone activating this
middleware does not have to explicitly enable them.

The paste.ini method of deploying the middleware was
chosen, because it needs to be able to annotate responses created
by keystonemiddleware.

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: I90d2a53c40e3c353abe3cf37bd7deb3a07aeb043
This commit is contained in:
Dong Ma 2015-09-17 15:19:12 +08:00 committed by Michael Krotscheck
parent 333024b69a
commit 45e1d22607
6 changed files with 107 additions and 2 deletions

View File

@ -22,6 +22,7 @@ from unittest import case
import uuid import uuid
from gabbi import fixture from gabbi import fixture
from oslo_config import cfg
from oslo_config import fixture as fixture_config from oslo_config import fixture as fixture_config
from oslo_policy import opts from oslo_policy import opts
from six.moves.urllib import parse as urlparse from six.moves.urllib import parse as urlparse
@ -64,6 +65,11 @@ class ConfigFixture(fixture.GabbiFixture):
conf.set_override('policy_file', conf.set_override('policy_file',
os.path.abspath('etc/ceilometer/policy.json'), os.path.abspath('etc/ceilometer/policy.json'),
group='oslo_policy') group='oslo_policy')
conf.set_override(
'api_paste_config',
os.path.abspath(
'ceilometer/tests/functional/gabbi/gabbi_paste.ini')
)
# A special pipeline is required to use the direct publisher. # A special pipeline is required to use the direct publisher.
conf.set_override('pipeline_cfg_file', conf.set_override('pipeline_cfg_file',
@ -148,3 +154,24 @@ class EventDataFixture(fixture.GabbiFixture):
def stop_fixture(self): def stop_fixture(self):
"""Destroy the events.""" """Destroy the events."""
self.conn.db.event.remove({'event_type': '/^cookies_/'}) self.conn.db.event.remove({'event_type': '/^cookies_/'})
class CORSConfigFixture(fixture.GabbiFixture):
"""Inject mock configuration for the CORS middleware."""
def start_fixture(self):
# Here we monkeypatch GroupAttr.__getattr__, necessary because the
# paste.ini method of initializing this middleware creates its own
# ConfigOpts instance, bypassing the regular config fixture.
def _mock_getattr(instance, key):
if key != 'allowed_origin':
return self._original_call_method(instance, key)
return "http://valid.example.com"
self._original_call_method = cfg.ConfigOpts.GroupAttr.__getattr__
cfg.ConfigOpts.GroupAttr.__getattr__ = _mock_getattr
def stop_fixture(self):
"""Remove the monkeypatch."""
cfg.ConfigOpts.GroupAttr.__getattr__ = self._original_call_method

View File

@ -0,0 +1,27 @@
# Ceilometer API WSGI Pipeline
# Define the filters that make up the pipeline for processing WSGI requests
# Note: This pipeline is PasteDeploy's term rather than Ceilometer's pipeline
# used for processing samples
#
# This version is specific for gabbi. It removes support for keystone while
# keeping suport for CORS.
# Remove authtoken from the pipeline if you don't want to use keystone authentication
[pipeline:main]
pipeline = cors api-server
[app:api-server]
paste.app_factory = ceilometer.api.app:app_factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = ceilometer
latent_allow_headers = 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-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID
latent_allow_methods = GET, PUT, POST, DELETE, PATCH

View File

@ -0,0 +1,44 @@
#
# Test the middlewares. Just CORS for now.
#
fixtures:
- ConfigFixture
- CORSConfigFixture
tests:
- name: valid cors options
OPTIONS: /
status: 200
request_headers:
origin: http://valid.example.com
access-control-request-method: GET
response_headers:
access-control-allow-origin: http://valid.example.com
- name: invalid cors options
OPTIONS: /
status: 200
request_headers:
origin: http://invalid.example.com
access-control-request-method: GET
response_forbidden_headers:
- access-control-allow-origin
- name: valid cors get
GET: /
status: 200
request_headers:
origin: http://valid.example.com
access-control-request-method: GET
response_headers:
access-control-allow-origin: http://valid.example.com
- name: invalid cors get
GET: /
status: 200
request_headers:
origin: http://invalid.example.com
response_forbidden_headers:
- access-control-allow-origin

View File

@ -32,5 +32,5 @@ def load_tests(loader, tests, pattern):
"""Provide a TestSuite to the discovery process.""" """Provide a TestSuite to the discovery process."""
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR) test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
return driver.build_tests(test_dir, loader, host=None, return driver.build_tests(test_dir, loader, host=None,
intercept=app.setup_app, intercept=app.load_app,
fixture_module=fixture_module) fixture_module=fixture_module)

View File

@ -5,7 +5,7 @@
# Remove authtoken from the pipeline if you don't want to use keystone authentication # Remove authtoken from the pipeline if you don't want to use keystone authentication
[pipeline:main] [pipeline:main]
pipeline = request_id authtoken api-server pipeline = cors request_id authtoken api-server
[app:api-server] [app:api-server]
paste.app_factory = ceilometer.api.app:app_factory paste.app_factory = ceilometer.api.app:app_factory
@ -16,3 +16,9 @@ paste.filter_factory = keystonemiddleware.auth_token:filter_factory
[filter:request_id] [filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory paste.filter_factory = oslo_middleware:RequestId.factory
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = ceilometer
latent_allow_headers = 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-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID
latent_allow_methods = GET, PUT, POST, DELETE, PATCH

View File

@ -6,6 +6,7 @@ namespace = oslo.concurrency
namespace = oslo.db namespace = oslo.db
namespace = oslo.log namespace = oslo.log
namespace = oslo.messaging namespace = oslo.messaging
namespace = oslo.middleware.cors
namespace = oslo.policy namespace = oslo.policy
namespace = oslo.service.service namespace = oslo.service.service
namespace = keystonemiddleware.auth_token namespace = keystonemiddleware.auth_token