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
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,

View File

@ -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),

View File

@ -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": "<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.'
'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.