summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--etc/heat/heat-policy-generator.conf4
-rw-r--r--etc/heat/policy.json5
-rw-r--r--heat/api/openstack/v1/util.py44
-rw-r--r--heat/common/policy.py32
-rw-r--r--heat/policies/__init__.py22
-rw-r--r--heat/policies/base.py48
-rw-r--r--heat/tests/api/openstack_v1/test_stacks.py1
-rw-r--r--heat/tests/api/openstack_v1/tools.py5
-rw-r--r--setup.cfg3
-rw-r--r--tox.ini4
11 files changed, 152 insertions, 19 deletions
diff --git a/.gitignore b/.gitignore
index 073858cde..c307e065e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,5 +25,8 @@ etc/heat/heat.conf.sample
# integration tests requirements are auto-generated from stub file
heat_integrationtests/requirements.txt
+# generated policy file
+etc/heat/policy.json.sample
+
# Files created by releasenotes build
releasenotes/build
diff --git a/etc/heat/heat-policy-generator.conf b/etc/heat/heat-policy-generator.conf
new file mode 100644
index 000000000..6d11632ac
--- /dev/null
+++ b/etc/heat/heat-policy-generator.conf
@@ -0,0 +1,4 @@
+[DEFAULT]
+format = json
+namespace = heat
+output_file = etc/heat/policy.json.sample
diff --git a/etc/heat/policy.json b/etc/heat/policy.json
index f805f2c54..c3a3bd58f 100644
--- a/etc/heat/policy.json
+++ b/etc/heat/policy.json
@@ -1,9 +1,4 @@
{
- "context_is_admin": "role:admin and is_admin_project:True",
- "project_admin": "role:admin",
- "deny_stack_user": "not role:heat_stack_user",
- "deny_everybody": "!",
-
"cloudformation:ListStacks": "rule:deny_stack_user",
"cloudformation:CreateStack": "rule:deny_stack_user",
"cloudformation:DescribeStacks": "rule:deny_stack_user",
diff --git a/heat/api/openstack/v1/util.py b/heat/api/openstack/v1/util.py
index 6782a3219..3bf6dab13 100644
--- a/heat/api/openstack/v1/util.py
+++ b/heat/api/openstack/v1/util.py
@@ -22,17 +22,34 @@ def policy_enforce(handler):
"""Decorator that enforces policies.
Checks the path matches the request context and enforce policy defined in
- policy.json.
+ policy.json or in policies.
This is a handler method decorator.
"""
+ return _policy_enforce(handler)
+
+
+def registered_policy_enforce(handler):
+ """Decorator that enforces policies.
+
+ Checks the path matches the request context and enforce policy defined in
+ policies.
+
+ This is a handler method decorator.
+ """
+ return _policy_enforce(handler, is_registered_policy=True)
+
+
+def _policy_enforce(handler, is_registered_policy=False):
@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()
- allowed = req.context.policy.enforce(context=req.context,
- action=handler.__name__,
- scope=controller.REQUEST_SCOPE)
+ allowed = req.context.policy.enforce(
+ context=req.context,
+ action=handler.__name__,
+ scope=controller.REQUEST_SCOPE,
+ is_registered_policy=is_registered_policy)
if not allowed:
raise exc.HTTPForbidden()
return handler(controller, req, **kwargs)
@@ -45,7 +62,21 @@ def identified_stack(handler):
This is a handler method decorator.
"""
- @policy_enforce
+
+ return _identified_stack(handler)
+
+
+def registered_identified_stack(handler):
+ """Decorator that passes a stack identifier instead of path components.
+
+ This is a handler method decorator.
+ """
+
+ return _identified_stack(handler, is_registered_policy=True)
+
+
+def _identified_stack(handler, is_registered_policy=False):
+
@six.wraps(handler)
def handle_stack_method(controller, req, stack_name, stack_id, **kwargs):
stack_identity = identifier.HeatIdentifier(req.context.tenant_id,
@@ -53,7 +84,8 @@ def identified_stack(handler):
stack_id)
return handler(controller, req, dict(stack_identity), **kwargs)
- return handle_stack_method
+ return _policy_enforce(handle_stack_method,
+ is_registered_policy=is_registered_policy)
def make_url(req, identity):
diff --git a/heat/common/policy.py b/heat/common/policy.py
index 605a19ebf..767d1b540 100644
--- a/heat/common/policy.py
+++ b/heat/common/policy.py
@@ -20,9 +20,12 @@
from oslo_config import cfg
from oslo_log import log as logging
from oslo_policy import policy
+from oslo_utils import excutils
import six
from heat.common import exception
+from heat.common.i18n import _
+from heat import policies
CONF = cfg.CONF
@@ -45,6 +48,9 @@ class Enforcer(object):
self.enforcer = policy.Enforcer(
CONF, default_rule=default_rule, policy_file=policy_file)
+ # register rules
+ self.enforcer.register_defaults(policies.list_rules())
+
def set_rules(self, rules, overwrite=True):
"""Create a new Rules object based on the provided dict of rules."""
rules_obj = policy.Rules(rules, self.default_rule)
@@ -54,7 +60,8 @@ class Enforcer(object):
"""Set the rules found in the json file on disk."""
self.enforcer.load_rules(force_reload)
- def _check(self, context, rule, target, exc, *args, **kwargs):
+ def _check(self, context, rule, target, exc,
+ is_registered_policy=False, *args, **kwargs):
"""Verifies that the action is valid on the target in this context.
:param context: Heat request context
@@ -65,10 +72,20 @@ class Enforcer(object):
"""
do_raise = False if not exc else True
credentials = context.to_policy_values()
- return self.enforcer.enforce(rule, target, credentials,
- do_raise, exc=exc, *args, **kwargs)
-
- def enforce(self, context, action, scope=None, target=None):
+ if is_registered_policy:
+ try:
+ return self.enforcer.authorize(rule, target, credentials,
+ do_raise=do_raise,
+ exc=exc, action=rule)
+ except policy.PolicyNotRegistered:
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_('Policy not registered.'))
+ else:
+ return self.enforcer.enforce(rule, target, credentials,
+ do_raise, exc=exc, *args, **kwargs)
+
+ def enforce(self, context, action, scope=None, target=None,
+ is_registered_policy=False):
"""Verifies that the action is valid on the target in this context.
:param context: Heat request context
@@ -79,10 +96,11 @@ class Enforcer(object):
"""
_action = '%s:%s' % (scope or self.scope, action)
_target = target or {}
- return self._check(context, _action, _target, self.exc, action=action)
+ return self._check(context, _action, _target, self.exc, action=action,
+ is_registered_policy=is_registered_policy)
def check_is_admin(self, context):
- """Whether or not is admin according to policy.json.
+ """Whether or not is admin according to policy.
By default the rule will check whether or not roles contains
'admin' role and is admin project.
diff --git a/heat/policies/__init__.py b/heat/policies/__init__.py
new file mode 100644
index 000000000..b35b935a3
--- /dev/null
+++ b/heat/policies/__init__.py
@@ -0,0 +1,22 @@
+# 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.
+
+
+import itertools
+
+from heat.policies import base
+
+
+def list_rules():
+ return itertools.chain(
+ base.list_rules(),
+ )
diff --git a/heat/policies/base.py b/heat/policies/base.py
new file mode 100644
index 000000000..7f4d8643d
--- /dev/null
+++ b/heat/policies/base.py
@@ -0,0 +1,48 @@
+# 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
+
+RULE_CONTEXT_IS_ADMIN = 'rule:context_is_admin'
+RULE_PROJECT_ADMIN = 'rule:project_admin'
+RULE_DENY_STACK_USER = 'rule:deny_stack_user'
+RULE_DENY_EVERYBODY = 'rule:deny_everybody'
+RULE_ALLOW_EVERYBODY = 'rule:allow_everybody'
+
+
+rules = [
+ policy.RuleDefault(
+ name="context_is_admin",
+ check_str="role:admin and is_admin_project:True",
+ description="Decides what is required for the 'is_admin:True' check "
+ "to succeed."),
+ policy.RuleDefault(
+ name="project_admin",
+ check_str="role:admin",
+ description="Default rule for project admin."),
+ policy.RuleDefault(
+ name="deny_stack_user",
+ check_str="not role:heat_stack_user",
+ description="Default rule for deny stack user."),
+ policy.RuleDefault(
+ name="deny_everybody",
+ check_str="!",
+ description="Default rule for deny everybody."),
+ policy.RuleDefault(
+ name="allow_everybody",
+ check_str="",
+ description="Default rule for allow everybody.")
+]
+
+
+def list_rules():
+ return rules
diff --git a/heat/tests/api/openstack_v1/test_stacks.py b/heat/tests/api/openstack_v1/test_stacks.py
index 7e7257b72..f7a9b7686 100644
--- a/heat/tests/api/openstack_v1/test_stacks.py
+++ b/heat/tests/api/openstack_v1/test_stacks.py
@@ -473,6 +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,
context=self.context)
def test_global_index_uses_admin_context(self, mock_enforce):
diff --git a/heat/tests/api/openstack_v1/tools.py b/heat/tests/api/openstack_v1/tools.py
index 5212cc659..3a27b8697 100644
--- a/heat/tests/api/openstack_v1/tools.py
+++ b/heat/tests/api/openstack_v1/tools.py
@@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import mock
from oslo_config import cfg
from oslo_log import log
from oslo_messaging._drivers import common as rpc_common
@@ -117,7 +118,9 @@ class ControllerTest(object):
self.mock_enforce.assert_called_with(
action=self.action,
context=self.context,
- scope=self.controller.REQUEST_SCOPE)
+ scope=self.controller.REQUEST_SCOPE,
+ is_registered_policy=mock.ANY
+ )
self.assertEqual(self.expected_request_count,
len(self.mock_enforce.call_args_list))
super(ControllerTest, self).tearDown()
diff --git a/setup.cfg b/setup.cfg
index 53c325361..bc656ac69 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -63,6 +63,9 @@ oslo.config.opts =
oslo.config.opts.defaults =
heat.common.config = heat.common.config:set_config_defaults
+oslo.policy.policies =
+ heat = heat.policies:list_rules
+
heat.clients =
aodh = heat.engine.clients.os.aodh:AodhClientPlugin
barbican = heat.engine.clients.os.barbican:BarbicanClientPlugin
diff --git a/tox.ini b/tox.ini
index f9af57890..64f9b7498 100644
--- a/tox.ini
+++ b/tox.ini
@@ -72,6 +72,10 @@ commands =
commands =
oslo-config-generator --config-file=config-generator.conf
+[testenv:genpolicy]
+commands =
+ oslopolicy-sample-generator --config-file etc/heat/heat-policy-generator.conf
+
[testenv:bandit]
deps = -r{toxinidir}/test-requirements.txt
# The following bandit tests are being skipped: