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
from gabbi import fixture
from oslo_config import cfg
from oslo_config import fixture as fixture_config
from oslo_policy import opts
from six.moves.urllib import parse as urlparse
@ -64,6 +65,11 @@ class ConfigFixture(fixture.GabbiFixture):
conf.set_override('policy_file',
os.path.abspath('etc/ceilometer/policy.json'),
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.
conf.set_override('pipeline_cfg_file',
@ -148,3 +154,24 @@ class EventDataFixture(fixture.GabbiFixture):
def stop_fixture(self):
"""Destroy the events."""
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."""
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
return driver.build_tests(test_dir, loader, host=None,
intercept=app.setup_app,
intercept=app.load_app,
fixture_module=fixture_module)

View File

@ -5,7 +5,7 @@
# Remove authtoken from the pipeline if you don't want to use keystone authentication
[pipeline:main]
pipeline = request_id authtoken api-server
pipeline = cors request_id authtoken api-server
[app:api-server]
paste.app_factory = ceilometer.api.app:app_factory
@ -16,3 +16,9 @@ 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

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