From 77973ba7c58b522b03b1a5cf51cf223132d9ad97 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Tue, 1 May 2018 15:27:07 -0400 Subject: [PATCH] Add granular policy rules for /resource_classes* This adds policy rules or the various /resource_classes* routes with the default being backward compatible with existing deployments (requires role:admin). The "non admin forbidden non json" gabbit had to be changed because the policy check moved so the test would fail with a 406 response instead of 403. Part of blueprint granular-placement-policy Change-Id: I90ef62e8e7b9e75830ac60e0c5c7e92845ab4afe --- nova/api/openstack/placement/handler.py | 5 +- .../placement/handlers/resource_class.py | 7 ++ .../openstack/placement/policies/__init__.py | 2 + .../placement/policies/resource_class.py | 81 +++++++++++++++++++ .../gabbits/resource-classes-policy.yaml | 40 +++++++++ .../placement/gabbits/resource-classes.yaml | 14 ++-- 6 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 nova/api/openstack/placement/policies/resource_class.py create mode 100644 nova/tests/functional/api/openstack/placement/gabbits/resource-classes-policy.yaml diff --git a/nova/api/openstack/placement/handler.py b/nova/api/openstack/placement/handler.py index 1e36a9e0ec5d..f76f8000ed04 100644 --- a/nova/api/openstack/placement/handler.py +++ b/nova/api/openstack/placement/handler.py @@ -141,7 +141,10 @@ PER_ROUTE_POLICY = [ '/$', # /resource_providers # /resource_providers/{uuid} - '/resource_providers(/[A-Za-z0-9-]+)?$' + '/resource_providers(/[A-Za-z0-9-]+)?$', + # /resource_classes + # /resource_classes/{name} + '/resource_classes', ] diff --git a/nova/api/openstack/placement/handlers/resource_class.py b/nova/api/openstack/placement/handlers/resource_class.py index 97e37ca60f0e..be1371709898 100644 --- a/nova/api/openstack/placement/handlers/resource_class.py +++ b/nova/api/openstack/placement/handlers/resource_class.py @@ -19,6 +19,7 @@ import webob from nova.api.openstack.placement import exception from nova.api.openstack.placement import microversion from nova.api.openstack.placement.objects import resource_provider as rp_obj +from nova.api.openstack.placement.policies import resource_class as policies from nova.api.openstack.placement.schemas import resource_class as schema from nova.api.openstack.placement import util from nova.api.openstack.placement import wsgi_wrapper @@ -62,6 +63,7 @@ def create_resource_class(req): header pointing to the newly created resource class. """ context = req.environ['placement.context'] + context.can(policies.CREATE) data = util.extract_json(req.body, schema.POST_RC_SCHEMA_V1_2) try: @@ -93,6 +95,7 @@ def delete_resource_class(req): """ name = util.wsgi_path_item(req.environ, 'name') context = req.environ['placement.context'] + context.can(policies.DELETE) # The containing application will catch a not found here. rc = rp_obj.ResourceClass.get_by_name(context, name) try: @@ -119,6 +122,7 @@ def get_resource_class(req): """ name = util.wsgi_path_item(req.environ, 'name') context = req.environ['placement.context'] + context.can(policies.SHOW) want_version = req.environ[microversion.MICROVERSION_ENVIRON] # The containing application will catch a not found here. rc = rp_obj.ResourceClass.get_by_name(context, name) @@ -147,6 +151,7 @@ def list_resource_classes(req): a collection of resource classes. """ context = req.environ['placement.context'] + context.can(policies.LIST) want_version = req.environ[microversion.MICROVERSION_ENVIRON] rcs = rp_obj.ResourceClassList.get_all(context) @@ -172,6 +177,7 @@ def update_resource_class(req): """ name = util.wsgi_path_item(req.environ, 'name') context = req.environ['placement.context'] + context.can(policies.UPDATE) data = util.extract_json(req.body, schema.PUT_RC_SCHEMA_V1_2) @@ -210,6 +216,7 @@ def update_resource_class(req): """ name = util.wsgi_path_item(req.environ, 'name') context = req.environ['placement.context'] + context.can(policies.UPDATE) # Use JSON validation to validation resource class name. util.extract_json('{"name": "%s"}' % name, schema.PUT_RC_SCHEMA_V1_2) diff --git a/nova/api/openstack/placement/policies/__init__.py b/nova/api/openstack/placement/policies/__init__.py index 575f1d8642e8..7efd30fbc701 100644 --- a/nova/api/openstack/placement/policies/__init__.py +++ b/nova/api/openstack/placement/policies/__init__.py @@ -13,6 +13,7 @@ import itertools from nova.api.openstack.placement.policies import base +from nova.api.openstack.placement.policies import resource_class from nova.api.openstack.placement.policies import resource_provider @@ -20,4 +21,5 @@ def list_rules(): return itertools.chain( base.list_rules(), resource_provider.list_rules(), + resource_class.list_rules(), ) diff --git a/nova/api/openstack/placement/policies/resource_class.py b/nova/api/openstack/placement/policies/resource_class.py new file mode 100644 index 000000000000..9c3e384406f9 --- /dev/null +++ b/nova/api/openstack/placement/policies/resource_class.py @@ -0,0 +1,81 @@ +# 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 nova.api.openstack.placement.policies import base + + +PREFIX = 'placement:resource_classes:%s' +LIST = PREFIX % 'list' +CREATE = PREFIX % 'create' +SHOW = PREFIX % 'show' +UPDATE = PREFIX % 'update' +DELETE = PREFIX % 'delete' + +rules = [ + policy.DocumentedRuleDefault( + LIST, + base.RULE_ADMIN_API, + "List resource classes.", + [ + { + 'method': 'GET', + 'path': '/resource_classes' + } + ]), + policy.DocumentedRuleDefault( + CREATE, + base.RULE_ADMIN_API, + "Create resource class.", + [ + { + 'method': 'POST', + 'path': '/resource_classes' + } + ]), + policy.DocumentedRuleDefault( + SHOW, + base.RULE_ADMIN_API, + "Show resource class.", + [ + { + 'method': 'GET', + 'path': '/resource_classes/{name}' + } + ]), + policy.DocumentedRuleDefault( + UPDATE, + base.RULE_ADMIN_API, + "Update resource class.", + [ + { + 'method': 'PUT', + 'path': '/resource_classes/{name}' + } + ]), + policy.DocumentedRuleDefault( + DELETE, + base.RULE_ADMIN_API, + "Delete resource class.", + [ + { + 'method': 'DELETE', + 'path': '/resource_classes/{name}' + } + ]), +] + + +def list_rules(): + return rules diff --git a/nova/tests/functional/api/openstack/placement/gabbits/resource-classes-policy.yaml b/nova/tests/functional/api/openstack/placement/gabbits/resource-classes-policy.yaml new file mode 100644 index 000000000000..51f14b4336e0 --- /dev/null +++ b/nova/tests/functional/api/openstack/placement/gabbits/resource-classes-policy.yaml @@ -0,0 +1,40 @@ +# This tests the individual CRUD operations on /resource_classes +# using a non-admin user with an open policy configuration. The +# response validation is intentionally minimal. +fixtures: + - OpenPolicyFixture + +defaults: + request_headers: + x-auth-token: user + accept: application/json + content-type: application/json + openstack-api-version: placement latest + +tests: + +- name: list resource classes + GET: /resource_classes + response_json_paths: + $.resource_classes.`len`: 12 # Number of standard resource classes + +- name: create resource class + POST: /resource_classes + data: + name: CUSTOM_RES_CLASS_POLICY + status: 201 + response_headers: + location: //resource_classes/CUSTOM_RES_CLASS_POLICY/ + +- name: show resource class + GET: /resource_classes/CUSTOM_RES_CLASS_POLICY + response_json_paths: + $.name: CUSTOM_RES_CLASS_POLICY + +- name: update resource class + PUT: /resource_classes/CUSTOM_NEW_CLASS_POLICY + status: 201 + +- name: delete resource class + DELETE: /resource_classes/CUSTOM_NEW_CLASS_POLICY + status: 204 diff --git a/nova/tests/functional/api/openstack/placement/gabbits/resource-classes.yaml b/nova/tests/functional/api/openstack/placement/gabbits/resource-classes.yaml index 801e811e93bd..f77b1280b30c 100644 --- a/nova/tests/functional/api/openstack/placement/gabbits/resource-classes.yaml +++ b/nova/tests/functional/api/openstack/placement/gabbits/resource-classes.yaml @@ -63,14 +63,16 @@ tests: response_json_paths: $.errors[0].title: Forbidden -- name: non admin forbidden non json - GET: /resource_classes +- name: post invalid non json + POST: /resource_classes request_headers: - x-auth-token: user - accept: text/plain - status: 403 + accept: text/plain + content-type: application/json + data: + name: FOO + status: 400 response_strings: - - admin required + - JSON does not validate - name: post illegal characters in name POST: /resource_classes