diff options
author | Zuul <zuul@review.opendev.org> | 2020-04-14 14:56:30 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2020-04-14 14:56:31 +0000 |
commit | 08ad283eae6a590fb78925ada38a542f5d0cd57b (patch) | |
tree | 4309a98eee8ffbe077b4e5607970d2470abfa08b | |
parent | 0e8928ab1fdbcf3a7ce5eaa5016ad034c4928d7d (diff) | |
parent | 82303e0561b6ffe179478c4b420f98218aa586f1 (diff) | |
download | nova-08ad283eae6a590fb78925ada38a542f5d0cd57b.tar.gz |
Merge "Pass the actual target in server group policy"
-rw-r--r-- | nova/api/openstack/compute/server_groups.py | 43 | ||||
-rw-r--r-- | nova/policies/server_groups.py | 12 | ||||
-rw-r--r-- | nova/tests/unit/fake_policy.py | 1 | ||||
-rw-r--r-- | nova/tests/unit/policies/test_server_groups.py | 89 | ||||
-rw-r--r-- | nova/tests/unit/test_policy.py | 1 |
5 files changed, 117 insertions, 29 deletions
diff --git a/nova/api/openstack/compute/server_groups.py b/nova/api/openstack/compute/server_groups.py index 059103017e..c1fd2634be 100644 --- a/nova/api/openstack/compute/server_groups.py +++ b/nova/api/openstack/compute/server_groups.py @@ -42,12 +42,6 @@ CONF = nova.conf.CONF GROUP_POLICY_OBJ_MICROVERSION = "2.64" -def _authorize_context(req, action): - context = req.environ['nova.context'] - context.can(sg_policies.POLICY_ROOT % action) - return context - - def _get_not_deleted(context, uuids): mappings = objects.InstanceMappingList.get_by_instance_uuids( context, uuids) @@ -126,9 +120,11 @@ class ServerGroupController(wsgi.Controller): @wsgi.expected_errors(404) def show(self, req, id): """Return data about the given server group.""" - context = _authorize_context(req, 'show') + context = req.environ['nova.context'] try: sg = objects.InstanceGroup.get_by_uuid(context, id) + context.can(sg_policies.POLICY_ROOT % 'show', + target={'project_id': sg.project_id}) except nova.exception.InstanceGroupNotFound as e: raise webob.exc.HTTPNotFound(explanation=e.format_message()) return {'server_group': self._format_server_group(context, sg, req)} @@ -137,9 +133,11 @@ class ServerGroupController(wsgi.Controller): @wsgi.expected_errors(404) def delete(self, req, id): """Delete a server group.""" - context = _authorize_context(req, 'delete') + context = req.environ['nova.context'] try: sg = objects.InstanceGroup.get_by_uuid(context, id) + context.can(sg_policies.POLICY_ROOT % 'delete', + target={'project_id': sg.project_id}) except nova.exception.InstanceGroupNotFound as e: raise webob.exc.HTTPNotFound(explanation=e.format_message()) try: @@ -152,9 +150,24 @@ class ServerGroupController(wsgi.Controller): @validation.query_schema(schema.server_groups_query_param, '2.0', '2.74') def index(self, req): """Returns a list of server groups.""" - context = _authorize_context(req, 'index') + context = req.environ['nova.context'] project_id = context.project_id + # NOTE(gmann): Using context's project_id as target here so + # that when we remove the default target from policy class, + # it does not fail if user requesting operation on for their + # own server group. + context.can(sg_policies.POLICY_ROOT % 'index', + target={'project_id': project_id}) if 'all_projects' in req.GET and context.is_admin: + # TODO(gmann): Remove the is_admin check in the above condition + # so that the below policy can raise error if not allowed. + # In existing behavior, if non-admin users requesting + # all projects server groups they do not get error instead + # get their own server groups. Once we switch to policy + # new defaults completly then we can remove the above check. + # Until then, let's keep the old behaviour. + context.can(sg_policies.POLICY_ROOT % 'index:all_projects', + target={}) sgs = objects.InstanceGroupList.get_all(context) else: sgs = objects.InstanceGroupList.get_by_project_id( @@ -171,11 +184,13 @@ class ServerGroupController(wsgi.Controller): @validation.schema(schema.create_v264, GROUP_POLICY_OBJ_MICROVERSION) def create(self, req, body): """Creates a new server group.""" - context = _authorize_context(req, 'create') - + context = req.environ['nova.context'] + project_id = context.project_id + context.can(sg_policies.POLICY_ROOT % 'create', + target={'project_id': project_id}) try: objects.Quotas.check_deltas(context, {'server_groups': 1}, - context.project_id, context.user_id) + project_id, context.user_id) except nova.exception.OverQuota: msg = _("Quota exceeded, too many server groups.") raise exc.HTTPForbidden(explanation=msg) @@ -201,7 +216,7 @@ class ServerGroupController(wsgi.Controller): sg = objects.InstanceGroup(context, policy=policies[0]) try: sg.name = vals.get('name') - sg.project_id = context.project_id + sg.project_id = project_id sg.user_id = context.user_id sg.create() except ValueError as e: @@ -214,7 +229,7 @@ class ServerGroupController(wsgi.Controller): if CONF.quota.recheck_quota: try: objects.Quotas.check_deltas(context, {'server_groups': 0}, - context.project_id, + project_id, context.user_id) except nova.exception.OverQuota: sg.destroy() diff --git a/nova/policies/server_groups.py b/nova/policies/server_groups.py index f678213617..e9b95d316d 100644 --- a/nova/policies/server_groups.py +++ b/nova/policies/server_groups.py @@ -59,6 +59,18 @@ server_groups_policies = [ scope_types=['system', 'project'] ), policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'index:all_projects', + check_str=base.RULE_ADMIN_API, + description="List all server groups for all projects", + operations=[ + { + 'path': '/os-server-groups', + 'method': 'GET' + } + ], + scope_types=['system'] + ), + policy.DocumentedRuleDefault( name=POLICY_ROOT % 'show', check_str=base.RULE_ADMIN_OR_OWNER, description="Show details of a server group", diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index b9aeaa2882..a2ff6f7c6e 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -118,6 +118,7 @@ policy_data = """ "os_compute_api:os-server-tags:delete_all": "", "os_compute_api:os-server-groups:show": "", "os_compute_api:os-server-groups:index": "", + "os_compute_api:os-server-groups:index:all_projects": "", "os_compute_api:os-server-groups:create": "", "os_compute_api:os-server-groups:delete": "", "os_compute_api:os-services:list": "", diff --git a/nova/tests/unit/policies/test_server_groups.py b/nova/tests/unit/policies/test_server_groups.py index 05e0084cdd..babdab3f75 100644 --- a/nova/tests/unit/policies/test_server_groups.py +++ b/nova/tests/unit/policies/test_server_groups.py @@ -10,10 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import fixtures import mock from oslo_utils.fixture import uuidsentinel as uuids from nova.api.openstack.compute import server_groups +from nova import objects from nova.policies import server_groups as policies from nova.tests.unit.api.openstack import fakes from nova.tests.unit.policies import base @@ -32,32 +34,76 @@ class ServerGroupPolicyTest(base.BasePolicyTest): self.controller = server_groups.ServerGroupController() self.req = fakes.HTTPRequest.blank('') - # Policy is admin_or_owner but we do not pass the project id - # in policy enforcement to check the ownership. project id - # is nothing but of server group for which request is made. So - # let's keep it as it is and with new defaults and sceop enbled, - # these can be authorized to meanigful roles. + self.mock_get = self.useFixture( + fixtures.MockPatch('nova.objects.InstanceGroup.get_by_uuid')).mock + self.sg = [objects.InstanceGroup( + uuid=uuids.fake_id, name='fake', + project_id=self.project_id, user_id='u1', + policies=[], members=[]), + objects.InstanceGroup( + uuid=uuids.fake_id, name='fake2', project_id='proj2', + user_id='u2', policies=[], members=[])] + self.mock_get.return_value = self.sg[0] + + # Check that admin or and owner is able to get and delete + # the server group. self.admin_or_owner_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, self.project_admin_context, self.project_member_context, + self.project_reader_context, self.project_foo_context] + # Check that non-admin/owner is not able to get and delete + # the server group. + self.admin_or_owner_unauthorized_contexts = [ + self.system_member_context, self.system_reader_context, + self.system_foo_context, + self.other_project_member_context + ] + + # Check that everyone is able to list and create + # theie own server group. + self.everyone_authorized_contexts = [ + self.legacy_admin_context, self.system_admin_context, + self.project_admin_context, self.project_member_context, self.project_reader_context, self.project_foo_context, self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_member_context] - self.admin_or_owner_unauthorized_contexts = [ + self.everyone_unauthorized_contexts = [ ] - @mock.patch('nova.objects.InstanceGroupList.get_all') + @mock.patch('nova.objects.InstanceGroupList.get_by_project_id') def test_index_server_groups_policy(self, mock_get): rule_name = policies.POLICY_ROOT % 'index' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, + self.common_policy_check(self.everyone_authorized_contexts, + self.everyone_unauthorized_contexts, rule_name, self.controller.index, self.req) - @mock.patch('nova.objects.InstanceGroup.get_by_uuid') - def test_show_server_groups_policy(self, mock_get): + @mock.patch('nova.objects.InstanceGroupList.get_all') + def test_index_all_project_server_groups_policy(self, mock_get_all): + mock_get_all.return_value = objects.InstanceGroupList(objects=self.sg) + # 'index' policy is checked before 'index:all_projects' so + # we have to allow it for everyone otherwise it will fail for + # unauthorized contexts here. + rule = policies.POLICY_ROOT % 'index' + self.policy.set_rules({rule: "@"}, overwrite=False) + admin_req = fakes.HTTPRequest.blank( + '/os-server-groups?all_projects=True', + version='2.13', use_admin_context=True) + # Check admin user get all projects server groups. + resp = self.controller.index(admin_req) + projs = [sg['project_id'] for sg in resp['server_groups']] + self.assertEqual(2, len(projs)) + self.assertIn('proj2', projs) + # Check non-admin user does not get all projects server groups. + req = fakes.HTTPRequest.blank('/os-server-groups?all_projects=True', + version='2.13') + resp = self.controller.index(req) + projs = [sg['project_id'] for sg in resp['server_groups']] + self.assertNotIn('proj2', projs) + + def test_show_server_groups_policy(self): rule_name = policies.POLICY_ROOT % 'show' self.common_policy_check(self.admin_or_owner_authorized_contexts, self.admin_or_owner_unauthorized_contexts, @@ -70,14 +116,14 @@ class ServerGroupPolicyTest(base.BasePolicyTest): rule_name = policies.POLICY_ROOT % 'create' body = {'server_group': {'name': 'fake', 'policies': ['affinity']}} - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, + self.common_policy_check(self.everyone_authorized_contexts, + self.everyone_unauthorized_contexts, rule_name, self.controller.create, self.req, body=body) - @mock.patch('nova.objects.InstanceGroup.get_by_uuid') - def test_delete_server_groups_policy(self, mock_get): + @mock.patch('nova.objects.InstanceGroup.destroy') + def test_delete_server_groups_policy(self, mock_destroy): rule_name = policies.POLICY_ROOT % 'delete' self.common_policy_check(self.admin_or_owner_authorized_contexts, self.admin_or_owner_unauthorized_contexts, @@ -99,3 +145,16 @@ class ServerGroupScopeTypePolicyTest(ServerGroupPolicyTest): def setUp(self): super(ServerGroupScopeTypePolicyTest, self).setUp() self.flags(enforce_scope=True, group="oslo_policy") + + # TODO(gmann): Test this with system scope once we remove + # the hardcoded admin check + def test_index_all_project_server_groups_policy(self): + pass + + +class ServerGroupNoLegacyPolicyTest(ServerGroupScopeTypePolicyTest): + """Test Server Group APIs policies with system scope enabled, + and no more deprecated rules that allow the legacy admin API to + access system APIs. + """ + without_deprecated_rules = True diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py index 13a422daf7..ee4a4b87bc 100644 --- a/nova/tests/unit/test_policy.py +++ b/nova/tests/unit/test_policy.py @@ -350,6 +350,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase): "os_compute_api:os-quota-sets:update", "os_compute_api:os-quota-sets:delete", "os_compute_api:os-server-diagnostics", +"os_compute_api:os-server-groups:index:all_projects", "os_compute_api:os-services:update", "os_compute_api:os-services:delete", "os_compute_api:os-shelve:shelve_offload", |