From f63470e0e257fa59ac9419e616cac17cf6e75510 Mon Sep 17 00:00:00 2001 From: gordon chung Date: Wed, 14 Oct 2015 10:49:40 -0400 Subject: [PATCH] Fix events rbac Rbac context is limited not by policy but is inherently built in as we cannot enforce policy on a list. This patch drops the dummy policy, the invalid context_is_project and context_is_admin policies, and ensures policy rbac can restrict on admin appropriately. Closes-Bug: #1504495 Change-Id: Id3b1ad71aea46456c6e6c1995776b988017d4786 --- ceilometer/api/rbac.py | 8 +- .../functional/api/v2/test_acl_scenarios.py | 105 +++++++++++------- .../functional/api/v2/test_api_upgrade.py | 13 +++ .../api/v2/test_post_samples_scenarios.py | 20 ++++ ceilometer/tests/functional/gabbi/fixtures.py | 13 ++- etc/ceilometer/policy.json | 17 ++- etc/ceilometer/policy.json.sample | 18 --- 7 files changed, 126 insertions(+), 68 deletions(-) delete mode 100644 etc/ceilometer/policy.json.sample diff --git a/ceilometer/api/rbac.py b/ceilometer/api/rbac.py index 50e7737612..d4443e4337 100644 --- a/ceilometer/api/rbac.py +++ b/ceilometer/api/rbac.py @@ -54,8 +54,8 @@ def enforce(policy_name, request): policy_dict = dict() policy_dict['roles'] = headers.get('X-Roles', "").split(",") - policy_dict['target.user_id'] = (headers.get('X-User-Id')) - policy_dict['target.project_id'] = (headers.get('X-Project-Id')) + policy_dict['user_id'] = (headers.get('X-User-Id')) + policy_dict['project_id'] = (headers.get('X-Project-Id')) # maintain backward compat with Juno and previous by allowing the action if # there is no rule defined for it @@ -82,8 +82,8 @@ def get_limited_to(headers): policy_dict = dict() policy_dict['roles'] = headers.get('X-Roles', "").split(",") - policy_dict['target.user_id'] = (headers.get('X-User-Id')) - policy_dict['target.project_id'] = (headers.get('X-Project-Id')) + policy_dict['user_id'] = (headers.get('X-User-Id')) + policy_dict['project_id'] = (headers.get('X-Project-Id')) # maintain backward compat with Juno and previous by using context_is_admin # rule if the segregation rule (added in Kilo) is not defined diff --git a/ceilometer/tests/functional/api/v2/test_acl_scenarios.py b/ceilometer/tests/functional/api/v2/test_acl_scenarios.py index c2f25493c7..d88a2bd143 100644 --- a/ceilometer/tests/functional/api/v2/test_acl_scenarios.py +++ b/ceilometer/tests/functional/api/v2/test_acl_scenarios.py @@ -18,6 +18,7 @@ import datetime import hashlib import json import os +import uuid from oslo_utils import fileutils from oslo_utils import timeutils @@ -25,6 +26,7 @@ import six import webtest from ceilometer.api import app +from ceilometer.event.storage import models as ev_model from ceilometer.publisher import utils from ceilometer import sample from ceilometer.tests.functional import api as acl @@ -226,51 +228,18 @@ class TestAPIEventACL(TestAPIACL): self.assertEqual(401, data.status_int) -class TestApiEventRBAC(v2.FunctionalTest): +class TestBaseApiEventRBAC(v2.FunctionalTest): PATH = '/events' def setUp(self): - super(TestApiEventRBAC, self).setUp() - content = ('{"context_is_admin": "role:admin",' - '"segregation": "rule:context_is_admin",' - '"default" : "!",' - '"telemetry:events:index": "rule:context_is_admin",' - '"telemetry:events:show": "rule:context_is_admin"}') - if six.PY3: - content = content.encode('utf-8') - self.tempfile = fileutils.write_to_tempfile(content=content, - prefix='policy', - suffix='.json') - - self.CONF.set_override("policy_file", - self.path_get(self.tempfile), - group='oslo_policy') - self.app = self._make_app() - - def tearDown(self): - os.remove(self.tempfile) - super(TestApiEventRBAC, self).tearDown() - - def test_get_event_by_message_rbac(self): - headers_rbac = {"X-Roles": "non-admin"} - data = self.get_json(self.PATH + "/100", - expect_errors=True, - headers=headers_rbac, - status=403) - self.assertEqual(u'403 Forbidden\n\nAccess was denied to this ' - 'resource.\n\n RBAC Authorization Failed ', - data.json['error_message']) - - def test_get_events_rbac(self): - headers_rbac = {"X-Roles": "non-admin"} - data = self.get_json(self.PATH, - expect_errors=True, - headers=headers_rbac, - status=403) - self.assertEqual(u'403 Forbidden\n\nAccess was denied to this ' - 'resource.\n\n RBAC Authorization Failed ', - data.json['error_message']) + super(TestBaseApiEventRBAC, self).setUp() + traits = [ev_model.Trait('project_id', 1, 'project-good'), + ev_model.Trait('user_id', 1, 'user-good')] + self.message_id = str(uuid.uuid4()) + ev = ev_model.Event(self.message_id, 'event_type', + datetime.datetime.now(), traits, {}) + self.event_conn.record_events([ev]) def test_get_events_without_project(self): headers_no_proj = {"X-Roles": "admin", "X-User-Id": "user-good"} @@ -291,3 +260,57 @@ class TestApiEventRBAC(v2.FunctionalTest): headers=headers_no_user_proj, status=403) self.assertEqual(403, resp.status_int) + + def test_get_events(self): + headers = {"X-Roles": "Member", "X-User-Id": "user-good", + "X-Project-Id": "project-good"} + self.get_json(self.PATH, headers=headers, status=200) + + def test_get_event(self): + headers = {"X-Roles": "Member", "X-User-Id": "user-good", + "X-Project-Id": "project-good"} + self.get_json(self.PATH + "/" + self.message_id, headers=headers, + status=200) + + +class TestApiEventAdminRBAC(TestBaseApiEventRBAC): + + def _make_app(self, enable_acl=False): + content = ('{"context_is_admin": "role:admin",' + '"telemetry:events:index": "rule:context_is_admin",' + '"telemetry:events:show": "rule:context_is_admin"}') + if six.PY3: + content = content.encode('utf-8') + self.tempfile = fileutils.write_to_tempfile(content=content, + prefix='policy', + suffix='.json') + + self.CONF.set_override("policy_file", self.tempfile, + group='oslo_policy') + return super(TestApiEventAdminRBAC, self)._make_app() + + def tearDown(self): + os.remove(self.tempfile) + super(TestApiEventAdminRBAC, self).tearDown() + + def test_get_events(self): + headers_rbac = {"X-Roles": "admin", "X-User-Id": "user-good", + "X-Project-Id": "project-good"} + self.get_json(self.PATH, headers=headers_rbac, status=200) + + def test_get_events_bad(self): + headers_rbac = {"X-Roles": "Member", "X-User-Id": "user-good", + "X-Project-Id": "project-good"} + self.get_json(self.PATH, headers=headers_rbac, status=403) + + def test_get_event(self): + headers = {"X-Roles": "admin", "X-User-Id": "user-good", + "X-Project-Id": "project-good"} + self.get_json(self.PATH + "/" + self.message_id, headers=headers, + status=200) + + def test_get_event_bad(self): + headers = {"X-Roles": "Member", "X-User-Id": "user-good", + "X-Project-Id": "project-good"} + self.get_json(self.PATH + "/" + self.message_id, headers=headers, + status=403) diff --git a/ceilometer/tests/functional/api/v2/test_api_upgrade.py b/ceilometer/tests/functional/api/v2/test_api_upgrade.py index e2185e17cb..f344a559e8 100644 --- a/ceilometer/tests/functional/api/v2/test_api_upgrade.py +++ b/ceilometer/tests/functional/api/v2/test_api_upgrade.py @@ -13,12 +13,25 @@ from keystoneclient import exceptions import mock +from oslo_utils import fileutils from oslotest import mockpatch +import six from ceilometer.tests.functional.api import v2 class TestAPIUpgradePath(v2.FunctionalTest): + def _make_app(self): + content = ('{"default": ""}') + if six.PY3: + content = content.encode('utf-8') + self.tempfile = fileutils.write_to_tempfile(content=content, + prefix='policy', + suffix='.json') + self.CONF.set_override("policy_file", self.tempfile, + group='oslo_policy') + return super(TestAPIUpgradePath, self)._make_app() + def _setup_osloconfig_options(self): self.CONF.set_override('gnocchi_is_enabled', True, group='api') self.CONF.set_override('aodh_is_enabled', True, group='api') diff --git a/ceilometer/tests/functional/api/v2/test_post_samples_scenarios.py b/ceilometer/tests/functional/api/v2/test_post_samples_scenarios.py index 38740bd3f6..033f292518 100644 --- a/ceilometer/tests/functional/api/v2/test_post_samples_scenarios.py +++ b/ceilometer/tests/functional/api/v2/test_post_samples_scenarios.py @@ -17,10 +17,13 @@ import copy import datetime +import os import mock +from oslo_utils import fileutils from oslo_utils import timeutils from oslotest import mockpatch +import six from ceilometer.tests.functional.api import v2 @@ -32,6 +35,23 @@ class TestPostSamples(v2.FunctionalTest): del m['message_signature'] self.published.append(samples) + def _make_app(self, enable_acl=False): + content = ('{"context_is_project": "project_id:%(project_id)s",' + '"default" : "!",' + '"telemetry:create_samples": ""}') + if six.PY3: + content = content.encode('utf-8') + self.tempfile = fileutils.write_to_tempfile(content=content, + prefix='policy', + suffix='.json') + self.CONF.set_override("policy_file", self.tempfile, + group='oslo_policy') + return super(TestPostSamples, self)._make_app() + + def tearDown(self): + os.remove(self.tempfile) + super(TestPostSamples, self).tearDown() + def setUp(self): self.published = [] notifier = mock.Mock() diff --git a/ceilometer/tests/functional/gabbi/fixtures.py b/ceilometer/tests/functional/gabbi/fixtures.py index b0e49a6fe4..f0c72231b6 100644 --- a/ceilometer/tests/functional/gabbi/fixtures.py +++ b/ceilometer/tests/functional/gabbi/fixtures.py @@ -24,6 +24,8 @@ import uuid from gabbi import fixture from oslo_config import fixture as fixture_config from oslo_policy import opts +from oslo_utils import fileutils +import six from six.moves.urllib import parse as urlparse from ceilometer.event.storage import models @@ -61,8 +63,15 @@ class ConfigFixture(fixture.GabbiFixture): conf.import_group('api', 'ceilometer.api.controllers.v2.root') conf.import_opt('store_events', 'ceilometer.notification', group='notification') - conf.set_override('policy_file', - os.path.abspath('etc/ceilometer/policy.json'), + + content = ('{"default": ""}') + if six.PY3: + content = content.encode('utf-8') + self.tempfile = fileutils.write_to_tempfile(content=content, + prefix='policy', + suffix='.json') + + conf.set_override("policy_file", self.tempfile, group='oslo_policy') # A special pipeline is required to use the direct publisher. diff --git a/etc/ceilometer/policy.json b/etc/ceilometer/policy.json index 2bcd03425a..21760cdc39 100644 --- a/etc/ceilometer/policy.json +++ b/etc/ceilometer/policy.json @@ -1,7 +1,18 @@ { "context_is_admin": "role:admin", - "context_is_project": "project_id:%(target.project_id)s", - "context_is_owner": "user_id:%(target.user_id)s", "segregation": "rule:context_is_admin", - "default": "" + + "telemetry:get_samples": "", + "telemetry:get_sample": "", + "telemetry:query_sample": "", + "telemetry:create_samples": "rule:context_is_admin", + + "telemetry:compute_statistics": "", + "telemetry:get_meters": "", + + "telemetry:get_resource": "", + "telemetry:get_resources": "", + + "telemetry:events:index": "", + "telemetry:events:show": "" } diff --git a/etc/ceilometer/policy.json.sample b/etc/ceilometer/policy.json.sample deleted file mode 100644 index 685f80f478..0000000000 --- a/etc/ceilometer/policy.json.sample +++ /dev/null @@ -1,18 +0,0 @@ -{ - "context_is_admin": "role:admin", - "context_is_project": "project_id:%(target.project_id)s", - "context_is_owner": "user_id:%(target.user_id)s", - "segregation": "rule:context_is_admin", - "service_role": "role:service", - "iaas_role": "role:iaas", - - "telemetry:get_samples": "rule:service_role or rule:iaas_role", - "telemetry:get_sample": "rule:context_is_project", - "telemetry:query_sample": "rule:context_is_admin", - "telemetry:create_samples": "rule:context_is_admin", - - "telemetry:compute_statistics": "rule:context_is_admin", - "telemetry:get_meters": "rule:context_is_admin", - - "telemetry:get_resource": "rule:context_is_admin", - "telemetry:get_resources": "rule:context_is_admin",