Add separate policy for updates with no changes

Allow operators to set a different (presumably looser) policy on PATCH
updates that don't make any changes to the stack, but just retrigger a
new update traversal (that will result in e.g. replacing any unhealthy
resources).

Change-Id: Id29e7ec7f6cf127177ea7ab29127b0568afaa18b
Task: 37305
This commit is contained in:
Zane Bitter 2019-10-25 17:07:12 -04:00
parent 53976e9998
commit 37033936bf
4 changed files with 55 additions and 1 deletions

View File

@ -167,6 +167,17 @@ class InstantiationData(object):
params = self.data.items()
return dict((k, v) for k, v in params if k not in self.PARAMS)
def no_change(self):
assert self.patch
return ((self.template() is None) and
(self.environment() ==
environment_format.default_for_missing({})) and
(not self.files()) and
(not self.environment_files()) and
(self.files_container() is None) and
(not any(k != rpc_api.PARAM_EXISTING
for k in self.args().keys())))
class StackController(object):
"""WSGI controller for stacks resource in Heat v1 API.
@ -497,7 +508,8 @@ class StackController(object):
raise exc.HTTPAccepted()
@util.registered_identified_stack
@util.no_policy_enforce
@util._identified_stack
def update_patch(self, req, identity, body):
"""Update an existing stack with a new template.
@ -506,6 +518,15 @@ class StackController(object):
"""
data = InstantiationData(body, patch=True)
policy_act = 'update_no_change' if data.no_change() else 'update_patch'
allowed = req.context.policy.enforce(
context=req.context,
action=policy_act,
scope=self.REQUEST_SCOPE,
is_registered_policy=True)
if not allowed:
raise exc.HTTPForbidden()
args = self.prepare_args(data, is_update=True)
self.rpc_client.update_stack(
req.context,

View File

@ -42,6 +42,22 @@ def registered_policy_enforce(handler):
return handle_stack_method
def no_policy_enforce(handler):
"""Decorator that does *not* enforce policies.
Checks the path matches the request context.
This is a handler method decorator.
"""
@six.wraps(handler)
def handle_stack_method(controller, req, tenant_id, **kwargs):
if req.context.tenant_id != tenant_id and not req.context.is_admin:
raise exc.HTTPForbidden()
return handler(controller, req, **kwargs)
return handle_stack_method
def registered_identified_stack(handler):
"""Decorator that passes a stack identifier instead of path components.

View File

@ -244,6 +244,17 @@ stacks_policies = [
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'update_no_change',
check_str='rule:%s' % (POLICY_ROOT % 'update_patch'),
description='Update stack (PATCH) with no changes.',
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,

View File

@ -0,0 +1,6 @@
---
features:
- |
Operators can now set a separate ``stacks:update_no_change`` policy for
PATCH updates that don't modify the stack, independently of the existing
``stacks:update_patch`` policy.