[policy in code] Add support for service, limits

This patch adds policy in code support for capabilities,
hosts, services, limits and depends on the quota patch [1].

[1]: https://review.openstack.org/#/c/508091/

Change-Id: Ib2bac2d28d950c0d8b734a54e300dd4185d98ca9
Partial-Implements: blueprint policy-in-code
This commit is contained in:
TommyLike 2017-10-10 08:56:30 +08:00
parent 5a099de01a
commit 5b5715e2ad
16 changed files with 276 additions and 48 deletions

View File

@ -21,13 +21,10 @@ from cinder.api.views import capabilities as capabilities_view
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import objects from cinder import objects
from cinder.policies import capabilities as policy
from cinder.volume import rpcapi from cinder.volume import rpcapi
def authorize(context, action_name):
extensions.extension_authorizer('volume', action_name)(context)
class CapabilitiesController(wsgi.Controller): class CapabilitiesController(wsgi.Controller):
"""The Capabilities controller for the OpenStack API.""" """The Capabilities controller for the OpenStack API."""
@ -43,7 +40,7 @@ class CapabilitiesController(wsgi.Controller):
def show(self, req, id): def show(self, req, id):
"""Return capabilities list of given backend.""" """Return capabilities list of given backend."""
context = req.environ['cinder.context'] context = req.environ['cinder.context']
authorize(context, 'capabilities') context.authorize(policy.CAPABILITIES_POLICY)
filters = {'host_or_cluster': id, 'binary': 'cinder-volume'} filters = {'host_or_cluster': id, 'binary': 'cinder-volume'}
services = objects.ServiceList.get_all(context, filters) services = objects.ServiceList.get_all(context, filters)
if not services: if not services:

View File

@ -27,13 +27,13 @@ from cinder import db
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import objects from cinder import objects
from cinder.policies import hosts as policy
from cinder.volume import api as volume_api from cinder.volume import api as volume_api
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('volume', 'hosts')
def _list_hosts(req, service=None): def _list_hosts(req, service=None):
@ -91,12 +91,14 @@ class HostController(wsgi.Controller):
super(HostController, self).__init__() super(HostController, self).__init__()
def index(self, req): def index(self, req):
authorize(req.environ['cinder.context']) context = req.environ['cinder.context']
context.authorize(policy.MANAGE_POLICY)
return {'hosts': _list_hosts(req)} return {'hosts': _list_hosts(req)}
@check_host @check_host
def update(self, req, id, body): def update(self, req, id, body):
authorize(req.environ['cinder.context']) context = req.environ['cinder.context']
context.authorize(policy.MANAGE_POLICY)
update_values = {} update_values = {}
for raw_key, raw_val in body.items(): for raw_key, raw_val in body.items():
key = raw_key.lower().strip() key = raw_key.lower().strip()

View File

@ -19,15 +19,11 @@ from cinder.api import extensions
from cinder.api import microversions as mv from cinder.api import microversions as mv
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder.api.views import scheduler_stats as scheduler_stats_view from cinder.api.views import scheduler_stats as scheduler_stats_view
from cinder.policies import scheduler_stats as policy
from cinder.scheduler import rpcapi from cinder.scheduler import rpcapi
from cinder import utils from cinder import utils
def authorize(context, action_name):
action = 'scheduler_stats:%s' % action_name
extensions.extension_authorizer('scheduler', action)(context)
class SchedulerStatsController(wsgi.Controller): class SchedulerStatsController(wsgi.Controller):
"""The Scheduler Stats controller for the OpenStack API.""" """The Scheduler Stats controller for the OpenStack API."""
@ -46,7 +42,7 @@ class SchedulerStatsController(wsgi.Controller):
def get_pools(self, req): def get_pools(self, req):
"""List all active pools in scheduler.""" """List all active pools in scheduler."""
context = req.environ['cinder.context'] context = req.environ['cinder.context']
authorize(context, 'get_pools') context.authorize(policy.GET_POOL_POLICY)
detail = utils.get_bool_param('detail', req.params) detail = utils.get_bool_param('detail', req.params)

View File

@ -29,6 +29,7 @@ from cinder.common import constants
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import objects from cinder import objects
from cinder.policies import services as policy
from cinder.scheduler import rpcapi as scheduler_rpcapi from cinder.scheduler import rpcapi as scheduler_rpcapi
from cinder import utils from cinder import utils
from cinder import volume from cinder import volume
@ -38,7 +39,6 @@ from cinder.volume import rpcapi as volume_rpcapi
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('volume', 'services')
class ServiceController(wsgi.Controller): class ServiceController(wsgi.Controller):
@ -61,7 +61,7 @@ class ServiceController(wsgi.Controller):
Filter by host & service name. Filter by host & service name.
""" """
context = req.environ['cinder.context'] context = req.environ['cinder.context']
authorize(context, action='index') context.authorize(policy.GET_ALL_POLICY)
detailed = self.ext_mgr.is_loaded('os-extended-services') detailed = self.ext_mgr.is_loaded('os-extended-services')
now = timeutils.utcnow(with_timezone=True) now = timeutils.utcnow(with_timezone=True)
@ -223,7 +223,7 @@ class ServiceController(wsgi.Controller):
directly in this API layer. directly in this API layer.
""" """
context = req.environ['cinder.context'] context = req.environ['cinder.context']
authorize(context, action='update') context.authorize(policy.UPDATE_POLICY)
support_dynamic_log = req.api_version_request.matches(mv.LOG_LEVEL) support_dynamic_log = req.api_version_request.matches(mv.LOG_LEVEL)

View File

@ -15,19 +15,19 @@
from cinder.api import extensions from cinder.api import extensions
from cinder.api import microversions as mv from cinder.api import microversions as mv
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder.policies import limits as policy
from cinder import quota from cinder import quota
QUOTAS = quota.QUOTAS QUOTAS = quota.QUOTAS
authorize = extensions.soft_extension_authorizer('limits', 'used_limits')
class UsedLimitsController(wsgi.Controller): class UsedLimitsController(wsgi.Controller):
@wsgi.extends @wsgi.extends
def index(self, req, resp_obj): def index(self, req, resp_obj):
context = req.environ['cinder.context'] context = req.environ['cinder.context']
if authorize(context): if context.authorize(
policy.EXTEND_LIMIT_ATTRIBUTE_POLICY, fatal=False):
params = req.params.copy() params = req.params.copy()
req_version = req.api_version_request req_version = req.api_version_request

View File

@ -19,17 +19,22 @@ from cinder.policies import attachments
from cinder.policies import backup_actions from cinder.policies import backup_actions
from cinder.policies import backups from cinder.policies import backups
from cinder.policies import base from cinder.policies import base
from cinder.policies import capabilities
from cinder.policies import clusters from cinder.policies import clusters
from cinder.policies import group_actions from cinder.policies import group_actions
from cinder.policies import group_snapshot_actions from cinder.policies import group_snapshot_actions
from cinder.policies import group_snapshots from cinder.policies import group_snapshots
from cinder.policies import group_types from cinder.policies import group_types
from cinder.policies import groups from cinder.policies import groups
from cinder.policies import hosts
from cinder.policies import limits
from cinder.policies import manageable_snapshots from cinder.policies import manageable_snapshots
from cinder.policies import messages from cinder.policies import messages
from cinder.policies import qos_specs from cinder.policies import qos_specs
from cinder.policies import quota_class from cinder.policies import quota_class
from cinder.policies import quotas from cinder.policies import quotas
from cinder.policies import scheduler_stats
from cinder.policies import services
from cinder.policies import snapshot_actions from cinder.policies import snapshot_actions
from cinder.policies import snapshot_metadata from cinder.policies import snapshot_metadata
from cinder.policies import snapshots from cinder.policies import snapshots
@ -57,4 +62,9 @@ def list_rules():
qos_specs.list_rules(), qos_specs.list_rules(),
quota_class.list_rules(), quota_class.list_rules(),
quotas.list_rules(), quotas.list_rules(),
capabilities.list_rules(),
services.list_rules(),
scheduler_stats.list_rules(),
hosts.list_rules(),
limits.list_rules(),
) )

View File

@ -0,0 +1,39 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
CAPABILITIES_POLICY = "volume_extension:capabilities",
capabilities_policies = [
policy.DocumentedRuleDefault(
name=CAPABILITIES_POLICY,
check_str=base.RULE_ADMIN_API,
description="Show backend capabilities.",
operations=[
{
'method': 'GET',
'path': '/capabilities/{host_name}'
}
])
]
def list_rules():
return capabilities_policies

42
cinder/policies/hosts.py Normal file
View File

@ -0,0 +1,42 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
MANAGE_POLICY = "volume_extension:hosts"
hosts_policies = [
policy.DocumentedRuleDefault(
name=MANAGE_POLICY,
check_str=base.RULE_ADMIN_API,
description="List or update hosts for a project.",
operations=[
{
'method': 'GET',
'path': '/os-hosts'
},
{
'method': 'PUT',
'path': '/os-hosts/{host_name}'
}
])
]
def list_rules():
return hosts_policies

38
cinder/policies/limits.py Normal file
View File

@ -0,0 +1,38 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
EXTEND_LIMIT_ATTRIBUTE_POLICY = "limits_extension:used_limits"
limits_policies = [
policy.DocumentedRuleDefault(
name=EXTEND_LIMIT_ATTRIBUTE_POLICY,
check_str=base.RULE_ADMIN_OR_OWNER,
description="Show limits with used limit attributes.",
operations=[
{
'method': 'GET',
'path': '/limits'
}
])
]
def list_rules():
return limits_policies

View File

@ -0,0 +1,38 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
GET_POOL_POLICY = "scheduler_extension:scheduler_stats:get_pools"
pools_policies = [
policy.DocumentedRuleDefault(
name=GET_POOL_POLICY,
check_str=base.RULE_ADMIN_API,
description="List all backend pools.",
operations=[
{
'method': 'GET',
'path': '/scheduler-stats/get_pools'
}
])
]
def list_rules():
return pools_policies

View File

@ -0,0 +1,83 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
GET_ALL_POLICY = "volume_extension:services:index"
UPDATE_POLICY = "volume_extension:services:update"
FAILOVER_POLICY = "volume:failover_host"
FREEZE_POLICY = "volume:freeze_host"
THAW_POLICY = "volume:thaw_host"
services_policies = [
policy.DocumentedRuleDefault(
name=GET_ALL_POLICY,
check_str=base.RULE_ADMIN_API,
description="List all services.",
operations=[
{
'method': 'GET',
'path': '/os-services'
}
]),
policy.DocumentedRuleDefault(
name=UPDATE_POLICY,
check_str=base.RULE_ADMIN_API,
description="Update service, including failover_host, thaw, freeze, "
"disable, enable, set-log and get-log actions.",
operations=[
{
'method': 'PUT',
'path': '/os-services/{action}'
}
]),
policy.DocumentedRuleDefault(
name=FREEZE_POLICY,
check_str=base.RULE_ADMIN_API,
description="Freeze a backend host.",
operations=[
{
'method': 'PUT',
'path': '/os-services/freeze'
}
]),
policy.DocumentedRuleDefault(
name=THAW_POLICY,
check_str=base.RULE_ADMIN_API,
description="Thaw a backend host.",
operations=[
{
'method': 'PUT',
'path': '/os-services/thaw'
}
]),
policy.DocumentedRuleDefault(
name=FAILOVER_POLICY,
check_str=base.RULE_ADMIN_API,
description="Failover a backend host.",
operations=[
{
'method': 'PUT',
'path': '/os-services/failover_host'
}
]),
]
def list_rules():
return services_policies

View File

@ -174,7 +174,7 @@ def fake_service_update(context, service_id, values):
'disabled': values['disabled']} 'disabled': values['disabled']}
def fake_policy_enforce(context, action, target): def fake_policy_authorize(context, action, target):
pass pass
@ -188,7 +188,7 @@ def fake_utcnow(with_timezone=False):
@mock.patch('cinder.db.service_get', fake_service_get) @mock.patch('cinder.db.service_get', fake_service_get)
@mock.patch('oslo_utils.timeutils.utcnow', fake_utcnow) @mock.patch('oslo_utils.timeutils.utcnow', fake_utcnow)
@mock.patch('cinder.db.sqlalchemy.api.service_update', fake_service_update) @mock.patch('cinder.db.sqlalchemy.api.service_update', fake_service_update)
@mock.patch('cinder.policy.enforce', fake_policy_enforce) @mock.patch('cinder.policy.authorize', fake_policy_authorize)
class ServicesTest(test.TestCase): class ServicesTest(test.TestCase):
def setUp(self): def setUp(self):

View File

@ -47,8 +47,8 @@ class UsedLimitsTestCase(test.TestCase):
(mv.LIMITS_ADMIN_FILTER, True), (mv.LIMITS_ADMIN_FILTER, True),
(mv.LIMITS_ADMIN_FILTER, False)) (mv.LIMITS_ADMIN_FILTER, False))
@mock.patch('cinder.quota.QUOTAS.get_project_quotas') @mock.patch('cinder.quota.QUOTAS.get_project_quotas')
@mock.patch('cinder.policy.enforce') @mock.patch('cinder.policy.authorize')
def test_used_limits(self, ver_project, _mock_policy_enforce, def test_used_limits(self, ver_project, _mock_policy_authorize,
_mock_get_project_quotas): _mock_get_project_quotas):
version, has_project = ver_project version, has_project = ver_project
fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID, fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
@ -77,7 +77,7 @@ class UsedLimitsTestCase(test.TestCase):
_mock_get_project_quotas.side_effect = get_project_quotas _mock_get_project_quotas.side_effect = get_project_quotas
# allow user to access used limits # allow user to access used limits
_mock_policy_enforce.return_value = None _mock_policy_authorize.return_value = True
self.controller.index(fake_req, res) self.controller.index(fake_req, res)
abs_limits = res.obj['limits']['absolute'] abs_limits = res.obj['limits']['absolute']
@ -111,7 +111,7 @@ class UsedLimitsTestCase(test.TestCase):
res = wsgi.ResponseObject(obj) res = wsgi.ResponseObject(obj)
# unallow user to access used limits # unallow user to access used limits
_mock_policy_enforce.side_effect = exception.NotAuthorized _mock_policy_authorize.side_effect = exception.NotAuthorized
self.controller.index(fake_req, res) self.controller.index(fake_req, res)
abs_limits = res.obj['limits']['absolute'] abs_limits = res.obj['limits']['absolute']

View File

@ -39,9 +39,6 @@
"volume:update_readonly_flag": "", "volume:update_readonly_flag": "",
"volume:retype": "", "volume:retype": "",
"volume:copy_volume_to_image": "", "volume:copy_volume_to_image": "",
"volume:failover_host": "rule:admin_api",
"volume:freeze_host": "rule:admin_api",
"volume:thaw_host": "rule:admin_api",
"volume:revert_to_snapshot": "", "volume:revert_to_snapshot": "",
"volume_extension:volume_admin_actions:reset_status": "rule:admin_api", "volume_extension:volume_admin_actions:reset_status": "rule:admin_api",
"volume_extension:volume_admin_actions:force_delete": "rule:admin_api", "volume_extension:volume_admin_actions:force_delete": "rule:admin_api",
@ -68,13 +65,11 @@
"volume_extension:volume_host_attribute": "rule:admin_api", "volume_extension:volume_host_attribute": "rule:admin_api",
"volume_extension:volume_tenant_attribute": "rule:admin_api", "volume_extension:volume_tenant_attribute": "rule:admin_api",
"volume_extension:volume_mig_status_attribute": "rule:admin_api", "volume_extension:volume_mig_status_attribute": "rule:admin_api",
"volume_extension:hosts": "rule:admin_api",
"volume_extension:services:index": "", "volume_extension:services:index": "",
"volume_extension:services:update" : "rule:admin_api", "volume_extension:services:update" : "rule:admin_api",
"volume_extension:volume_manage": "rule:admin_api", "volume_extension:volume_manage": "rule:admin_api",
"volume_extension:volume_unmanage": "rule:admin_api", "volume_extension:volume_unmanage": "rule:admin_api",
"volume_extension:list_manageable": "rule:admin_api", "volume_extension:list_manageable": "rule:admin_api",
"volume_extension:capabilities": "rule:admin_api",
"limits_extension:used_limits": "", "limits_extension:used_limits": "",
@ -115,8 +110,6 @@
"group:enable_replication": "", "group:enable_replication": "",
"group:disable_replication": "", "group:disable_replication": "",
"group:failover_replication": "", "group:failover_replication": "",
"group:list_replication_targets": "", "group:list_replication_targets": ""
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",
} }

View File

@ -44,6 +44,7 @@ from cinder import objects
from cinder.objects import base as objects_base from cinder.objects import base as objects_base
from cinder.objects import fields from cinder.objects import fields
from cinder.policies import attachments as attachment_policy from cinder.policies import attachments as attachment_policy
from cinder.policies import services as svr_policy
from cinder.policies import snapshot_metadata as s_meta_policy from cinder.policies import snapshot_metadata as s_meta_policy
from cinder.policies import snapshots as snapshot_policy from cinder.policies import snapshots as snapshot_policy
import cinder.policy import cinder.policy
@ -1843,7 +1844,7 @@ class API(base.Base):
return cluster, services return cluster, services
def failover(self, ctxt, host, cluster_name, secondary_id=None): def failover(self, ctxt, host, cluster_name, secondary_id=None):
check_policy(ctxt, 'failover_host') ctxt.authorize(svr_policy.FAILOVER_POLICY)
ctxt = ctxt if ctxt.is_admin else ctxt.elevated() ctxt = ctxt if ctxt.is_admin else ctxt.elevated()
# TODO(geguileo): In P - Remove this version check # TODO(geguileo): In P - Remove this version check
@ -1864,7 +1865,7 @@ class API(base.Base):
self.volume_rpcapi.failover(ctxt, services[0], secondary_id) self.volume_rpcapi.failover(ctxt, services[0], secondary_id)
def freeze_host(self, ctxt, host, cluster_name): def freeze_host(self, ctxt, host, cluster_name):
check_policy(ctxt, 'freeze_host') ctxt.authorize(svr_policy.FREEZE_POLICY)
ctxt = ctxt if ctxt.is_admin else ctxt.elevated() ctxt = ctxt if ctxt.is_admin else ctxt.elevated()
expected = False expected = False
@ -1879,7 +1880,7 @@ class API(base.Base):
self.volume_rpcapi.freeze_host(ctxt, services[0]) self.volume_rpcapi.freeze_host(ctxt, services[0])
def thaw_host(self, ctxt, host, cluster_name): def thaw_host(self, ctxt, host, cluster_name):
check_policy(ctxt, 'thaw_host') ctxt.authorize(svr_policy.THAW_POLICY)
ctxt = ctxt if ctxt.is_admin else ctxt.elevated() ctxt = ctxt if ctxt.is_admin else ctxt.elevated()
expected = True expected = True

View File

@ -46,26 +46,17 @@
"volume_extension:volume_host_attribute": "rule:admin_api", "volume_extension:volume_host_attribute": "rule:admin_api",
"volume_extension:volume_tenant_attribute": "rule:admin_or_owner", "volume_extension:volume_tenant_attribute": "rule:admin_or_owner",
"volume_extension:volume_mig_status_attribute": "rule:admin_api", "volume_extension:volume_mig_status_attribute": "rule:admin_api",
"volume_extension:hosts": "rule:admin_api",
"volume_extension:services:index": "rule:admin_api",
"volume_extension:services:update" : "rule:admin_api",
"volume_extension:volume_manage": "rule:admin_api", "volume_extension:volume_manage": "rule:admin_api",
"volume_extension:volume_unmanage": "rule:admin_api", "volume_extension:volume_unmanage": "rule:admin_api",
"volume_extension:list_manageable": "rule:admin_api", "volume_extension:list_manageable": "rule:admin_api",
"volume_extension:capabilities": "rule:admin_api",
"volume:create_transfer": "rule:admin_or_owner", "volume:create_transfer": "rule:admin_or_owner",
"volume:accept_transfer": "", "volume:accept_transfer": "",
"volume:delete_transfer": "rule:admin_or_owner", "volume:delete_transfer": "rule:admin_or_owner",
"volume:get_transfer": "rule:admin_or_owner", "volume:get_transfer": "rule:admin_or_owner",
"volume:get_all_transfers": "rule:admin_or_owner", "volume:get_all_transfers": "rule:admin_or_owner",
"volume:failover_host": "rule:admin_api",
"volume:freeze_host": "rule:admin_api",
"volume:thaw_host": "rule:admin_api",
"consistencygroup:create" : "group:nobody", "consistencygroup:create" : "group:nobody",
"consistencygroup:delete": "group:nobody", "consistencygroup:delete": "group:nobody",
"consistencygroup:update": "group:nobody", "consistencygroup:update": "group:nobody",
@ -77,6 +68,4 @@
"consistencygroup:get_cgsnapshot": "group:nobody", "consistencygroup:get_cgsnapshot": "group:nobody",
"consistencygroup:get_all_cgsnapshots": "group:nobody", "consistencygroup:get_all_cgsnapshots": "group:nobody",
"scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api",
} }