Implement project personas for quotas and quota_classes
This commit updates the quota policies to use default roles provided by keystone, including read-only support. This is inline with broader changes across the OpenStack community to provide a more consistent authorization experience. Future changes will incorporate system scope when cinder fully supports it. This commit also updates the roles of a quotas test to not use the 'admin' role for checking the policy is not authorized. Instead, use 'member' and 'reader', which seem to be more realistic values for the test since anyone with 'admin' can do just about anything in the deployment. Co-Authored-by: Alan Bishop <abishop@redhat.com> Change-Id: I1b72a78180da46ff4a6257903d76069389f2a1f4
This commit is contained in:
parent
81e0da35dc
commit
e0f4ab2286
@ -40,7 +40,7 @@ class QuotaClassSetsController(wsgi.Controller):
|
||||
|
||||
def show(self, req, id):
|
||||
context = req.environ['cinder.context']
|
||||
context.authorize(policy.MANAGE_POLICY)
|
||||
context.authorize(policy.GET_POLICY)
|
||||
try:
|
||||
db.sqlalchemy.api.authorize_quota_class_context(context, id)
|
||||
except exception.NotAuthorized:
|
||||
@ -54,7 +54,7 @@ class QuotaClassSetsController(wsgi.Controller):
|
||||
@validation.schema(quota_class.update_quota_class)
|
||||
def update(self, req, id, body):
|
||||
context = req.environ['cinder.context']
|
||||
context.authorize(policy.MANAGE_POLICY)
|
||||
context.authorize(policy.UPDATE_POLICY)
|
||||
self.validate_string_length(id, 'quota_class_name',
|
||||
min_length=1, max_length=255)
|
||||
|
||||
|
@ -18,24 +18,45 @@ from oslo_policy import policy
|
||||
from cinder.policies import base
|
||||
|
||||
|
||||
# MANAGE_POLICY is deprecated
|
||||
MANAGE_POLICY = 'volume_extension:quota_classes'
|
||||
GET_POLICY = 'volume_extension:quota_classes:get'
|
||||
UPDATE_POLICY = 'volume_extension:quota_classes:update'
|
||||
|
||||
|
||||
deprecated_manage_policy = base.CinderDeprecatedRule(
|
||||
name=MANAGE_POLICY,
|
||||
check_str=base.RULE_ADMIN_API,
|
||||
deprecated_reason=(f'{MANAGE_POLICY} has been replaced by more granular '
|
||||
'policies that separately govern GET and PUT '
|
||||
'operations.'),
|
||||
)
|
||||
|
||||
quota_class_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name=MANAGE_POLICY,
|
||||
name=GET_POLICY,
|
||||
check_str=base.RULE_ADMIN_API,
|
||||
description="Show or update project quota class.",
|
||||
description="Show project quota class.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/os-quota-class-sets/{project_id}'
|
||||
},
|
||||
}
|
||||
],
|
||||
deprecated_rule=deprecated_manage_policy,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=UPDATE_POLICY,
|
||||
check_str=base.RULE_ADMIN_API,
|
||||
description="Update project quota class.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/os-quota-class-sets/{project_id}'
|
||||
}
|
||||
]),
|
||||
],
|
||||
deprecated_rule=deprecated_manage_policy,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -22,11 +22,15 @@ SHOW_POLICY = 'volume_extension:quotas:show'
|
||||
UPDATE_POLICY = 'volume_extension:quotas:update'
|
||||
DELETE_POLICY = 'volume_extension:quotas:delete'
|
||||
|
||||
deprecated_show_policy = policy.DeprecatedRule(
|
||||
name=SHOW_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER
|
||||
)
|
||||
|
||||
quota_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name=SHOW_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
check_str=base.SYSTEM_READER_OR_PROJECT_READER,
|
||||
description="Show project quota (including usage and default).",
|
||||
operations=[
|
||||
{
|
||||
@ -41,7 +45,9 @@ quota_policies = [
|
||||
'method': 'GET',
|
||||
'path': '/os-quota-sets/{project_id}?usage=True'
|
||||
}
|
||||
]),
|
||||
],
|
||||
deprecated_rule=deprecated_show_policy,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=UPDATE_POLICY,
|
||||
check_str=base.RULE_ADMIN_API,
|
||||
@ -51,7 +57,8 @@ quota_policies = [
|
||||
'method': 'PUT',
|
||||
'path': '/os-quota-sets/{project_id}'
|
||||
}
|
||||
]),
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=DELETE_POLICY,
|
||||
check_str=base.RULE_ADMIN_API,
|
||||
@ -61,7 +68,8 @@ quota_policies = [
|
||||
'method': 'DELETE',
|
||||
'path': '/os-quota-sets/{project_id}'
|
||||
}
|
||||
]),
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -134,6 +134,7 @@ class QuotaSetsControllerTest(QuotaSetsControllerTestBase):
|
||||
self.req.environ['cinder.context'].is_admin = False
|
||||
self.req.environ['cinder.context'].user_id = fake.USER_ID
|
||||
self.req.environ['cinder.context'].project_id = fake.PROJECT_ID
|
||||
self.req.environ['cinder.context'].roles = ['member', 'reader']
|
||||
self.assertRaises(exception.PolicyNotAuthorized, self.controller.show,
|
||||
self.req, fake.PROJECT2_ID)
|
||||
|
||||
|
99
cinder/tests/unit/policies/test_quota_class.py
Normal file
99
cinder/tests/unit/policies/test_quota_class.py
Normal file
@ -0,0 +1,99 @@
|
||||
# Copyright 2021 Red Hat, Inc.
|
||||
# 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.
|
||||
|
||||
import ddt
|
||||
|
||||
from cinder.api.contrib import quota_classes
|
||||
from cinder.api import microversions as mv
|
||||
from cinder.policies import quota_class as policy
|
||||
from cinder.tests.unit.api import fakes as fake_api
|
||||
from cinder.tests.unit.policies import base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class QuotaClassPolicyTest(base.BasePolicyTest):
|
||||
authorized_admins = [
|
||||
'legacy_admin',
|
||||
'system_admin',
|
||||
'project_admin',
|
||||
]
|
||||
|
||||
unauthorized_admins = [
|
||||
'legacy_owner',
|
||||
'system_member',
|
||||
'system_reader',
|
||||
'system_foo',
|
||||
'project_member',
|
||||
'project_reader',
|
||||
'project_foo',
|
||||
'other_project_member',
|
||||
'other_project_reader',
|
||||
]
|
||||
|
||||
unauthorized_exceptions = []
|
||||
|
||||
# Basic policy test is without enforcing scope (which cinder doesn't
|
||||
# yet support) and deprecated rules enabled.
|
||||
def setUp(self, enforce_scope=False, enforce_new_defaults=False,
|
||||
*args, **kwargs):
|
||||
super().setUp(enforce_scope, enforce_new_defaults, *args, **kwargs)
|
||||
self.controller = quota_classes.QuotaClassSetsController()
|
||||
self.api_path = '/v3/os-quota-class-sets'
|
||||
self.api_version = mv.BASE_VERSION
|
||||
|
||||
@ddt.data(*base.all_users)
|
||||
def test_get_policy(self, user_id):
|
||||
rule_name = policy.GET_POLICY
|
||||
req = fake_api.HTTPRequest.blank(self.api_path,
|
||||
version=self.api_version)
|
||||
|
||||
self.common_policy_check(user_id, self.authorized_admins,
|
||||
self.unauthorized_admins,
|
||||
self.unauthorized_exceptions,
|
||||
rule_name, self.controller.show,
|
||||
req, id='my_class')
|
||||
|
||||
@ddt.data(*base.all_users)
|
||||
def test_update_policy(self, user_id):
|
||||
rule_name = policy.UPDATE_POLICY
|
||||
req = fake_api.HTTPRequest.blank(self.api_path,
|
||||
version=self.api_version)
|
||||
req.method = 'PUT'
|
||||
body = {
|
||||
"quota_class_set": {
|
||||
"groups": 11,
|
||||
"volumes": 5,
|
||||
"backups": 4
|
||||
}
|
||||
}
|
||||
|
||||
self.common_policy_check(user_id, self.authorized_admins,
|
||||
self.unauthorized_admins,
|
||||
self.unauthorized_exceptions,
|
||||
rule_name, self.controller.update,
|
||||
req, id='my_class', body=body)
|
||||
|
||||
|
||||
class QuotaClassPolicySecureRbacTest(QuotaClassPolicyTest):
|
||||
# NOTE(Xena): The authorized_admins and unauthorized_admins are the same
|
||||
# as the QuotasPolicyTest's. This is because in Xena the "admin only"
|
||||
# rules are the legacy RULE_ADMIN_API. This will change in Yoga, when
|
||||
# RULE_ADMIN_API will be deprecated in favor of the SYSTEM_ADMIN rule that
|
||||
# is scope based.
|
||||
|
||||
def setUp(self, *args, **kwargs):
|
||||
# Test secure RBAC by disabling deprecated policy rules (scope
|
||||
# is still not enabled).
|
||||
super().setUp(enforce_scope=False, enforce_new_defaults=True,
|
||||
*args, **kwargs)
|
147
cinder/tests/unit/policies/test_quotas.py
Normal file
147
cinder/tests/unit/policies/test_quotas.py
Normal file
@ -0,0 +1,147 @@
|
||||
# Copyright 2021 Red Hat, Inc.
|
||||
# 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.
|
||||
|
||||
import ddt
|
||||
|
||||
from cinder.api.contrib import quotas
|
||||
from cinder.api import microversions as mv
|
||||
from cinder.policies import quotas as policy
|
||||
from cinder.tests.unit.api import fakes as fake_api
|
||||
from cinder.tests.unit.policies import base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class QuotasPolicyTest(base.BasePolicyTest):
|
||||
authorized_users = [
|
||||
'legacy_admin',
|
||||
'legacy_owner',
|
||||
'system_admin',
|
||||
'project_admin',
|
||||
'project_member',
|
||||
'project_reader',
|
||||
'project_foo',
|
||||
]
|
||||
|
||||
unauthorized_users = [
|
||||
'system_member',
|
||||
'system_reader',
|
||||
'system_foo',
|
||||
'other_project_member',
|
||||
'other_project_reader',
|
||||
]
|
||||
|
||||
authorized_admins = [
|
||||
'legacy_admin',
|
||||
'system_admin',
|
||||
'project_admin',
|
||||
]
|
||||
|
||||
unauthorized_admins = [
|
||||
'legacy_owner',
|
||||
'system_member',
|
||||
'system_reader',
|
||||
'system_foo',
|
||||
'project_member',
|
||||
'project_reader',
|
||||
'project_foo',
|
||||
'other_project_member',
|
||||
'other_project_reader',
|
||||
]
|
||||
|
||||
unauthorized_exceptions = []
|
||||
|
||||
# Basic policy test is without enforcing scope (which cinder doesn't
|
||||
# yet support) and deprecated rules enabled.
|
||||
def setUp(self, enforce_scope=False, enforce_new_defaults=False,
|
||||
*args, **kwargs):
|
||||
super().setUp(enforce_scope, enforce_new_defaults, *args, **kwargs)
|
||||
self.controller = quotas.QuotaSetsController()
|
||||
self.api_path = '/v3/os-quota-sets'
|
||||
self.api_version = mv.BASE_VERSION
|
||||
|
||||
@ddt.data(*base.all_users)
|
||||
def test_show_policy(self, user_id):
|
||||
rule_name = policy.SHOW_POLICY
|
||||
req = fake_api.HTTPRequest.blank(self.api_path,
|
||||
version=self.api_version)
|
||||
|
||||
self.common_policy_check(user_id, self.authorized_users,
|
||||
self.unauthorized_users,
|
||||
self.unauthorized_exceptions,
|
||||
rule_name, self.controller.show,
|
||||
req, id=self.project_id)
|
||||
|
||||
@ddt.data(*base.all_users)
|
||||
def test_update_policy(self, user_id):
|
||||
rule_name = policy.UPDATE_POLICY
|
||||
req = fake_api.HTTPRequest.blank(self.api_path,
|
||||
version=self.api_version)
|
||||
req.method = 'PUT'
|
||||
body = {
|
||||
"quota_set": {
|
||||
"groups": 11,
|
||||
"volumes": 5,
|
||||
"backups": 4
|
||||
}
|
||||
}
|
||||
|
||||
self.common_policy_check(user_id, self.authorized_admins,
|
||||
self.unauthorized_admins,
|
||||
self.unauthorized_exceptions,
|
||||
rule_name, self.controller.update,
|
||||
req, id=self.project_id, body=body)
|
||||
|
||||
@ddt.data(*base.all_users)
|
||||
def test_delete_policy(self, user_id):
|
||||
rule_name = policy.DELETE_POLICY
|
||||
req = fake_api.HTTPRequest.blank(self.api_path,
|
||||
version=self.api_version)
|
||||
req.method = 'DELETE'
|
||||
|
||||
self.common_policy_check(user_id, self.authorized_admins,
|
||||
self.unauthorized_admins,
|
||||
self.unauthorized_exceptions,
|
||||
rule_name, self.controller.delete,
|
||||
req, id=self.project_id)
|
||||
|
||||
|
||||
class QuotasPolicySecureRbacTest(QuotasPolicyTest):
|
||||
authorized_users = [
|
||||
'legacy_admin',
|
||||
'system_admin',
|
||||
'project_admin',
|
||||
'project_member',
|
||||
'project_reader',
|
||||
]
|
||||
|
||||
unauthorized_users = [
|
||||
'legacy_owner',
|
||||
'system_member',
|
||||
'system_foo',
|
||||
'project_foo',
|
||||
'other_project_member',
|
||||
'other_project_reader',
|
||||
]
|
||||
|
||||
# NOTE(Xena): The authorized_admins and unauthorized_admins are the same
|
||||
# as the QuotasPolicyTest's. This is because in Xena the "admin only"
|
||||
# rules are the legacy RULE_ADMIN_API. This will change in Yoga, when
|
||||
# RULE_ADMIN_API will be deprecated in favor of the SYSTEM_ADMIN rule that
|
||||
# is scope based.
|
||||
|
||||
def setUp(self, *args, **kwargs):
|
||||
# Test secure RBAC by disabling deprecated policy rules (scope
|
||||
# is still not enabled).
|
||||
super().setUp(enforce_scope=False, enforce_new_defaults=True,
|
||||
*args, **kwargs)
|
Loading…
Reference in New Issue
Block a user