Merge "Add new default roles in os-services API policies"

This commit is contained in:
Zuul 2019-12-20 21:48:13 +00:00 committed by Gerrit Code Review
commit 3e7e2530f1
7 changed files with 191 additions and 40 deletions

View File

@ -64,7 +64,6 @@ class ServiceController(wsgi.Controller):
api_services = ('nova-osapi_compute', 'nova-metadata')
context = req.environ['nova.context']
context.can(services_policies.BASE_POLICY_NAME)
cell_down_support = api_version_request.is_supported(
req, min_version=PARTIAL_CONSTRUCT_FOR_CELL_DOWN_MIN_VERSION)
@ -218,7 +217,6 @@ class ServiceController(wsgi.Controller):
def _perform_action(self, req, id, body, actions):
"""Calculate action dictionary dependent on provided fields"""
context = req.environ['nova.context']
context.can(services_policies.BASE_POLICY_NAME)
try:
action = actions[id]
@ -233,7 +231,7 @@ class ServiceController(wsgi.Controller):
def delete(self, req, id):
"""Deletes the specified service."""
context = req.environ['nova.context']
context.can(services_policies.BASE_POLICY_NAME)
context.can(services_policies.BASE_POLICY_NAME % 'delete')
if api_version_request.is_supported(
req, min_version=UUID_FOR_ID_MIN_VERSION):
@ -348,6 +346,8 @@ class ServiceController(wsgi.Controller):
"""Return a list of all running services. Filter by host & service
name
"""
context = req.environ['nova.context']
context.can(services_policies.BASE_POLICY_NAME % 'list')
if api_version_request.is_supported(req, min_version='2.11'):
_services = self._get_services_list(req, ['forced_down'])
else:
@ -367,6 +367,8 @@ class ServiceController(wsgi.Controller):
service ID passed on the path, just the action, for example
PUT /os-services/disable.
"""
context = req.environ['nova.context']
context.can(services_policies.BASE_POLICY_NAME % 'update')
if api_version_request.is_supported(req, min_version='2.11'):
actions = self.actions.copy()
actions["force-down"] = self._forced_down
@ -393,7 +395,7 @@ class ServiceController(wsgi.Controller):
# Validate the request context against the policy.
context = req.environ['nova.context']
context.can(services_policies.BASE_POLICY_NAME)
context.can(services_policies.BASE_POLICY_NAME % 'update')
# Get the service by uuid.
try:

View File

@ -18,49 +18,67 @@ from oslo_policy import policy
from nova.policies import base
BASE_POLICY_NAME = 'os_compute_api:os-services'
BASE_POLICY_NAME = 'os_compute_api:os-services:%s'
DEPRECATED_SERVICE_POLICY = policy.DeprecatedRule(
'os_compute_api:os-services',
base.RULE_ADMIN_API,
)
DEPRECATED_REASON = """
Since Ussuri release, nova API policies are introducing new default roles
with scope_type capabilities. These new changes improve the security level
and manageability. New policies are more rich in term of handling access
at system and project level token with read, write roles.
Start using the new policies and enable the scope checks via config option
``nova.conf [oslo_policy] enforce_scope=True`` which is False by default.
Old policies are marked as deprecated and silently going to be ignored
in nova 23.0.0 (OpenStack W) release
"""
services_policies = [
policy.DocumentedRuleDefault(
BASE_POLICY_NAME,
base.RULE_ADMIN_API,
"List all running Compute services in a region, enables or disable "
"scheduling for a Compute service, logs disabled Compute service "
"information, set or unset forced_down flag for the compute service "
"and delete a Compute service",
[
name=BASE_POLICY_NAME % 'list',
check_str=base.SYSTEM_READER,
description="List all running Compute services in a region.",
operations=[
{
'method': 'GET',
'path': '/os-services'
},
{
'method': 'PUT',
'path': '/os-services/enable'
},
{
'method': 'PUT',
'path': '/os-services/disable'
},
{
'method': 'PUT',
'path': '/os-services/disable-log-reason'
},
{
'method': 'PUT',
'path': '/os-services/force-down'
},
}
],
scope_types=['system'],
deprecated_rule=DEPRECATED_SERVICE_POLICY,
deprecated_reason=DEPRECATED_REASON,
deprecated_since='20.0.0'),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'update',
check_str=base.SYSTEM_ADMIN,
description="Update a Compute service.",
operations=[
{
# Added in microversion 2.53.
'method': 'PUT',
'path': '/os-services/{service_id}'
},
],
scope_types=['system'],
deprecated_rule=DEPRECATED_SERVICE_POLICY,
deprecated_reason=DEPRECATED_REASON,
deprecated_since='20.0.0'),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'delete',
check_str=base.SYSTEM_ADMIN,
description="Delete a Compute service.",
operations=[
{
'method': 'DELETE',
'path': '/os-services/{service_id}'
}
],
scope_types=['system']),
scope_types=['system'],
deprecated_rule=DEPRECATED_SERVICE_POLICY,
deprecated_reason=DEPRECATED_REASON,
deprecated_since='20.0.0'),
]

View File

@ -1201,7 +1201,7 @@ class ServicesTestV253(test.TestCase):
def test_update_policy_failed(self):
"""Tests that policy is checked with microversion 2.53."""
rule_name = "os_compute_api:os-services"
rule_name = "os_compute_api:os-services:update"
self.policy.set_rules({rule_name: "project_id:non_fake"})
exc = self.assertRaises(
exception.PolicyNotAuthorized,

View File

@ -77,7 +77,9 @@ policy_data = """
"os_compute_api:os-server-groups:index": "",
"os_compute_api:os-server-groups:create": "",
"os_compute_api:os-server-groups:delete": "",
"os_compute_api:os-services": "",
"os_compute_api:os-services:list": "",
"os_compute_api:os-services:update": "",
"os_compute_api:os-services:delete": "",
"os_compute_api:os-shelve:shelve": "",
"os_compute_api:os-shelve:shelve_offload": "",
"os_compute_api:os-simple-tenant-usage:show": "",

View File

@ -14,8 +14,11 @@
import mock
from nova.api.openstack.compute import services as services_v21
from nova import exception
from nova.policies import base as base_policy
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit.policies import base
from nova.tests.unit import policy_fixture
class ServicesPolicyTest(base.BasePolicyTest):
@ -43,8 +46,26 @@ class ServicesPolicyTest(base.BasePolicyTest):
self.project_foo_context, self.project_reader_context
]
# Check that system scoped admin, member and reader are able to
# read the service data.
# NOTE(gmann): Until old default rule which is admin_api is
# deprecated and not removed, project admin and legacy admin
# will be able to read the service data. This make sure that existing
# tokens will keep working even we have changed this policy defaults
# to reader role.
self.reader_authorized_contexts = [
self.system_admin_context, self.system_member_context,
self.system_reader_context, self.legacy_admin_context,
self.project_admin_context]
# Check that non-system-reader are not able to read the service
# data
self.reader_unauthorized_contexts = [
self.system_foo_context, self.other_project_member_context,
self.project_foo_context, self.project_member_context,
self.project_reader_context]
def test_delete_service_policy(self):
rule_name = "os_compute_api:os-services"
rule_name = "os_compute_api:os-services:delete"
with mock.patch('nova.compute.api.HostAPI.service_get_by_id'):
self.common_policy_check(self.admin_authorized_contexts,
self.admin_unauthorized_contexts,
@ -52,15 +73,15 @@ class ServicesPolicyTest(base.BasePolicyTest):
self.req, 1)
def test_index_service_policy(self):
rule_name = "os_compute_api:os-services"
rule_name = "os_compute_api:os-services:list"
with mock.patch('nova.compute.api.HostAPI.service_get_all'):
self.common_policy_check(self.admin_authorized_contexts,
self.admin_unauthorized_contexts,
self.common_policy_check(self.reader_authorized_contexts,
self.reader_unauthorized_contexts,
rule_name, self.controller.index,
self.req)
def test_old_update_service_policy(self):
rule_name = "os_compute_api:os-services"
rule_name = "os_compute_api:os-services:update"
body = {'host': 'host1', 'binary': 'nova-compute'}
update = 'nova.compute.api.HostAPI.service_update_by_host_and_binary'
with mock.patch(update):
@ -70,7 +91,7 @@ class ServicesPolicyTest(base.BasePolicyTest):
self.req, 'enable', body=body)
def test_update_service_policy(self):
rule_name = "os_compute_api:os-services"
rule_name = "os_compute_api:os-services:update"
req = fakes.HTTPRequest.blank(
'', version=services_v21.UUID_FOR_ID_MIN_VERSION)
service = self.start_service(
@ -109,3 +130,67 @@ class ServicesScopeTypePolicyTest(ServicesPolicyTest):
self.other_project_member_context,
self.project_foo_context, self.project_reader_context
]
# Check that system admin, member and reader are able to read the
# service data
self.reader_authorized_contexts = [
self.system_admin_context, self.system_member_context,
self.system_reader_context]
# Check that non-system or non-reader are not able to read the service
# data
self.reader_unauthorized_contexts = [
self.system_foo_context, self.legacy_admin_context,
self.project_admin_context, self.project_member_context,
self.other_project_member_context,
self.project_foo_context, self.project_reader_context
]
class ServicesDeprecatedPolicyTest(base.BasePolicyTest):
"""Test os-services APIs Deprecated policies.
This class checks if deprecated policy rules are
overridden by user on policy.json file then they
still work because oslo.policy add deprecated rules
in logical OR condition and enforce them for policy
checks if overridden.
"""
def setUp(self):
super(ServicesDeprecatedPolicyTest, self).setUp()
self.controller = services_v21.ServiceController()
self.member_req = fakes.HTTPRequest.blank('')
self.member_req.environ['nova.context'] = self.project_member_context
self.reader_req = fakes.HTTPRequest.blank('')
self.reader_req.environ['nova.context'] = self.project_reader_context
self.deprecated_policy = "os_compute_api:os-services"
# Overridde rule with different checks than defaults so that we can
# verify the rule overridden case.
override_rules = {self.deprecated_policy: base_policy.PROJECT_MEMBER}
# NOTE(gmann): Only override the deprecated rule in policy file so
# that
# we can verify if overridden checks are considered by oslo.policy.
# Oslo.policy will consider the overridden rules if:
# 1. overridden deprecated rule's checks are different than defaults
# 2. new rules are not present in policy file
self.policy = self.useFixture(policy_fixture.OverridePolicyFixture(
rules_in_file=override_rules))
def test_deprecated_policy_overridden_rule_is_checked(self):
# Test to verify if deprecatd overridden policy is working.
# check for success as member role. Deprecated rule
# has been overridden with member checks in policy.json
# If member role pass it means overridden rule is enforced by
# olso.policy because new default is system admin and the old
# default is admin.
with mock.patch('nova.compute.api.HostAPI.service_get_by_id'):
self.controller.index(self.member_req)
# check for failure with reader context.
exc = self.assertRaises(exception.PolicyNotAuthorized,
self.controller.index, self.reader_req)
self.assertEqual(
"Policy doesn't allow os_compute_api:os-services:list to be"
" performed.",
exc.format_message())

View File

@ -130,3 +130,42 @@ class RoleBasedPolicyFixture(RealPolicyFixture):
self.policy_file = os.path.join(self.policy_dir.path, 'policy.json')
with open(self.policy_file, 'w') as f:
jsonutils.dump(policy, f)
class OverridePolicyFixture(RealPolicyFixture):
"""Load the set of requested rules into policy file
This overrides the policy with the requested rules only into
policy file. This fixture is to verify the use case where operator
has overridden the policy rules in policy file means default policy
not used. One example is when policy rules are deprecated. In that case
tests can use this fixture and verify if deprecated rules are overridden
then does nova code enforce the overridden rules not only defaults.
As per oslo.policy deprecattion feature, if deprecated rule is overridden
in policy file then, overridden check is used to verify the policy.
Example of usage:
self.deprecated_policy = "os_compute_api:os-services"
# set check_str as different than defaults to verify the
# rule overridden case.
override_rules = {self.deprecated_policy: 'is_admin:True'}
# NOTE(gmann): Only override the deprecated rule in policy file so that
# we can verify if overridden checks are considered by oslo.policy.
# Oslo.policy will consider the overridden rules if:
# 1. overridden checks are different than defaults
# 2. new rules for deprecated rules are not present in policy file
self.policy = self.useFixture(policy_fixture.OverridePolicyFixture(
rules_in_file=override_rules))
"""
def __init__(self, rules_in_file, *args, **kwargs):
self.rules_in_file = rules_in_file
super(OverridePolicyFixture, self).__init__(*args, **kwargs)
def _prepare_policy(self):
self.policy_dir = self.useFixture(fixtures.TempDir())
self.policy_file = os.path.join(self.policy_dir.path,
'policy.json')
with open(self.policy_file, 'w') as f:
jsonutils.dump(self.rules_in_file, f)
CONF.set_override('policy_dirs', [], group='oslo_policy')

View File

@ -349,7 +349,8 @@ 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-services",
"os_compute_api:os-services:update",
"os_compute_api:os-services:delete",
"os_compute_api:os-shelve:shelve_offload",
"os_compute_api:os-simple-tenant-usage:list",
"os_compute_api:os-availability-zone:detail",
@ -453,6 +454,10 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
self.allow_all_rules = (
"os_compute_api:os-quota-sets:defaults",
)
self.system_reader_rules = (
"os_compute_api:os-services:list",
)
def test_all_rules_in_sample_file(self):
@ -492,5 +497,5 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
'system_admin_or_owner', 'system_or_project_reader')
result = set(rules.keys()) - set(self.admin_only_rules +
self.admin_or_owner_rules +
self.allow_all_rules + special_rules)
self.allow_all_rules + self.system_reader_rules + special_rules)
self.assertEqual(set([]), result)