diff --git a/nova/api/openstack/compute/hosts.py b/nova/api/openstack/compute/hosts.py index 2e9cdd0c8fb0..a891ace1c721 100644 --- a/nova/api/openstack/compute/hosts.py +++ b/nova/api/openstack/compute/hosts.py @@ -71,7 +71,8 @@ class HostController(wsgi.Controller): """ context = req.environ['nova.context'] - context.can(hosts_policies.BASE_POLICY_NAME) + context.can(hosts_policies.POLICY_NAME % 'list', + target={}) filters = {'disabled': False} zone = req.GET.get('zone', None) if zone: @@ -108,7 +109,8 @@ class HostController(wsgi.Controller): return val == "enable" context = req.environ['nova.context'] - context.can(hosts_policies.BASE_POLICY_NAME) + context.can(hosts_policies.POLICY_NAME % 'update', + target={}) # See what the user wants to 'update' status = body.get('status') maint_mode = body.get('maintenance_mode') @@ -168,7 +170,6 @@ class HostController(wsgi.Controller): def _host_power_action(self, req, host_name, action): """Reboots, shuts down or powers up the host.""" context = req.environ['nova.context'] - context.can(hosts_policies.BASE_POLICY_NAME) try: result = self.api.host_power_action(context, host_name, action) except NotImplementedError: @@ -182,16 +183,25 @@ class HostController(wsgi.Controller): @wsgi.Controller.api_version("2.1", "2.42") @wsgi.expected_errors((400, 404, 501)) def startup(self, req, id): + context = req.environ['nova.context'] + context.can(hosts_policies.POLICY_NAME % 'start', + target={}) return self._host_power_action(req, host_name=id, action="startup") @wsgi.Controller.api_version("2.1", "2.42") @wsgi.expected_errors((400, 404, 501)) def shutdown(self, req, id): + context = req.environ['nova.context'] + context.can(hosts_policies.POLICY_NAME % 'shutdown', + target={}) return self._host_power_action(req, host_name=id, action="shutdown") @wsgi.Controller.api_version("2.1", "2.42") @wsgi.expected_errors((400, 404, 501)) def reboot(self, req, id): + context = req.environ['nova.context'] + context.can(hosts_policies.POLICY_NAME % 'reboot', + target={}) return self._host_power_action(req, host_name=id, action="reboot") @staticmethod @@ -257,7 +267,8 @@ class HostController(wsgi.Controller): 'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30} """ context = req.environ['nova.context'] - context.can(hosts_policies.BASE_POLICY_NAME) + context.can(hosts_policies.POLICY_NAME % 'show', + target={}) host_name = id try: mapping = objects.HostMapping.get_by_host(context, host_name) diff --git a/nova/policies/hosts.py b/nova/policies/hosts.py index 191d0c088224..97e9f8e6a8d5 100644 --- a/nova/policies/hosts.py +++ b/nova/policies/hosts.py @@ -20,41 +20,116 @@ from nova.policies import base BASE_POLICY_NAME = 'os_compute_api:os-hosts' +POLICY_NAME = 'os_compute_api:os-hosts:%s' + +DEPRECATED_POLICY = policy.DeprecatedRule( + BASE_POLICY_NAME, + base.RULE_ADMIN_API, +) + +DEPRECATED_REASON = """ +Nova API policies are introducing new default roles with scope_type +capabilities. Old policies are deprecated and silently going to be ignored +in nova 23.0.0 release. +""" hosts_policies = [ policy.DocumentedRuleDefault( - name=BASE_POLICY_NAME, - check_str=base.RULE_ADMIN_API, - description="""List, show and manage physical hosts. + name=POLICY_NAME % 'list', + check_str=base.SYSTEM_READER, + description="""List physical hosts. -These APIs are all deprecated in favor of os-hypervisors and os-services.""", +This API is deprecated in favor of os-hypervisors and os-services.""", operations=[ { 'method': 'GET', 'path': '/os-hosts' }, + ], + scope_types=['system'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0'), + policy.DocumentedRuleDefault( + name=POLICY_NAME % 'show', + check_str=base.SYSTEM_READER, + description="""Show physical host. + +This API is deprecated in favor of os-hypervisors and os-services.""", + operations=[ { 'method': 'GET', 'path': '/os-hosts/{host_name}' - }, + } + ], + scope_types=['system'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0'), + policy.DocumentedRuleDefault( + name=POLICY_NAME % 'update', + check_str=base.SYSTEM_ADMIN, + description="""Update physical host. + +This API is deprecated in favor of os-hypervisors and os-services.""", + operations=[ { 'method': 'PUT', 'path': '/os-hosts/{host_name}' }, + ], + scope_types=['system'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0'), + policy.DocumentedRuleDefault( + name=POLICY_NAME % 'reboot', + check_str=base.SYSTEM_ADMIN, + description="""Reboot physical host. + +This API is deprecated in favor of os-hypervisors and os-services.""", + operations=[ { 'method': 'GET', 'path': '/os-hosts/{host_name}/reboot' }, + ], + scope_types=['system'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0'), + policy.DocumentedRuleDefault( + name=POLICY_NAME % 'shutdown', + check_str=base.SYSTEM_ADMIN, + description="""Shutdown physical host. + +This API is deprecated in favor of os-hypervisors and os-services.""", + operations=[ { 'method': 'GET', 'path': '/os-hosts/{host_name}/shutdown' }, + ], + scope_types=['system'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0'), + policy.DocumentedRuleDefault( + name=POLICY_NAME % 'start', + check_str=base.SYSTEM_ADMIN, + description="""Start physical host. + +This API is deprecated in favor of os-hypervisors and os-services.""", + operations=[ { 'method': 'GET', 'path': '/os-hosts/{host_name}/startup' } ], - scope_types=['system']), + scope_types=['system'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='22.0.0'), ] diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index 1af4fe2cf05e..7510c5e1bed7 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -105,6 +105,12 @@ policy_data = """ "os_compute_api:os-keypairs:create": "", "os_compute_api:os-keypairs:show": "", "os_compute_api:os-keypairs:delete": "", + "os_compute_api:os-hosts:list": "", + "os_compute_api:os-hosts:show": "", + "os_compute_api:os-hosts:update": "", + "os_compute_api:os-hosts:reboot": "", + "os_compute_api:os-hosts:shutdown": "", + "os_compute_api:os-hosts:start": "", "os_compute_api:os-hypervisors:list": "", "os_compute_api:os-hypervisors:list-detail": "", "os_compute_api:os-hypervisors:statistics": "", diff --git a/nova/tests/unit/policies/test_hosts.py b/nova/tests/unit/policies/test_hosts.py index f2e4c1c4eda6..cdce7d2b1c76 100644 --- a/nova/tests/unit/policies/test_hosts.py +++ b/nova/tests/unit/policies/test_hosts.py @@ -13,6 +13,7 @@ import mock from nova.api.openstack.compute import hosts +from nova.policies import base as base_policy from nova.policies import hosts as policies from nova.tests.unit.api.openstack import fakes from nova.tests.unit.policies import base @@ -32,22 +33,34 @@ class HostsPolicyTest(base.BasePolicyTest): self.req = fakes.HTTPRequest.blank('') # Check that admin is able to perform operations on hosts. - self.admin_authorized_contexts = [ + self.system_admin_authorized_contexts = [ self.system_admin_context, self.legacy_admin_context, self.project_admin_context] # Check that non-admin is not able to perform operations # on hosts. - self.admin_unauthorized_contexts = [ + self.system_admin_unauthorized_contexts = [ self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_member_context, self.project_foo_context, self.project_member_context, - self.project_reader_context, self.other_project_reader_context] + self.project_reader_context, self.other_project_reader_context + ] + self.system_reader_authorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context, self.legacy_admin_context, + self.project_admin_context + ] + self.system_reader_unauthorized_contexts = [ + self.project_foo_context, self.system_foo_context, + self.project_member_context, self.project_reader_context, + self.other_project_member_context, + self.other_project_reader_context + ] @mock.patch('nova.compute.api.HostAPI.service_get_all') def test_list_hosts_policy(self, mock_get): - rule_name = policies.BASE_POLICY_NAME - self.common_policy_check(self.admin_authorized_contexts, - self.admin_unauthorized_contexts, + rule_name = policies.POLICY_NAME % 'list' + self.common_policy_check(self.system_reader_authorized_contexts, + self.system_reader_unauthorized_contexts, rule_name, self.controller.index, self.req) @@ -57,40 +70,40 @@ class HostsPolicyTest(base.BasePolicyTest): 'get_first_node_by_host_for_old_compat') @mock.patch('nova.compute.api.HostAPI.instance_get_all_by_host') def test_show_host_policy(self, mock_get, mock_node, mock_map, mock_set): - rule_name = policies.BASE_POLICY_NAME - self.common_policy_check(self.admin_authorized_contexts, - self.admin_unauthorized_contexts, + rule_name = policies.POLICY_NAME % 'show' + self.common_policy_check(self.system_reader_authorized_contexts, + self.system_reader_unauthorized_contexts, rule_name, self.controller.show, self.req, 11111) def test_update_host_policy(self): - rule_name = policies.BASE_POLICY_NAME - self.common_policy_check(self.admin_authorized_contexts, - self.admin_unauthorized_contexts, + rule_name = policies.POLICY_NAME % 'update' + self.common_policy_check(self.system_admin_authorized_contexts, + self.system_admin_unauthorized_contexts, rule_name, self.controller.update, self.req, 11111, body={}) @mock.patch('nova.compute.api.HostAPI.host_power_action') def test_reboot_host_policy(self, mock_action): - rule_name = policies.BASE_POLICY_NAME - self.common_policy_check(self.admin_authorized_contexts, - self.admin_unauthorized_contexts, + rule_name = policies.POLICY_NAME % 'reboot' + self.common_policy_check(self.system_admin_authorized_contexts, + self.system_admin_unauthorized_contexts, rule_name, self.controller.reboot, self.req, 11111) @mock.patch('nova.compute.api.HostAPI.host_power_action') def test_shutdown_host_policy(self, mock_action): - rule_name = policies.BASE_POLICY_NAME - self.common_policy_check(self.admin_authorized_contexts, - self.admin_unauthorized_contexts, + rule_name = policies.POLICY_NAME % 'shutdown' + self.common_policy_check(self.system_admin_authorized_contexts, + self.system_admin_unauthorized_contexts, rule_name, self.controller.shutdown, self.req, 11111) @mock.patch('nova.compute.api.HostAPI.host_power_action') def test_startup_host_policy(self, mock_action): - rule_name = policies.BASE_POLICY_NAME - self.common_policy_check(self.admin_authorized_contexts, - self.admin_unauthorized_contexts, + rule_name = policies.POLICY_NAME % 'start' + self.common_policy_check(self.system_admin_authorized_contexts, + self.system_admin_unauthorized_contexts, rule_name, self.controller.startup, self.req, 11111) @@ -110,13 +123,71 @@ class HostsScopeTypePolicyTest(HostsPolicyTest): self.flags(enforce_scope=True, group="oslo_policy") # Check that system admin is able to perform operations on hosts. - self.admin_authorized_contexts = [ + self.system_admin_authorized_contexts = [ self.system_admin_context] # Check that system non-admin is not able to perform operations # on hosts. - self.admin_unauthorized_contexts = [ + self.system_admin_unauthorized_contexts = [ self.legacy_admin_context, self.project_admin_context, self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_member_context, self.project_foo_context, self.project_member_context, - self.project_reader_context, self.other_project_reader_context] + self.project_reader_context, self.other_project_reader_context + ] + self.system_reader_authorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context + ] + self.system_reader_unauthorized_contexts = [ + self.legacy_admin_context, self.project_foo_context, + self.system_foo_context, self.project_admin_context, + self.project_member_context, self.project_reader_context, + self.other_project_member_context, + self.other_project_reader_context + ] + + +class HostsNoLegacyPolicyTest(HostsScopeTypePolicyTest): + """Test Hosts APIs policies with system scope enabled, + and no more deprecated rules that allow the legacy admin API to + access system_admin_or_owner APIs. + """ + without_deprecated_rules = True + rules_without_deprecation = { + policies.POLICY_NAME % 'list': + base_policy.SYSTEM_READER, + policies.POLICY_NAME % 'show': + base_policy.SYSTEM_READER, + policies.POLICY_NAME % 'update': + base_policy.SYSTEM_ADMIN, + policies.POLICY_NAME % 'reboot': + base_policy.SYSTEM_ADMIN, + policies.POLICY_NAME % 'shutdown': + base_policy.SYSTEM_ADMIN, + policies.POLICY_NAME % 'startup': + base_policy.SYSTEM_ADMIN} + + def setUp(self): + super(HostsNoLegacyPolicyTest, self).setUp() + + self.system_reader_authorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context + ] + self.system_reader_unauthorized_contexts = [ + self.legacy_admin_context, self.project_foo_context, + self.system_foo_context, self.project_admin_context, + self.project_member_context, self.project_reader_context, + self.other_project_member_context, + self.other_project_reader_context + ] + self.system_admin_authorized_contexts = [ + self.system_admin_context + ] + self.system_admin_unauthorized_contexts = [ + self.system_member_context, self.system_reader_context, + self.project_admin_context, self.project_member_context, + self.legacy_admin_context, self.other_project_member_context, + self.project_reader_context, self.project_foo_context, + self.system_foo_context, self.other_project_reader_context + ] diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py index 29a67737cdb2..dd57f671349b 100644 --- a/nova/tests/unit/test_policy.py +++ b/nova/tests/unit/test_policy.py @@ -348,7 +348,10 @@ class RealRolePolicyTestCase(test.NoDBTestCase): "os_compute_api:os-flavor-manage:create", "os_compute_api:os-flavor-manage:update", "os_compute_api:os-flavor-manage:delete", -"os_compute_api:os-hosts", +"os_compute_api:os-hosts:update", +"os_compute_api:os-hosts:reboot", +"os_compute_api:os-hosts:shutdown", +"os_compute_api:os-hosts:start", "os_compute_api:os-instance-actions:events", "os_compute_api:os-lock-server:unlock:unlock_override", "os_compute_api:os-migrate-server:migrate", @@ -467,6 +470,8 @@ class RealRolePolicyTestCase(test.NoDBTestCase): "os_compute_api:os-instance-usage-audit-log:list", "os_compute_api:os-instance-usage-audit-log:show", "os_compute_api:os-agents:list", +"os_compute_api:os-hosts:list", +"os_compute_api:os-hosts:show", "os_compute_api:os-hypervisors:list", "os_compute_api:os-hypervisors:list-detail", "os_compute_api:os-hypervisors:show",