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
This commit is contained in:
Mehdi Abaakouk 2017-06-12 10:27:23 +02:00
parent 132de83ed3
commit b0bdd43209
4 changed files with 71 additions and 8 deletions

View File

@ -18,6 +18,8 @@ import threading
import cachetools import cachetools
from gnocchiclient import client from gnocchiclient import client
from gnocchiclient import exceptions from gnocchiclient import exceptions
from keystoneauth1 import exceptions as ka_exceptions
from oslo_config import cfg
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
import pecan import pecan
import wsme import wsme
@ -28,6 +30,14 @@ from aodh.api.controllers.v2 import utils as v2_utils
from aodh import keystone_client 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): class GnocchiUnavailable(Exception):
code = 503 code = 503
@ -122,6 +132,20 @@ class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule):
'resource_type']) 'resource_type'])
return rule 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 @classmethod
def validate_alarm(cls, alarm): def validate_alarm(cls, alarm):
super(AggregationMetricByResourcesLookupRule, super(AggregationMetricByResourcesLookupRule,
@ -135,14 +159,27 @@ class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule):
except ValueError: except ValueError:
raise wsme.exc.InvalidInput('rule/query', rule.query) raise wsme.exc.InvalidInput('rule/query', rule.query)
conf = pecan.request.cfg
# Scope the alarm to the project id if needed # Scope the alarm to the project id if needed
auth_project = v2_utils.get_auth_project(alarm.project_id) auth_project = v2_utils.get_auth_project(alarm.project_id)
if auth_project: 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) rule.query = jsonutils.dumps(query)
conf = pecan.request.cfg
gnocchi_client = client.Client( gnocchi_client = client.Client(
'1', keystone_client.get_session(conf), '1', keystone_client.get_session(conf),
interface=conf.service_credentials.interface, interface=conf.service_credentials.interface,

View File

@ -16,6 +16,7 @@ import itertools
from keystoneauth1 import loading from keystoneauth1 import loading
import aodh.api import aodh.api
import aodh.api.controllers.v2.alarm_rules.gnocchi
import aodh.api.controllers.v2.alarms import aodh.api.controllers.v2.alarms
import aodh.coordination import aodh.coordination
import aodh.evaluator import aodh.evaluator
@ -42,6 +43,7 @@ def list_opts():
('api', ('api',
itertools.chain( itertools.chain(
aodh.api.OPTS, aodh.api.OPTS,
aodh.api.controllers.v2.alarm_rules.gnocchi.GNOCCHI_OPTS,
aodh.api.controllers.v2.alarms.ALARM_API_OPTS)), aodh.api.controllers.v2.alarms.ALARM_API_OPTS)),
('coordination', aodh.coordination.OPTS), ('coordination', aodh.coordination.OPTS),
('database', aodh.storage.OPTS), ('database', aodh.storage.OPTS),

View File

@ -2607,7 +2607,9 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase):
self.assertEqual(1, len(alarms)) self.assertEqual(1, len(alarms))
self._verify_alarm(json, alarms[0]) 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 = { json = {
'enabled': False, 'enabled': False,
'name': 'project_constraint', 'name': 'project_constraint',
@ -2630,10 +2632,21 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase):
} }
} }
expected_query = {"and": [{"=": {"created_by_project_id": expected_query = {"and": [
self.auth_headers['X-Project-Id']}}, {"or": [
{"=": {"server_group": {"=": {"created_by_project_id":
"my_autoscaling_group"}}]} self.auth_headers['X-Project-Id']}},
{"and": [
{"=": {"created_by_project_id": "<my-uuid>"}},
{"=": {"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='<my-uuid>')
get_client.return_value = ks_client
with mock.patch('aodh.api.controllers.v2.alarm_rules.' with mock.patch('aodh.api.controllers.v2.alarm_rules.'
'gnocchi.client') as clientlib: 'gnocchi.client') as clientlib:

View File

@ -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.