--- /dev/null
+{
+ "deny_stack_user": "not role:heat_stack_user",
+ "cloudformation:ListStacks": "rule:deny_stack_user",
+ "cloudformation:CreateStack": "rule:deny_stack_user",
+ "cloudformation:DescribeStacks": "rule:deny_stack_user",
+ "cloudformation:DeleteStack": "rule:deny_stack_user",
+ "cloudformation:UpdateStack": "rule:deny_stack_user",
+ "cloudformation:DescribeStackEvents": "rule:deny_stack_user",
+ "cloudformation:ValidateTemplate": "rule:deny_stack_user",
+ "cloudformation:GetTemplate": "rule:deny_stack_user",
+ "cloudformation:EstimateTemplateCost": "rule:deny_stack_user",
+ "cloudformation:DescribeStackResource": "",
+ "cloudformation:DescribeStackResources": "rule:deny_stack_user",
+ "cloudformation:ListStackResources": "rule:deny_stack_user"
+}
from heat.api.aws import exception
from heat.api.aws import utils as api_utils
from heat.common import wsgi
+from heat.common import exception as heat_exception
from heat.rpc import client as rpc_client
from heat.common import template_format
from heat.rpc import api as engine_api
from heat.common import identifier
from heat.common import urlfetch
+from heat.common import policy
import heat.openstack.common.rpc.common as rpc_common
def __init__(self, options):
self.options = options
self.engine_rpcapi = rpc_client.EngineClient()
+ self.policy = policy.Enforcer(scope='cloudformation')
+
+ def _enforce(self, req, action):
+ """Authorize an action against the policy.json"""
+ try:
+ self.policy.enforce(req.context, action, {})
+ except heat_exception.Forbidden:
+ raise exception.HeatAccessDeniedError("Action %s not allowed " %
+ action + "for user")
+ except Exception as ex:
+ # We expect policy.enforce to either pass or raise Forbidden
+ # however, if anything else happens, we want to raise
+ # HeatInternalFailureError, failure to do this results in
+ # the user getting a big stacktrace spew as an API response
+ raise exception.HeatInternalFailureError("Error authorizing " +
+ "action %s" % action)
@staticmethod
def _id_format(resp):
Implements ListStacks API action
Lists summary information for all stacks
"""
+ self._enforce(req, 'ListStacks')
def format_stack_summary(s):
"""
Implements DescribeStacks API action
Gets detailed information for a stack (or all stacks)
"""
+ self._enforce(req, 'DescribeStacks')
+
def format_stack_outputs(o):
keymap = {
engine_api.OUTPUT_DESCRIPTION: 'Description',
Implements the GetTemplate API action
Get the template body for an existing stack
"""
+ self._enforce(req, 'GetTemplate')
con = req.context
try:
Implements the EstimateTemplateCost API action
Get the estimated monthly cost of a template
"""
+ self._enforce(req, 'EstimateTemplateCost')
+
return api_utils.format_response('EstimateTemplateCost',
{'Url':
'http://en.wikipedia.org/wiki/Gratis'
Implements the ValidateTemplate API action
Validates the specified template
"""
+ self._enforce(req, 'ValidateTemplate')
con = req.context
try:
Implements the DeleteStack API action
Deletes the specified stack
"""
+ self._enforce(req, 'DeleteStack')
+
con = req.context
try:
identity = self._get_identity(con, req.params['StackName'])
Implements the DescribeStackEvents API action
Returns events related to a specified stack (or all stacks)
"""
+ self._enforce(req, 'DescribeStackEvents')
+
def format_stack_event(e):
"""
Reformat engine output into the AWS "StackEvent" format
Implements the DescribeStackResource API action
Return the details of the given resource belonging to the given stack.
"""
+ self._enforce(req, 'DescribeStackResource')
def format_resource_detail(r):
"""
`LogicalResourceId`: filter the resources list by the logical resource
id.
"""
+ self._enforce(req, 'DescribeStackResources')
def format_stack_resource(r):
"""
Implements the ListStackResources API action
Return summary of the resources belonging to the specified stack.
"""
+ self._enforce(req, 'ListStackResources')
+
def format_resource_summary(r):
"""
Reformat engine output into the AWS "StackResourceSummary" format
import mox
import json
+import os
import unittest
from nose.plugins.attrib import attr
from heat.common import context
from heat.common import identifier
+from heat.common import policy
from heat.openstack.common import cfg
from heat.openstack.common import rpc
import heat.openstack.common.rpc.common as rpc_common
self.assertEqual(response, expected)
self.m.VerifyAll()
+ def test_enforce_default(self):
+ self.m.ReplayAll()
+ params = {'Action': 'ListStacks'}
+ dummy_req = self._dummy_GET_request(params)
+ self.controller.policy.policy_path = None
+ response = self.controller._enforce(dummy_req, 'ListStacks')
+ self.assertEqual(response, None)
+ self.m.VerifyAll()
+
+ def test_enforce_denied(self):
+ self.m.ReplayAll()
+ params = {'Action': 'ListStacks'}
+ dummy_req = self._dummy_GET_request(params)
+ dummy_req.context.roles = ['heat_stack_user']
+ self.controller.policy.policy_path = (self.policy_path +
+ 'deny_stack_user.json')
+ self.assertRaises(exception.HeatAccessDeniedError,
+ self.controller._enforce, dummy_req, 'ListStacks')
+ self.m.VerifyAll()
+
+ def test_enforce_ise(self):
+ params = {'Action': 'ListStacks'}
+ dummy_req = self._dummy_GET_request(params)
+ dummy_req.context.roles = ['heat_stack_user']
+
+ self.m.StubOutWithMock(policy.Enforcer, 'enforce')
+ policy.Enforcer.enforce(dummy_req.context, 'ListStacks', {}
+ ).AndRaise(AttributeError)
+ self.m.ReplayAll()
+
+ self.controller.policy.policy_path = (self.policy_path +
+ 'deny_stack_user.json')
+ self.assertRaises(exception.HeatInternalFailureError,
+ self.controller._enforce, dummy_req, 'ListStacks')
+ self.m.VerifyAll()
+
def test_list(self):
# Format a dummy GET request to pass into the WSGI handler
params = {'Action': 'ListStacks'}
self.maxDiff = None
self.m = mox.Mox()
+ self.path = os.path.dirname(os.path.realpath(__file__))
+ self.policy_path = self.path + "/policy/"
+ opts = [
+ cfg.StrOpt('config_dir', default=self.policy_path),
+ cfg.StrOpt('config_file', default='foo'),
+ cfg.StrOpt('project', default='heat'),
+ ]
+ cfg.CONF.register_opts(opts)
cfg.CONF.set_default('engine_topic', 'engine')
cfg.CONF.set_default('host', 'host')
self.topic = '%s.%s' % (cfg.CONF.engine_topic, cfg.CONF.host)