diff --git a/neutron/conf/policies/__init__.py b/neutron/conf/policies/__init__.py index a0ec84b43c8..40ec41b18ca 100644 --- a/neutron/conf/policies/__init__.py +++ b/neutron/conf/policies/__init__.py @@ -31,6 +31,7 @@ from neutron.conf.policies import network_ip_availability from neutron.conf.policies import network_segment_range from neutron.conf.policies import port from neutron.conf.policies import qos +from neutron.conf.policies import quotas from neutron.conf.policies import rbac from neutron.conf.policies import router from neutron.conf.policies import security_group @@ -60,6 +61,7 @@ def list_rules(): network_segment_range.list_rules(), port.list_rules(), qos.list_rules(), + quotas.list_rules(), rbac.list_rules(), router.list_rules(), security_group.list_rules(), diff --git a/neutron/conf/policies/quotas.py b/neutron/conf/policies/quotas.py new file mode 100644 index 00000000000..06f82447404 --- /dev/null +++ b/neutron/conf/policies/quotas.py @@ -0,0 +1,64 @@ +# 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 neutron.conf.policies import base + + +COLLECTION_PATH = '/quota' +RESOURCE_PATH = '/quota/{id}' + + +rules = [ + policy.DocumentedRuleDefault( + 'get_quota', + base.RULE_ADMIN_ONLY, + 'Get a resource quota', + [ + { + 'method': 'GET', + 'path': COLLECTION_PATH, + }, + { + 'method': 'GET', + 'path': RESOURCE_PATH, + }, + ] + ), + policy.DocumentedRuleDefault( + 'update_quota', + base.RULE_ADMIN_ONLY, + 'Update a resource quota', + [ + { + 'method': 'PUT', + 'path': RESOURCE_PATH, + }, + ] + ), + policy.DocumentedRuleDefault( + 'delete_quota', + base.RULE_ADMIN_ONLY, + 'Delete a resource quota', + [ + { + 'method': 'DELETE', + 'path': RESOURCE_PATH, + }, + ] + ), +] + + +def list_rules(): + return rules diff --git a/neutron/extensions/quotasv2.py b/neutron/extensions/quotasv2.py index dc88017126f..4b2fb96a502 100644 --- a/neutron/extensions/quotasv2.py +++ b/neutron/extensions/quotasv2.py @@ -27,6 +27,7 @@ from neutron._i18n import _ from neutron.api import extensions from neutron.api.v2 import base from neutron.api.v2 import resource +from neutron import policy from neutron import quota from neutron.quota import resource_registry from neutron import wsgi @@ -42,6 +43,14 @@ EXTENDED_ATTRIBUTES_2_0 = { } +def validate_policy(context, policy_name): + policy.init() + policy.enforce(context, + policy_name, + target={'project_id': context.project_id}, + plugin=None) + + class QuotaSetsController(wsgi.Controller): def __init__(self, plugin): @@ -70,12 +79,11 @@ class QuotaSetsController(wsgi.Controller): tenant_id) def default(self, request, id): - if id != request.context.tenant_id: - self._check_admin(request.context, - reason=_("Only admin is authorized " - "to access quotas for another tenant")) + context = request.context + if id != context.tenant_id: + validate_policy(context, "get_quota") return {self._resource_name: self._driver.get_default_quotas( - context=request.context, + context=context, resources=resource_registry.get_all_resources(), tenant_id=id)} @@ -85,7 +93,7 @@ class QuotaSetsController(wsgi.Controller): def index(self, request): context = request.context - self._check_admin(context) + validate_policy(context, "get_quota") return {self._resource_name + "s": self._driver.get_all_quotas( context, resource_registry.get_all_resources())} @@ -99,22 +107,15 @@ class QuotaSetsController(wsgi.Controller): def show(self, request, id): if id != request.context.tenant_id: - self._check_admin(request.context, - reason=_("Only admin is authorized " - "to access quotas for another tenant")) + validate_policy(request.context, "get_quota") return {self._resource_name: self._get_quotas(request, id)} - def _check_admin(self, context, - reason=_("Only admin can view or configure quota")): - if not context.is_admin: - raise exceptions.AdminRequired(reason=reason) - def delete(self, request, id): - self._check_admin(request.context) + validate_policy(request.context, "delete_quota") self._driver.delete_tenant_quota(request.context, id) def update(self, request, id, body=None): - self._check_admin(request.context) + validate_policy(request.context, "update_quota") if self._update_extended_attributes: self._update_attributes() body = base.Controller.prepare_request_body( diff --git a/releasenotes/notes/bug-1671448-bfba96e42c7f2dbe.yaml b/releasenotes/notes/bug-1671448-bfba96e42c7f2dbe.yaml new file mode 100644 index 00000000000..322d9614955 --- /dev/null +++ b/releasenotes/notes/bug-1671448-bfba96e42c7f2dbe.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + `1671448 `_ + Access for Neutron quotas now governed using standard configurable + RBAC policies: 'get_quota', 'update_quota', 'delete_quota'