From b0bdd43209a47181a8124e28990127620cba03a7 Mon Sep 17 00:00:00 2001 From: Mehdi Abaakouk Date: Mon, 12 Jun 2017 10:27:23 +0200 Subject: [PATCH] gnocchi: fix alarms for unpriviledged user When an unprivileged user want to access to Gnocchi resources created by Ceilometer, that doesn't work because the filter scope the Gnocchi query to resource owner to the user. To fix we introduce a new configuration option "gnocchi_external_project_owner" set by default to "service". The new filter now allow two kind of Gnocchi resources: * owned by the user project * owned by "gnocchi_external_project_owner" and the orignal project_id of the resource is the user project. Change-Id: I0c86736a902a21520da18550aea0a7d1549bb24e --- .../api/controllers/v2/alarm_rules/gnocchi.py | 43 +++++++++++++++++-- aodh/opts.py | 2 + .../functional/api/v2/test_alarm_scenarios.py | 23 +++++++--- ...ernal-resource-owner-3fad253d30746b0d.yaml | 11 +++++ 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/gnocchi-external-resource-owner-3fad253d30746b0d.yaml diff --git a/aodh/api/controllers/v2/alarm_rules/gnocchi.py b/aodh/api/controllers/v2/alarm_rules/gnocchi.py index 6576fc2bf..25663d2a0 100644 --- a/aodh/api/controllers/v2/alarm_rules/gnocchi.py +++ b/aodh/api/controllers/v2/alarm_rules/gnocchi.py @@ -18,6 +18,8 @@ import threading import cachetools from gnocchiclient import client from gnocchiclient import exceptions +from keystoneauth1 import exceptions as ka_exceptions +from oslo_config import cfg from oslo_serialization import jsonutils import pecan import wsme @@ -28,6 +30,14 @@ from aodh.api.controllers.v2 import utils as v2_utils from aodh import keystone_client +GNOCCHI_OPTS = [ + cfg.StrOpt('gnocchi_external_project_owner', + default="service", + help='Project name of resources creator in Gnocchi. ' + '(For example the Ceilometer project name'), +] + + class GnocchiUnavailable(Exception): code = 503 @@ -122,6 +132,20 @@ class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule): 'resource_type']) return rule + cache = cachetools.TTLCache(maxsize=1, ttl=3600) + lock = threading.RLock() + + @staticmethod + @cachetools.cached(cache, lock=lock) + def get_external_project_owner(): + kc = keystone_client.get_client(pecan.request.cfg) + project_name = pecan.request.cfg.api.gnocchi_external_project_owner + try: + project = kc.projects.find(name=project_name) + return project.id + except ka_exceptions.NotFound: + return None + @classmethod def validate_alarm(cls, alarm): super(AggregationMetricByResourcesLookupRule, @@ -135,14 +159,27 @@ class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule): except ValueError: raise wsme.exc.InvalidInput('rule/query', rule.query) + conf = pecan.request.cfg + # Scope the alarm to the project id if needed auth_project = v2_utils.get_auth_project(alarm.project_id) if auth_project: - query = {"and": [{"=": {"created_by_project_id": auth_project}}, - query]} + + perms_filter = {"=": {"created_by_project_id": auth_project}} + + external_project_owner = cls.get_external_project_owner() + if external_project_owner: + perms_filter = {"or": [ + perms_filter, + {"and": [ + {"=": {"created_by_project_id": + external_project_owner}}, + {"=": {"project_id": auth_project}}]} + ]} + + query = {"and": [perms_filter, query]} rule.query = jsonutils.dumps(query) - conf = pecan.request.cfg gnocchi_client = client.Client( '1', keystone_client.get_session(conf), interface=conf.service_credentials.interface, diff --git a/aodh/opts.py b/aodh/opts.py index dbc02f2d1..2f434e6fd 100644 --- a/aodh/opts.py +++ b/aodh/opts.py @@ -16,6 +16,7 @@ import itertools from keystoneauth1 import loading import aodh.api +import aodh.api.controllers.v2.alarm_rules.gnocchi import aodh.api.controllers.v2.alarms import aodh.coordination import aodh.evaluator @@ -42,6 +43,7 @@ def list_opts(): ('api', itertools.chain( aodh.api.OPTS, + aodh.api.controllers.v2.alarm_rules.gnocchi.GNOCCHI_OPTS, aodh.api.controllers.v2.alarms.ALARM_API_OPTS)), ('coordination', aodh.coordination.OPTS), ('database', aodh.storage.OPTS), diff --git a/aodh/tests/functional/api/v2/test_alarm_scenarios.py b/aodh/tests/functional/api/v2/test_alarm_scenarios.py index d89dd894b..9b801d7b1 100644 --- a/aodh/tests/functional/api/v2/test_alarm_scenarios.py +++ b/aodh/tests/functional/api/v2/test_alarm_scenarios.py @@ -2607,7 +2607,9 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase): self.assertEqual(1, len(alarms)) self._verify_alarm(json, alarms[0]) - def test_post_gnocchi_aggregation_alarm_project_constraint(self): + @mock.patch('aodh.keystone_client.get_client') + def test_post_gnocchi_aggregation_alarm_project_constraint(self, + get_client): json = { 'enabled': False, 'name': 'project_constraint', @@ -2630,10 +2632,21 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase): } } - expected_query = {"and": [{"=": {"created_by_project_id": - self.auth_headers['X-Project-Id']}}, - {"=": {"server_group": - "my_autoscaling_group"}}]} + expected_query = {"and": [ + {"or": [ + {"=": {"created_by_project_id": + self.auth_headers['X-Project-Id']}}, + {"and": [ + {"=": {"created_by_project_id": ""}}, + {"=": {"project_id": self.auth_headers['X-Project-Id']}} + ]}, + ]}, + {"=": {"server_group": "my_autoscaling_group"}}, + ]} + + ks_client = mock.Mock() + ks_client.projects.find.return_value = mock.Mock(id='') + get_client.return_value = ks_client with mock.patch('aodh.api.controllers.v2.alarm_rules.' 'gnocchi.client') as clientlib: diff --git a/releasenotes/notes/gnocchi-external-resource-owner-3fad253d30746b0d.yaml b/releasenotes/notes/gnocchi-external-resource-owner-3fad253d30746b0d.yaml new file mode 100644 index 000000000..45a2997a3 --- /dev/null +++ b/releasenotes/notes/gnocchi-external-resource-owner-3fad253d30746b0d.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + When an unprivileged user want to access to Gnocchi resources created by + Ceilometer, that doesn't work because the filter scope the Gnocchi query to + resource owner to the user. To fix we introduce a new configuration option + "gnocchi_external_project_owner" set by default to "service". The new + filter now allow two kind of Gnocchi resources: + * owned by the user project + * owned by "gnocchi_external_project_owner" and the orignal project_id of + the resource is the user project.