[policy in code] part 2 (stacks)

Allow use policy in code to stacks's rule.
Also convert check_is_admin to use new mechanism.
Partially-Implements: bp policy-in-code

Change-Id: I398ed162790294d0d4453f7f12c77b38e95a5580
This commit is contained in:
ricolin 2017-10-06 13:05:11 +08:00
parent f4a06c2a92
commit 575a45b1c0
7 changed files with 408 additions and 70 deletions

View File

@ -34,36 +34,6 @@
"resource:signal": "",
"resource:mark_unhealthy": "rule:deny_stack_user",
"resource:show": "rule:deny_stack_user",
"stacks:abandon": "rule:deny_stack_user",
"stacks:create": "rule:deny_stack_user",
"stacks:delete": "rule:deny_stack_user",
"stacks:detail": "rule:deny_stack_user",
"stacks:export": "rule:deny_stack_user",
"stacks:generate_template": "rule:deny_stack_user",
"stacks:global_index": "rule:deny_everybody",
"stacks:index": "rule:deny_stack_user",
"stacks:list_resource_types": "rule:deny_stack_user",
"stacks:list_template_versions": "rule:deny_stack_user",
"stacks:list_template_functions": "rule:deny_stack_user",
"stacks:lookup": "",
"stacks:preview": "rule:deny_stack_user",
"stacks:resource_schema": "rule:deny_stack_user",
"stacks:show": "rule:deny_stack_user",
"stacks:template": "rule:deny_stack_user",
"stacks:environment": "rule:deny_stack_user",
"stacks:files": "rule:deny_stack_user",
"stacks:update": "rule:deny_stack_user",
"stacks:update_patch": "rule:deny_stack_user",
"stacks:preview_update": "rule:deny_stack_user",
"stacks:preview_update_patch": "rule:deny_stack_user",
"stacks:validate_template": "rule:deny_stack_user",
"stacks:snapshot": "rule:deny_stack_user",
"stacks:show_snapshot": "rule:deny_stack_user",
"stacks:delete_snapshot": "rule:deny_stack_user",
"stacks:list_snapshots": "rule:deny_stack_user",
"stacks:restore_snapshot": "rule:deny_stack_user",
"stacks:list_outputs": "rule:deny_stack_user",
"stacks:show_output": "rule:deny_stack_user",
"software_configs:global_index": "rule:deny_everybody",
"software_configs:index": "rule:deny_stack_user",

View File

@ -168,7 +168,8 @@ class StackController(object):
Implements the API actions.
"""
# Define request scope (must match what is in policy.json)
# Define request scope (must match what is in policy.json or policies in
# code)
REQUEST_SCOPE = 'stacks'
def __init__(self, options):
@ -329,11 +330,11 @@ class StackController(object):
count=count,
include_project=cnxt.is_admin)
@util.policy_enforce
@util.registered_policy_enforce
def global_index(self, req):
return self._index(req, use_admin_cnxt=True)
@util.policy_enforce
@util.registered_policy_enforce
def index(self, req):
"""Lists summary information for all stacks."""
global_tenant = False
@ -348,14 +349,14 @@ class StackController(object):
return self._index(req)
@util.policy_enforce
@util.registered_policy_enforce
def detail(self, req):
"""Lists detailed information for all stacks."""
stacks = self.rpc_client.list_stacks(req.context)
return {'stacks': [stacks_view.format_stack(req, s) for s in stacks]}
@util.policy_enforce
@util.registered_policy_enforce
def preview(self, req, body):
"""Preview the outcome of a template and its params."""
@ -389,7 +390,7 @@ class StackController(object):
raise exc.HTTPBadRequest(six.text_type(msg))
return args
@util.policy_enforce
@util.registered_policy_enforce
def create(self, req, body):
"""Create a new stack."""
data = InstantiationData(body)
@ -410,7 +411,7 @@ class StackController(object):
)
return {'stack': formatted_stack}
@util.policy_enforce
@util.registered_policy_enforce
def lookup(self, req, stack_name, path='', body=None):
"""Redirect to the canonical URL for a stack."""
try:
@ -429,7 +430,7 @@ class StackController(object):
raise exc.HTTPFound(location=location)
@util.identified_stack
@util.registered_identified_stack
def show(self, req, identity):
"""Gets detailed information for a stack."""
params = req.params
@ -450,7 +451,7 @@ class StackController(object):
return {'stack': stacks_view.format_stack(req, stack)}
@util.identified_stack
@util.registered_identified_stack
def template(self, req, identity):
"""Get the template body for an existing stack."""
@ -460,19 +461,19 @@ class StackController(object):
# TODO(zaneb): always set Content-type to application/json
return templ
@util.identified_stack
@util.registered_identified_stack
def environment(self, req, identity):
"""Get the environment for an existing stack."""
env = self.rpc_client.get_environment(req.context, identity)
return env
@util.identified_stack
@util.registered_identified_stack
def files(self, req, identity):
"""Get the files for an existing stack."""
return self.rpc_client.get_files(req.context, identity)
@util.identified_stack
@util.registered_identified_stack
def update(self, req, identity, body):
"""Update an existing stack with a new template and/or parameters."""
data = InstantiationData(body)
@ -489,7 +490,7 @@ class StackController(object):
raise exc.HTTPAccepted()
@util.identified_stack
@util.registered_identified_stack
def update_patch(self, req, identity, body):
"""Update an existing stack with a new template.
@ -518,7 +519,7 @@ class StackController(object):
if p_name in params:
return self._extract_bool_param(p_name, params[p_name])
@util.identified_stack
@util.registered_identified_stack
def preview_update(self, req, identity, body):
"""Preview update for existing stack with a new template/parameters."""
data = InstantiationData(body)
@ -538,7 +539,7 @@ class StackController(object):
return {'resource_changes': changes}
@util.identified_stack
@util.registered_identified_stack
def preview_update_patch(self, req, identity, body):
"""Preview PATCH update for existing stack."""
data = InstantiationData(body, patch=True)
@ -558,7 +559,7 @@ class StackController(object):
return {'resource_changes': changes}
@util.identified_stack
@util.registered_identified_stack
def delete(self, req, identity):
"""Delete the specified stack."""
@ -567,7 +568,7 @@ class StackController(object):
cast=False)
raise exc.HTTPNoContent()
@util.identified_stack
@util.registered_identified_stack
def abandon(self, req, identity):
"""Abandons specified stack.
@ -577,7 +578,7 @@ class StackController(object):
return self.rpc_client.abandon_stack(req.context,
identity)
@util.identified_stack
@util.registered_identified_stack
def export(self, req, identity):
"""Export specified stack.
@ -585,7 +586,7 @@ class StackController(object):
"""
return self.rpc_client.export_stack(req.context, identity)
@util.policy_enforce
@util.registered_policy_enforce
def validate_template(self, req, body):
"""Implements the ValidateTemplate API action.
@ -623,7 +624,7 @@ class StackController(object):
return result
@util.policy_enforce
@util.registered_policy_enforce
def list_resource_types(self, req):
"""Returns a resource types list which may be used in template."""
support_status = req.params.get('support_status')
@ -646,7 +647,7 @@ class StackController(object):
heat_version=version,
with_description=with_description)}
@util.policy_enforce
@util.registered_policy_enforce
def list_template_versions(self, req):
"""Returns a list of available template versions."""
return {
@ -654,7 +655,7 @@ class StackController(object):
self.rpc_client.list_template_versions(req.context)
}
@util.policy_enforce
@util.registered_policy_enforce
def list_template_functions(self, req, template_version):
"""Returns a list of available functions in a given template."""
if req.params.get('with_condition_func') is not None:
@ -671,14 +672,14 @@ class StackController(object):
with_condition)
}
@util.policy_enforce
@util.registered_policy_enforce
def resource_schema(self, req, type_name, with_description=False):
"""Returns the schema of the given resource type."""
return self.rpc_client.resource_schema(
req.context, type_name,
self._extract_bool_param('with_description', with_description))
@util.policy_enforce
@util.registered_policy_enforce
def generate_template(self, req, type_name):
"""Generates a template based on the specified type."""
template_type = 'cfn'
@ -694,42 +695,42 @@ class StackController(object):
type_name,
template_type)
@util.identified_stack
@util.registered_identified_stack
def snapshot(self, req, identity, body):
name = body.get('name')
return self.rpc_client.stack_snapshot(req.context, identity, name)
@util.identified_stack
@util.registered_identified_stack
def show_snapshot(self, req, identity, snapshot_id):
snapshot = self.rpc_client.show_snapshot(
req.context, identity, snapshot_id)
return {'snapshot': snapshot}
@util.identified_stack
@util.registered_identified_stack
def delete_snapshot(self, req, identity, snapshot_id):
self.rpc_client.delete_snapshot(req.context, identity, snapshot_id)
raise exc.HTTPNoContent()
@util.identified_stack
@util.registered_identified_stack
def list_snapshots(self, req, identity):
return {
'snapshots': self.rpc_client.stack_list_snapshots(
req.context, identity)
}
@util.identified_stack
@util.registered_identified_stack
def restore_snapshot(self, req, identity, snapshot_id):
self.rpc_client.stack_restore(req.context, identity, snapshot_id)
raise exc.HTTPAccepted()
@util.identified_stack
@util.registered_identified_stack
def list_outputs(self, req, identity):
return {
'outputs': self.rpc_client.list_outputs(
req.context, identity)
}
@util.identified_stack
@util.registered_identified_stack
def show_output(self, req, identity, output_key):
return {'output': self.rpc_client.show_output(req.context,
identity,

View File

@ -108,7 +108,8 @@ class Enforcer(object):
:param context: Heat request context
:returns: A non-False value if the user is admin according to policy
"""
return self._check(context, 'context_is_admin', target={}, exc=None)
return self._check(context, 'context_is_admin', target={}, exc=None,
is_registered_policy=True)
def get_enforcer():

View File

@ -14,9 +14,11 @@
import itertools
from heat.policies import base
from heat.policies import stacks
def list_rules():
return itertools.chain(
base.list_rules(),
stacks.list_rules(),
)

370
heat/policies/stacks.py Normal file
View File

@ -0,0 +1,370 @@
# 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 = 'stacks:%s'
stacks_policies = [
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'abandon',
check_str=base.RULE_DENY_STACK_USER,
description='Abandon stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'abandon',
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create',
check_str=base.RULE_DENY_STACK_USER,
description='Create stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete',
check_str=base.RULE_DENY_STACK_USER,
description='Delete stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}',
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'detail',
check_str=base.RULE_DENY_STACK_USER,
description='List stacks in detail.',
operations=[
{
'path': '/v1/{tenant_id}/stacks',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'export',
check_str=base.RULE_DENY_STACK_USER,
description='Export stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'export',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'generate_template',
check_str=base.RULE_DENY_STACK_USER,
description='Generate stack template.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'template',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'global_index',
check_str=base.RULE_DENY_EVERYBODY,
description='List stacks globally.',
operations=[
{
'path': '/v1/{tenant_id}/stacks',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'index',
check_str=base.RULE_DENY_STACK_USER,
description='List stacks.',
operations=[
{
'path': '/v1/{tenant_id}/stacks',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'list_resource_types',
check_str=base.RULE_DENY_STACK_USER,
description='List resource types.',
operations=[
{
'path': '/v1/{tenant_id}/resource_types',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'list_template_versions',
check_str=base.RULE_DENY_STACK_USER,
description='List template versions.',
operations=[
{
'path': '/v1/{tenant_id}/template_versions',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'list_template_functions',
check_str=base.RULE_DENY_STACK_USER,
description='List template functions.',
operations=[
{
'path': '/v1/{tenant_id}/template_versions/'
'{template_version}/functions',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'lookup',
check_str=base.RULE_ALLOW_EVERYBODY,
description='Find stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_identity}',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'preview',
check_str=base.RULE_DENY_STACK_USER,
description='Preview stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/preview',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'resource_schema',
check_str=base.RULE_DENY_STACK_USER,
description='Show resource type schema.',
operations=[
{
'path': '/v1/{tenant_id}/resource_types/{type_name}',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'show',
check_str=base.RULE_DENY_STACK_USER,
description='Show stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_identity}',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'template',
check_str=base.RULE_DENY_STACK_USER,
description='Get stack template.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'template',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'environment',
check_str=base.RULE_DENY_STACK_USER,
description='Get stack environment.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'environment',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'files',
check_str=base.RULE_DENY_STACK_USER,
description='Get stack files.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'files',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'update',
check_str=base.RULE_DENY_STACK_USER,
description='Update stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}',
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'update_patch',
check_str=base.RULE_DENY_STACK_USER,
description='Update stack (PATCH).',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}',
'method': 'PATCH'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'preview_update',
check_str=base.RULE_DENY_STACK_USER,
description='Preview update stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'preview',
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'preview_update_patch',
check_str=base.RULE_DENY_STACK_USER,
description='Preview update stack (PATCH).',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'preview',
'method': 'PATCH'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'validate_template',
check_str=base.RULE_DENY_STACK_USER,
description='Validate template.',
operations=[
{
'path': '/v1/{tenant_id}/validate',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'snapshot',
check_str=base.RULE_DENY_STACK_USER,
description='Snapshot Stack.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'snapshots',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'show_snapshot',
check_str=base.RULE_DENY_STACK_USER,
description='Show snapshot.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'snapshots/{snapshot_id}',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete_snapshot',
check_str=base.RULE_DENY_STACK_USER,
description='Delete snapshot.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'snapshots/{snapshot_id}',
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'list_snapshots',
check_str=base.RULE_DENY_STACK_USER,
description='List snapshots.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'snapshots',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'restore_snapshot',
check_str=base.RULE_DENY_STACK_USER,
description='Restore snapshot.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'snapshots/{snapshot_id}/restore',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'list_outputs',
check_str=base.RULE_DENY_STACK_USER,
description='List outputs.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'outputs',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'show_output',
check_str=base.RULE_DENY_STACK_USER,
description='Show outputs.',
operations=[
{
'path': '/v1/{tenant_id}/stacks/{stack_name}/{stack_id}/'
'outputs/{output_key}',
'method': 'GET'
}
]
)
]
def list_rules():
return stacks_policies

View File

@ -473,7 +473,7 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
self.controller.index(req, tenant_id=self.tenant)
mock_enforce.assert_called_with(action='global_index',
scope=self.controller.REQUEST_SCOPE,
is_registered_policy=False,
is_registered_policy=True,
context=self.context)
def test_global_index_uses_admin_context(self, mock_enforce):

View File

@ -159,8 +159,7 @@ class TestPolicyEnforcer(common.HeatTestCase):
self.assertFalse(enforcer.enforce(ctx, action))
def test_check_admin(self):
enforcer = policy.Enforcer(
policy_file=self.get_policy_file('check_admin.json'))
enforcer = policy.Enforcer()
ctx = utils.dummy_context(roles=[])
self.assertFalse(enforcer.check_is_admin(ctx))
@ -174,11 +173,6 @@ class TestPolicyEnforcer(common.HeatTestCase):
def test_enforce_creds(self):
enforcer = policy.Enforcer()
ctx = utils.dummy_context(roles=['admin'])
self.m.StubOutWithMock(base_policy.Enforcer, 'enforce')
base_policy.Enforcer.enforce('context_is_admin', {},
ctx.to_policy_values(),
False, exc=None).AndReturn(True)
self.m.ReplayAll()
self.assertTrue(enforcer.check_is_admin(ctx))
def test_resource_default_rule(self):