diff --git a/nova/policies/servers.py b/nova/policies/servers.py index 5354d3bcbb..952963bdca 100644 --- a/nova/policies/servers.py +++ b/nova/policies/servers.py @@ -25,50 +25,55 @@ CROSS_CELL_RESIZE = 'compute:servers:resize:cross_cell' rules = [ policy.DocumentedRuleDefault( - SERVERS % 'index', - RULE_AOO, - "List all servers", - [ + name=SERVERS % 'index', + check_str=RULE_AOO, + description="List all servers", + operations=[ { 'method': 'GET', 'path': '/servers' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'detail', - RULE_AOO, - "List all servers with detailed information", - [ + name=SERVERS % 'detail', + check_str=RULE_AOO, + description="List all servers with detailed information", + operations=[ { 'method': 'GET', 'path': '/servers/detail' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'index:get_all_tenants', - base.RULE_ADMIN_API, - "List all servers for all projects", - [ + name=SERVERS % 'index:get_all_tenants', + check_str=base.RULE_ADMIN_API, + description="List all servers for all projects", + operations=[ { 'method': 'GET', 'path': '/servers' } - ]), + ], + scope_types=['system']), policy.DocumentedRuleDefault( - SERVERS % 'detail:get_all_tenants', - base.RULE_ADMIN_API, - "List all servers with detailed information for all projects", - [ + name=SERVERS % 'detail:get_all_tenants', + check_str=base.RULE_ADMIN_API, + description="List all servers with detailed information for " + " all projects", + operations=[ { 'method': 'GET', 'path': '/servers/detail' } - ]), + ], + scope_types=['system']), policy.DocumentedRuleDefault( - SERVERS % 'allow_all_filters', - base.RULE_ADMIN_API, - "Allow all filters when listing servers", - [ + name=SERVERS % 'allow_all_filters', + check_str=base.RULE_ADMIN_API, + description="Allow all filters when listing servers", + operations=[ { 'method': 'GET', 'path': '/servers' @@ -77,17 +82,19 @@ rules = [ 'method': 'GET', 'path': '/servers/detail' } - ]), + ], + scope_types=['system']), policy.DocumentedRuleDefault( - SERVERS % 'show', - RULE_AOO, - "Show a server", - [ + name=SERVERS % 'show', + check_str=RULE_AOO, + description="Show a server", + operations=[ { 'method': 'GET', 'path': '/servers/{server_id}' } - ]), + ], + scope_types=['system', 'project']), # the details in host_status are pretty sensitive, only admins # should do that by default. policy.DocumentedRuleDefault( @@ -148,31 +155,33 @@ allow everyone. } ]), policy.DocumentedRuleDefault( - SERVERS % 'create', - RULE_AOO, - "Create a server", - [ + name=SERVERS % 'create', + check_str=RULE_AOO, + description="Create a server", + operations=[ { 'method': 'POST', 'path': '/servers' } - ]), + ], + scope_types=['project']), policy.DocumentedRuleDefault( - SERVERS % 'create:forced_host', - base.RULE_ADMIN_API, - """ + name=SERVERS % 'create:forced_host', + check_str=base.RULE_ADMIN_API, + description=""" Create a server on the specified host and/or node. In this case, the server is forced to launch on the specified host and/or node by bypassing the scheduler filters unlike the ``compute:servers:create:requested_destination`` rule. """, - [ + operations=[ { 'method': 'POST', 'path': '/servers' } - ]), + ], + scope_types=['project']), policy.DocumentedRuleDefault( REQUESTED_DESTINATION, base.RULE_ADMIN_API, @@ -191,35 +200,39 @@ validated by the scheduler filters unlike the } ]), policy.DocumentedRuleDefault( - SERVERS % 'create:attach_volume', - RULE_AOO, - "Create a server with the requested volume attached to it", - [ + name=SERVERS % 'create:attach_volume', + check_str=RULE_AOO, + description="Create a server with the requested volume attached to it", + operations=[ { 'method': 'POST', 'path': '/servers' } - ]), + ], + scope_types=['project']), policy.DocumentedRuleDefault( - SERVERS % 'create:attach_network', - RULE_AOO, - "Create a server with the requested network attached to it", - [ + name=SERVERS % 'create:attach_network', + check_str=RULE_AOO, + description="Create a server with the requested network attached " + " to it", + operations=[ { 'method': 'POST', 'path': '/servers' } - ]), + ], + scope_types=['project']), policy.DocumentedRuleDefault( - SERVERS % 'create:trusted_certs', - RULE_AOO, - "Create a server with trusted image certificate IDs", - [ + name=SERVERS % 'create:trusted_certs', + check_str=RULE_AOO, + description="Create a server with trusted image certificate IDs", + operations=[ { 'method': 'POST', 'path': '/servers' } - ]), + ], + scope_types=['project']), policy.DocumentedRuleDefault( ZERO_DISK_FLAVOR, base.RULE_ADMIN_API, @@ -261,65 +274,71 @@ https://bugs.launchpad.net/nova/+bug/1739646 for details. } ]), policy.DocumentedRuleDefault( - SERVERS % 'delete', - RULE_AOO, - "Delete a server", - [ + name=SERVERS % 'delete', + check_str=RULE_AOO, + description="Delete a server", + operations=[ { 'method': 'DELETE', 'path': '/servers/{server_id}' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'update', - RULE_AOO, - "Update a server", - [ + name=SERVERS % 'update', + check_str=RULE_AOO, + description="Update a server", + operations=[ { 'method': 'PUT', 'path': '/servers/{server_id}' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'confirm_resize', - RULE_AOO, - "Confirm a server resize", - [ + name=SERVERS % 'confirm_resize', + check_str=RULE_AOO, + description="Confirm a server resize", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (confirmResize)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'revert_resize', - RULE_AOO, - "Revert a server resize", - [ + name=SERVERS % 'revert_resize', + check_str=RULE_AOO, + description="Revert a server resize", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (revertResize)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'reboot', - RULE_AOO, - "Reboot a server", - [ + name=SERVERS % 'reboot', + check_str=RULE_AOO, + description="Reboot a server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (reboot)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'resize', - RULE_AOO, - "Resize a server", - [ + name=SERVERS % 'resize', + check_str=RULE_AOO, + description="Resize a server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (resize)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( CROSS_CELL_RESIZE, base.RULE_NOBODY, @@ -334,75 +353,82 @@ https://bugs.launchpad.net/nova/+bug/1739646 for details. } ]), policy.DocumentedRuleDefault( - SERVERS % 'rebuild', - RULE_AOO, - "Rebuild a server", - [ + name=SERVERS % 'rebuild', + check_str=RULE_AOO, + description="Rebuild a server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (rebuild)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'rebuild:trusted_certs', - RULE_AOO, - "Rebuild a server with trusted image certificate IDs", - [ + name=SERVERS % 'rebuild:trusted_certs', + check_str=RULE_AOO, + description="Rebuild a server with trusted image certificate IDs", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (rebuild)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'create_image', - RULE_AOO, - "Create an image from a server", - [ + name=SERVERS % 'create_image', + check_str=RULE_AOO, + description="Create an image from a server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (createImage)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'create_image:allow_volume_backed', - RULE_AOO, - "Create an image from a volume backed server", - [ + name=SERVERS % 'create_image:allow_volume_backed', + check_str=RULE_AOO, + description="Create an image from a volume backed server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (createImage)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'start', - RULE_AOO, - "Start a server", - [ + name=SERVERS % 'start', + check_str=RULE_AOO, + description="Start a server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (os-start)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'stop', - RULE_AOO, - "Stop a server", - [ + name=SERVERS % 'stop', + check_str=RULE_AOO, + description="Stop a server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (os-stop)' } - ]), + ], + scope_types=['system', 'project']), policy.DocumentedRuleDefault( - SERVERS % 'trigger_crash_dump', - RULE_AOO, - "Trigger crash dump in a server", - [ + name=SERVERS % 'trigger_crash_dump', + check_str=RULE_AOO, + description="Trigger crash dump in a server", + operations=[ { 'method': 'POST', 'path': '/servers/{server_id}/action (trigger_crash_dump)' } - ]), + ], + scope_types=['system', 'project']), ] diff --git a/nova/tests/unit/policies/base.py b/nova/tests/unit/policies/base.py index 6825edffaa..6f44f88684 100644 --- a/nova/tests/unit/policies/base.py +++ b/nova/tests/unit/policies/base.py @@ -149,9 +149,10 @@ class BasePolicyTest(test.TestCase): def ensure_raises(req, *args, **kwargs): exc = self.assertRaises( exception.PolicyNotAuthorized, func, req, *arg, **kwarg) - self.assertEqual( - "Policy doesn't allow %s to be performed." % - rule_name, exc.format_message()) + if rule_name is not None: + self.assertEqual( + "Policy doesn't allow %s to be performed." % + rule_name, exc.format_message()) # Verify all the context having allowed scope and roles pass # the policy check. for context in authorized_contexts: diff --git a/nova/tests/unit/policies/test_servers.py b/nova/tests/unit/policies/test_servers.py index 91b37f4588..c7a51c72e2 100644 --- a/nova/tests/unit/policies/test_servers.py +++ b/nova/tests/unit/policies/test_servers.py @@ -121,6 +121,17 @@ class ServersPolicyTest(base.BasePolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_member_context ] + # Check that project member is able to create serve + self.project_member_authorized_contexts = [ + self.legacy_admin_context, self.system_admin_context, + self.project_admin_context, self.project_member_context, + self.system_member_context, + self.other_project_member_context, + self.project_reader_context, self.project_foo_context, + self.system_reader_context, self.system_foo_context] + # Check that non-project member is not able to create server + self.project_member_unauthorized_contexts = [ + ] def test_index_server_policy(self): @@ -241,8 +252,8 @@ class ServersPolicyTest(base.BasePolicyTest): def test_create_server_policy(self, mock_create): mock_create.return_value = ([self.instance], '') rule_name = policies.SERVERS % 'create' - self.common_policy_check(self.everyone_authorized_contexts, - self.everyone_unauthorized_contexts, + self.common_policy_check(self.project_member_authorized_contexts, + self.project_member_unauthorized_contexts, rule_name, self.controller.create, self.req, body=self.body) @@ -281,8 +292,8 @@ class ServersPolicyTest(base.BasePolicyTest): 'block_device_mapping': [{'device_name': 'foo'}], }, } - self.common_policy_check(self.everyone_authorized_contexts, - self.everyone_unauthorized_contexts, + self.common_policy_check(self.project_member_authorized_contexts, + self.project_member_unauthorized_contexts, rule_name, self.controller.create, self.req, body=body) @@ -306,8 +317,8 @@ class ServersPolicyTest(base.BasePolicyTest): }], }, } - self.common_policy_check(self.everyone_authorized_contexts, - self.everyone_unauthorized_contexts, + self.common_policy_check(self.project_member_authorized_contexts, + self.project_member_unauthorized_contexts, rule_name, self.controller.create, self.req, body=body) @@ -334,8 +345,8 @@ class ServersPolicyTest(base.BasePolicyTest): }, } - self.common_policy_check(self.everyone_authorized_contexts, - self.everyone_unauthorized_contexts, + self.common_policy_check(self.project_member_authorized_contexts, + self.project_member_unauthorized_contexts, rule_name, self.controller.create, req, body=body) @@ -698,3 +709,143 @@ class ServersScopeTypePolicyTest(ServersPolicyTest): def setUp(self): super(ServersScopeTypePolicyTest, self).setUp() self.flags(enforce_scope=True, group="oslo_policy") + + # Check that system admin is able to list the servers + # for all projects. + self.admin_authorized_contexts = [ + self.system_admin_context] + # Check that non-system/admin is not able to list the servers + # for all projects. + self.admin_unauthorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.system_member_context, self.system_reader_context, + self.system_foo_context, self.project_member_context, + self.project_reader_context, self.project_foo_context, + self.other_project_member_context + ] + + # Check if project member can create the server. + self.project_member_authorized_contexts = [ + self.legacy_admin_context, + self.project_admin_context, self.project_member_context, + self.other_project_member_context, self.project_reader_context, + self.project_foo_context + ] + # Check if non-project member cannot create the server. + self.project_member_unauthorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context, self.system_foo_context, + ] + + # Check if project admin can create the server with host. + self.project_admin_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context + ] + # Check if non-project admin cannot create the server with host. + self.project_admin_unauthorized_contexts = [ + self.project_member_context, self.other_project_member_context, + self.project_reader_context, self.project_foo_context, + self.system_admin_context, self.system_member_context, + self.system_reader_context, self.system_foo_context, + ] + + @mock.patch('nova.compute.api.API.create') + @mock.patch('nova.compute.api.API.parse_availability_zone') + def test_create_forced_host_server_policy(self, mock_az, mock_create): + # These policy are project scoped only and 'create' policy is checked + # before 'create:forced_host' so we need to allow 'create' policy + # for everyone. Also skip the error message assertion because for + # system scoped unauth context 'create' policy fail and for project + # scoped unauth context 'create:forced_host' fail. + rule = policies.SERVERS % 'create' + self.policy.set_rules({rule: "@"}, overwrite=False) + mock_create.return_value = ([self.instance], '') + mock_az.return_value = ('test', 'host', None) + # rule_name = policies.SERVERS % 'create:forced_host' + self.common_policy_check(self.project_admin_authorized_contexts, + self.project_admin_unauthorized_contexts, + None, + self.controller.create, + self.req, body=self.body) + + @mock.patch('nova.compute.api.API.create') + def test_create_attach_volume_server_policy(self, mock_create): + # These policy are project scoped only and 'create' policy is checked + # before 'create:attach_volume' so even we allow it for everyone + # system scoped context cannot validate theese as they fail + # on 'create' policy itself. + # So sending the 'create' rule for policy error assertion. + rule = policies.SERVERS % 'create' + self.policy.set_rules({rule: "@"}, overwrite=False) + mock_create.return_value = ([self.instance], '') + # rule_name = policies.SERVERS % 'create:attach_volume' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': uuids.fake_id, + 'flavorRef': uuids.fake_id, + 'block_device_mapping': [{'device_name': 'foo'}], + }, + } + self.common_policy_check(self.project_member_authorized_contexts, + self.project_member_unauthorized_contexts, + rule, + self.controller.create, + self.req, body=body) + + @mock.patch('nova.compute.api.API.create') + def test_create_attach_network_server_policy(self, mock_create): + # These policy are project scoped only and 'create' policy is checked + # before 'create:attach_network' so even we allow it for everyone + # system scoped context cannot validate theese as they fail + # on 'create' policy itself. + # So sending the 'create' rule for policy error assertion. + rule = policies.SERVERS % 'create' + self.policy.set_rules({rule: "@"}, overwrite=False) + mock_create.return_value = ([self.instance], '') + # rule_name = policies.SERVERS % 'create:attach_network' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': uuids.fake_id, + 'flavorRef': uuids.fake_id, + 'networks': [{ + 'uuid': uuids.fake_id + }], + }, + } + self.common_policy_check(self.project_member_authorized_contexts, + self.project_member_unauthorized_contexts, + rule, + self.controller.create, + self.req, body=body) + + @mock.patch('nova.compute.api.API.create') + def test_create_trusted_certs_server_policy(self, mock_create): + # These policy are project scoped only and 'create' policy is checked + # before 'create:trusted_certs' so even we allow it for everyone + # system scoped context cannot validate theese as they fail + # on 'create' policy itself. + # So sending the 'create' rule for policy error assertion. + rule = policies.SERVERS % 'create' + self.policy.set_rules({rule: "@"}, overwrite=False) + req = fakes.HTTPRequest.blank('', version='2.63') + mock_create.return_value = ([self.instance], '') + # rule_name = policies.SERVERS % 'create:trusted_certs' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': uuids.fake_id, + 'flavorRef': uuids.fake_id, + 'trusted_image_certificates': [uuids.fake_id], + 'networks': [{ + 'uuid': uuids.fake_id + }], + + }, + } + self.common_policy_check(self.project_member_authorized_contexts, + self.project_member_unauthorized_contexts, + rule, + self.controller.create, + req, body=body)