Merge "[policy in code] part3 (resource types)"

This commit is contained in:
Zuul 2017-12-06 06:43:05 +00:00 committed by Gerrit Code Review
commit 3fb7b4eb2e
8 changed files with 148 additions and 63 deletions

View File

@ -47,21 +47,5 @@
"software_deployments:delete": "rule:deny_stack_user",
"software_deployments:metadata": "",
"service:index": "rule:context_is_admin",
"resource_types:OS::Nova::Flavor": "rule:project_admin",
"resource_types:OS::Cinder::EncryptedVolumeType": "rule:project_admin",
"resource_types:OS::Cinder::VolumeType": "rule:project_admin",
"resource_types:OS::Cinder::Quota": "rule:project_admin",
"resource_types:OS::Neutron::Quota": "rule:project_admin",
"resource_types:OS::Nova::Quota": "rule:project_admin",
"resource_types:OS::Manila::ShareType": "rule:project_admin",
"resource_types:OS::Neutron::ProviderNet": "rule:project_admin",
"resource_types:OS::Neutron::QoSPolicy": "rule:project_admin",
"resource_types:OS::Neutron::QoSBandwidthLimitRule": "rule:project_admin",
"resource_types:OS::Neutron::Segment": "rule:project_admin",
"resource_types:OS::Nova::HostAggregate": "rule:project_admin",
"resource_types:OS::Cinder::QoSSpecs": "rule:project_admin",
"resource_types:OS::Cinder::QoSAssociation": "rule:project_admin",
"resource_types:OS::Keystone::*": "rule:project_admin"
"service:index": "rule:context_is_admin"
}

View File

@ -125,12 +125,15 @@ class ResourceEnforcer(Enforcer):
super(ResourceEnforcer, self).__init__(
default_rule=default_rule, **kwargs)
def _enforce(self, context, res_type, scope=None, target=None):
def _enforce(self, context, res_type, scope=None, target=None,
is_registered_policy=False):
try:
result = super(ResourceEnforcer, self).enforce(
context, res_type,
scope=scope or 'resource_types',
target=target)
target=target, is_registered_policy=is_registered_policy)
except policy.PolicyNotRegistered:
result = True
except self.exc as ex:
LOG.info(six.text_type(ex))
raise
@ -139,19 +142,27 @@ class ResourceEnforcer(Enforcer):
raise self.exc(action=res_type)
return result
def enforce(self, context, res_type, scope=None, target=None):
def enforce(self, context, res_type, scope=None, target=None,
is_registered_policy=False):
# NOTE(pas-ha): try/except just to log the exception
result = self._enforce(context, res_type, scope, target)
result = self._enforce(context, res_type, scope, target,
is_registered_policy=is_registered_policy)
if result:
# check for wildcard resource types
subparts = res_type.split("::")[:-1]
subparts.append('*')
res_type_wc = "::".join(subparts)
return self._enforce(context, res_type_wc, scope, target)
try:
return self._enforce(context, res_type_wc, scope, target,
is_registered_policy=is_registered_policy)
except self.exc:
raise self.exc(action=res_type)
return result
def enforce_stack(self, stack, scope=None, target=None):
def enforce_stack(self, stack, scope=None, target=None,
is_registered_policy=False):
for res in stack.resources.values():
self.enforce(stack.context, res.type(), scope=scope, target=target)
self.enforce(stack.context, res.type(), scope=scope, target=target,
is_registered_policy=is_registered_policy)

View File

@ -618,7 +618,7 @@ class ResourceRegistry(object):
if cnxt is None:
return True
try:
enforcer.enforce(cnxt, name)
enforcer.enforce(cnxt, name, is_registered_policy=True)
except enforcer.exc:
return False
else:

View File

@ -730,7 +730,7 @@ class EngineService(service.ServiceBase):
parent_resource=parent_resource_name,
**common_params)
self.resource_enforcer.enforce_stack(stack)
self.resource_enforcer.enforce_stack(stack, is_registered_policy=True)
self._validate_deferred_auth_context(cnxt, stack)
is_root = stack.nested_depth == 0
stack.validate()
@ -964,7 +964,8 @@ class EngineService(service.ServiceBase):
if invalid_params:
raise exception.ImmutableParameterModified(*invalid_params)
self.resource_enforcer.enforce_stack(updated_stack)
self.resource_enforcer.enforce_stack(updated_stack,
is_registered_policy=True)
updated_stack.parameters.set_stack_id(current_stack.identifier())
self._validate_deferred_auth_context(cnxt, updated_stack)
@ -999,7 +1000,8 @@ class EngineService(service.ServiceBase):
cnxt, stack=db_stack, use_stored_context=True)
else:
current_stack = parser.Stack.load(cnxt, stack=db_stack)
self.resource_enforcer.enforce_stack(current_stack)
self.resource_enforcer.enforce_stack(current_stack,
is_registered_policy=True)
if current_stack.action == current_stack.SUSPEND:
msg = _('Updating a stack when it is suspended')
@ -1417,7 +1419,7 @@ class EngineService(service.ServiceBase):
LOG.info('Deleting stack %s', st.name)
stack = parser.Stack.load(cnxt, stack=st)
self.resource_enforcer.enforce_stack(stack)
self.resource_enforcer.enforce_stack(stack, is_registered_policy=True)
if stack.convergence and cfg.CONF.convergence_engine:
def convergence_delete():
@ -1465,7 +1467,8 @@ class EngineService(service.ServiceBase):
def reload():
st = self._get_stack(cnxt, stack_identity)
stack = parser.Stack.load(cnxt, stack=st)
self.resource_enforcer.enforce_stack(stack)
self.resource_enforcer.enforce_stack(stack,
is_registered_policy=True)
return stack
def wait_then_delete(stack):
@ -1642,7 +1645,8 @@ class EngineService(service.ServiceBase):
:param type_name: Name of the resource type to obtain the schema of.
:param with_description: Return result with description or not.
"""
self.resource_enforcer.enforce(cnxt, type_name)
self.resource_enforcer.enforce(cnxt, type_name,
is_registered_policy=True)
try:
resource_class = resources.global_env().get_class(type_name)
except exception.NotFound:
@ -1703,7 +1707,8 @@ class EngineService(service.ServiceBase):
:param type_name: Name of the resource type to generate a template for.
:param template_type: the template type to generate, cfn or hot.
"""
self.resource_enforcer.enforce(cnxt, type_name)
self.resource_enforcer.enforce(cnxt, type_name,
is_registered_policy=True)
try:
resource_class = resources.global_env().get_class(type_name)
except exception.NotFound:
@ -2047,7 +2052,7 @@ class EngineService(service.ServiceBase):
s = self._get_stack(cnxt, stack_identity)
stack = parser.Stack.load(cnxt, stack=s)
self.resource_enforcer.enforce_stack(stack)
self.resource_enforcer.enforce_stack(stack, is_registered_policy=True)
self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id,
_stack_suspend, stack)
@ -2061,7 +2066,7 @@ class EngineService(service.ServiceBase):
s = self._get_stack(cnxt, stack_identity)
stack = parser.Stack.load(cnxt, stack=s)
self.resource_enforcer.enforce_stack(stack)
self.resource_enforcer.enforce_stack(stack, is_registered_policy=True)
self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id,
_stack_resume, stack)
@ -2146,7 +2151,7 @@ class EngineService(service.ServiceBase):
s = self._get_stack(cnxt, stack_identity)
stack = parser.Stack.load(cnxt, stack=s)
self.resource_enforcer.enforce_stack(stack)
self.resource_enforcer.enforce_stack(stack, is_registered_policy=True)
snapshot = snapshot_object.Snapshot.get_snapshot_by_stack(
cnxt, snapshot_id, s)
# FIXME(pas-ha) has to be amended to deny restoring stacks

View File

@ -14,6 +14,7 @@
import itertools
from heat.policies import base
from heat.policies import resource_types
from heat.policies import stacks
@ -21,4 +22,5 @@ def list_rules():
return itertools.chain(
base.list_rules(),
stacks.list_rules(),
resource_types.list_rules(),
)

View File

@ -0,0 +1,69 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from heat.policies import base
POLICY_ROOT = 'resource_types:%s'
resource_types_policies = [
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Nova::Flavor',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Cinder::EncryptedVolumeType',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Cinder::VolumeType',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Cinder::Quota',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Neutron::Quota',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Nova::Quota',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Manila::ShareType',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Neutron::ProviderNet',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Neutron::QoSPolicy',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Neutron::QoSBandwidthLimitRule',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Neutron::Segment',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Nova::HostAggregate',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Cinder::QoSSpecs',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Cinder::QoSAssociation',
check_str=base.RULE_PROJECT_ADMIN),
policy.RuleDefault(
name=POLICY_ROOT % 'OS::Keystone::*',
check_str=base.RULE_PROJECT_ADMIN)
]
def list_rules():
return resource_types_policies

View File

@ -1,7 +1,7 @@
{
"context_is_admin": "role:admin",
"resource_types:OS::Test::AdminOnly": "rule:context_is_admin",
"resource_types:OS::Cinder::Quota": "!",
"resource_types:OS::Keystone::*": "rule:context_is_admin"
}

View File

@ -17,7 +17,6 @@
import os.path
from oslo_config import fixture as config_fixture
from oslo_policy import policy as base_policy
from heat.common import exception
from heat.common import policy
@ -177,55 +176,70 @@ class TestPolicyEnforcer(common.HeatTestCase):
def test_resource_default_rule(self):
context = utils.dummy_context(roles=['non-admin'])
enforcer = policy.ResourceEnforcer(
policy_file=self.get_policy_file('resources.json'))
enforcer = policy.ResourceEnforcer()
res_type = "OS::Test::NotInPolicy"
self.assertTrue(enforcer.enforce(context, res_type))
self.assertTrue(enforcer.enforce(context, res_type,
is_registered_policy=True))
def test_resource_enforce_success(self):
context = utils.dummy_context(roles=['admin'])
enforcer = policy.ResourceEnforcer(
policy_file=self.get_policy_file('resources.json'))
res_type = "OS::Test::AdminOnly"
self.assertTrue(enforcer.enforce(context, res_type))
enforcer = policy.ResourceEnforcer()
res_type = "OS::Keystone::User"
self.assertTrue(enforcer.enforce(context, res_type,
is_registered_policy=True))
def test_resource_enforce_fail(self):
context = utils.dummy_context(roles=['non-admin'])
enforcer = policy.ResourceEnforcer(
policy_file=self.get_policy_file('resources.json'))
res_type = "OS::Test::AdminOnly"
enforcer = policy.ResourceEnforcer()
res_type = "OS::Nova::Quota"
ex = self.assertRaises(exception.Forbidden,
enforcer.enforce,
context, res_type)
context, res_type,
None, None,
True)
self.assertIn(res_type, ex.message)
def test_resource_wildcard_enforce_fail(self):
context = utils.dummy_context(roles=['non-admin'])
enforcer = policy.ResourceEnforcer(
policy_file=self.get_policy_file('resources.json'))
enforcer = policy.ResourceEnforcer()
res_type = "OS::Keystone::User"
ex = self.assertRaises(exception.Forbidden,
enforcer.enforce,
context, res_type)
context, res_type,
None, None,
True)
self.assertIn(res_type.split("::", 1)[0], ex.message)
def test_resource_enforce_returns_false(self):
context = utils.dummy_context(roles=['non-admin'])
enforcer = policy.ResourceEnforcer(
policy_file=self.get_policy_file('resources.json'),
exc=None)
res_type = "OS::Test::AdminOnly"
self.assertFalse(enforcer.enforce(context, res_type))
self.assertIsNotNone(enforcer.enforce(context, res_type))
enforcer = policy.ResourceEnforcer(exc=None)
res_type = "OS::Keystone::User"
self.assertFalse(enforcer.enforce(context, res_type,
is_registered_policy=True))
self.assertIsNotNone(enforcer.enforce(context, res_type,
is_registered_policy=True))
def test_resource_enforce_exc_on_false(self):
context = utils.dummy_context(roles=['non-admin'])
enforcer = policy.ResourceEnforcer(
policy_file=self.get_policy_file('resources.json'))
res_type = "OS::Test::AdminOnly"
self.patchobject(base_policy.Enforcer, 'enforce',
return_value=False)
enforcer = policy.ResourceEnforcer()
res_type = "OS::Keystone::User"
ex = self.assertRaises(exception.Forbidden,
enforcer.enforce,
context, res_type)
context, res_type,
None, None,
True)
self.assertIn(res_type, ex.message)
def test_resource_enforce_override_deny_admin(self):
context = utils.dummy_context(roles=['admin'])
enforcer = policy.ResourceEnforcer(
policy_file=self.get_policy_file('resources.json'))
res_type = "OS::Cinder::Quota"
ex = self.assertRaises(exception.Forbidden,
enforcer.enforce,
context, res_type,
None, None,
True)
self.assertIn(res_type, ex.message)