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.

Closes-bug: #1703824
Change-Id: I0c86736a902a21520da18550aea0a7d1549bb24e
(cherry picked from commit b0bdd43209)
This commit is contained in:
Mehdi Abaakouk 2017-06-12 10:27:23 +02:00
parent 2c40cdadd1
commit f87e0d05c4
4 changed files with 73 additions and 8 deletions

View File

@ -15,6 +15,8 @@
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
@ -25,6 +27,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
@ -138,6 +148,22 @@ class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule):
'resource_type'])
return rule
# NOTE(sileht): once cachetools is in the requirements
# enable it
# cache = cachetools.TTLCache(maxsize=1, ttl=3600)
# lock = threading.RLock()
# @cachetools.cached(cache, lock=lock)
@staticmethod
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,
@ -151,14 +177,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

@ -3010,7 +3010,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',
@ -3033,10 +3035,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.