diff --git a/nova/api/openstack/compute/server_password.py b/nova/api/openstack/compute/server_password.py index 3493366744b7..c7caf04b602d 100644 --- a/nova/api/openstack/compute/server_password.py +++ b/nova/api/openstack/compute/server_password.py @@ -32,7 +32,7 @@ class ServerPasswordController(wsgi.Controller): def index(self, req, server_id): context = req.environ['nova.context'] instance = common.get_instance(self.compute_api, context, server_id) - context.can(sp_policies.BASE_POLICY_NAME, + context.can(sp_policies.BASE_POLICY_NAME % 'show', target={'project_id': instance.project_id}) passw = password.extract_password(instance) @@ -49,7 +49,7 @@ class ServerPasswordController(wsgi.Controller): context = req.environ['nova.context'] instance = common.get_instance(self.compute_api, context, server_id) - context.can(sp_policies.BASE_POLICY_NAME, + context.can(sp_policies.BASE_POLICY_NAME % 'clear', target={'project_id': instance.project_id}) meta = password.convert_password(context, None) instance.system_metadata.update(meta) diff --git a/nova/policies/server_password.py b/nova/policies/server_password.py index 1a43f4af222a..6015789cba78 100644 --- a/nova/policies/server_password.py +++ b/nova/policies/server_password.py @@ -18,26 +18,51 @@ from oslo_policy import policy from nova.policies import base -BASE_POLICY_NAME = 'os_compute_api:os-server-password' +BASE_POLICY_NAME = 'os_compute_api:os-server-password:%s' + +DEPRECATED_POLICY = policy.DeprecatedRule( + 'os_compute_api:os-server-password', + base.RULE_ADMIN_OR_OWNER, +) + +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. +""" server_password_policies = [ policy.DocumentedRuleDefault( - name=BASE_POLICY_NAME, - check_str=base.RULE_ADMIN_OR_OWNER, - description="Show and clear the encrypted administrative " + name=BASE_POLICY_NAME % 'show', + check_str=base.PROJECT_READER_OR_SYSTEM_READER, + description="Show the encrypted administrative " "password of a server", operations=[ { 'method': 'GET', 'path': '/servers/{server_id}/os-server-password' }, + ], + scope_types=['system', 'project'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='21.0.0'), + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % 'clear', + check_str=base.PROJECT_MEMBER_OR_SYSTEM_ADMIN, + description="Clear the encrypted administrative " + "password of a server", + operations=[ { 'method': 'DELETE', 'path': '/servers/{server_id}/os-server-password' } ], - scope_types=['system', 'project']), + scope_types=['system', 'project'], + deprecated_rule=DEPRECATED_POLICY, + deprecated_reason=DEPRECATED_REASON, + deprecated_since='21.0.0'), ] diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index 4b4b32af2e17..5ccaa4ddf51b 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -93,7 +93,8 @@ policy_data = """ "os_compute_api:os-security-groups:add": "", "os_compute_api:os-security-groups:remove": "", "os_compute_api:os-server-diagnostics": "", - "os_compute_api:os-server-password": "", + "os_compute_api:os-server-password:show": "", + "os_compute_api:os-server-password:clear": "", "os_compute_api:os-server-tags:index": "", "os_compute_api:os-server-tags:show": "", "os_compute_api:os-server-tags:update": "", diff --git a/nova/tests/unit/policies/test_server_password.py b/nova/tests/unit/policies/test_server_password.py index f575eca1bd43..1a28cf9f2094 100644 --- a/nova/tests/unit/policies/test_server_password.py +++ b/nova/tests/unit/policies/test_server_password.py @@ -15,6 +15,7 @@ import mock from oslo_utils.fixture import uuidsentinel as uuids from nova.api.openstack.compute import server_password +from nova.policies import base as base_policy from nova.policies import server_password as policies from nova.tests.unit.api.openstack import fakes from nova.tests.unit import fake_instance @@ -41,32 +42,45 @@ class ServerPasswordPolicyTest(base.BasePolicyTest): system_metadata={}, expected_attrs=['system_metadata']) self.mock_get.return_value = self.instance - # Check that admin or and server owner is able to get - # and delete the server password. + # Check that admin or and server owner is able to + # delete the server password. 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 + # Check that non-admin/owner is not able to delete # the server password. self.admin_or_owner_unauthorized_contexts = [ self.system_member_context, self.system_reader_context, - self.system_foo_context, - self.other_project_member_context + self.system_foo_context, self.other_project_member_context, + self.other_project_reader_context + ] + # Check that admin or and server owner is able to get + # the server password. + self.reader_authorized_contexts = [ + self.legacy_admin_context, self.system_admin_context, + self.system_member_context, self.system_reader_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 + # the server password. + self.reader_unauthorized_contexts = [ + self.system_foo_context, self.other_project_member_context, + self.other_project_reader_context ] @mock.patch('nova.api.metadata.password.extract_password') def test_index_server_password_policy(self, mock_pass): - rule_name = policies.BASE_POLICY_NAME - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, + rule_name = policies.BASE_POLICY_NAME % 'show' + self.common_policy_check(self.reader_authorized_contexts, + self.reader_unauthorized_contexts, rule_name, self.controller.index, self.req, self.instance.uuid) @mock.patch('nova.api.metadata.password.convert_password') def test_clear_server_password_policy(self, mock_pass): - rule_name = policies.BASE_POLICY_NAME + rule_name = policies.BASE_POLICY_NAME % 'clear' self.common_policy_check(self.admin_or_owner_authorized_contexts, self.admin_or_owner_unauthorized_contexts, rule_name, @@ -87,3 +101,50 @@ class ServerPasswordScopeTypePolicyTest(ServerPasswordPolicyTest): def setUp(self): super(ServerPasswordScopeTypePolicyTest, self).setUp() self.flags(enforce_scope=True, group="oslo_policy") + + +class ServerPasswordNoLegacyPolicyTest(ServerPasswordScopeTypePolicyTest): + """Test Server Password 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.BASE_POLICY_NAME % 'show': + base_policy.PROJECT_READER_OR_SYSTEM_READER, + policies.BASE_POLICY_NAME % 'clear': + base_policy.PROJECT_MEMBER_OR_SYSTEM_ADMIN} + + def setUp(self): + super(ServerPasswordNoLegacyPolicyTest, self).setUp() + + # Check that system or projct admin or owner is able to clear + # server password. + self.admin_or_owner_authorized_contexts = [ + self.system_admin_context, + self.project_admin_context, self.project_member_context] + # Check that non-system and non-admin/owner is not able to clear + # server password. + self.admin_or_owner_unauthorized_contexts = [ + self.legacy_admin_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.other_project_reader_context] + + # Check that system reader or projct owner is able to get + # server password. + self.reader_authorized_contexts = [ + self.system_admin_context, + self.project_admin_context, self.system_member_context, + self.system_reader_context, self.project_reader_context, + self.project_member_context, + ] + + # Check that non-system reader nd non-admin/owner is not able to get + # server password. + self.reader_unauthorized_contexts = [ + self.legacy_admin_context, self.project_foo_context, + self.system_foo_context, self.other_project_member_context, + self.other_project_reader_context + ] diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py index 2313872e4560..ba47285224bd 100644 --- a/nova/tests/unit/test_policy.py +++ b/nova/tests/unit/test_policy.py @@ -429,7 +429,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase): "os_compute_api:os-security-groups", "os_compute_api:os-security-groups:add", "os_compute_api:os-security-groups:remove", -"os_compute_api:os-server-password", +"os_compute_api:os-server-password:clear", "os_compute_api:os-server-tags:delete", "os_compute_api:os-server-tags:delete_all", "os_compute_api:os-server-tags:update", @@ -479,6 +479,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase): "os_compute_api:os-attach-interfaces:show", "os_compute_api:os-instance-actions:list", "os_compute_api:os-instance-actions:show", +"os_compute_api:os-server-password:show", "os_compute_api:os-server-tags:index", "os_compute_api:os-server-tags:show", )