Merge "Implemented a policy enforcement engine"
This commit is contained in:
commit
52b4d8050b
0
monasca_common/policy/__init__.py
Normal file
0
monasca_common/policy/__init__.py
Normal file
46
monasca_common/policy/i18n.py
Normal file
46
monasca_common/policy/i18n.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""oslo.i18n integration module.
|
||||||
|
|
||||||
|
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import oslo_i18n
|
||||||
|
|
||||||
|
DOMAIN = 'monasca'
|
||||||
|
|
||||||
|
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||||
|
|
||||||
|
# The primary translation function using the well-known name "_"
|
||||||
|
_ = _translators.primary
|
||||||
|
|
||||||
|
# Translators for log levels.
|
||||||
|
#
|
||||||
|
# The abbreviated names are meant to reflect the usual use of a short
|
||||||
|
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||||
|
# the level.
|
||||||
|
_LI = _translators.log_info
|
||||||
|
_LW = _translators.log_warning
|
||||||
|
_LE = _translators.log_error
|
||||||
|
_LC = _translators.log_critical
|
||||||
|
|
||||||
|
|
||||||
|
def translate(value, user_locale):
|
||||||
|
return oslo_i18n.translate(value, user_locale)
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_languages():
|
||||||
|
return oslo_i18n.get_available_languages(DOMAIN)
|
248
monasca_common/policy/policy_engine.py
Normal file
248
monasca_common/policy/policy_engine.py
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# Copyright 2017 OP5 AB
|
||||||
|
# Copyright 2017 FUJITSU LIMITED
|
||||||
|
# Copyright (c) 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 copy
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from monasca_common.policy.i18n import _LW
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
POLICIES = None
|
||||||
|
USER_BASED_RESOURCES = ['os-keypairs']
|
||||||
|
KEY_EXPR = re.compile(r'%\((\w+)\)s')
|
||||||
|
|
||||||
|
|
||||||
|
_ENFORCER = None
|
||||||
|
# oslo_policy will read the policy configuration file again when the file
|
||||||
|
# is changed in runtime so the old policy rules will be saved to
|
||||||
|
# saved_file_rules and used to compare with new rules to determine
|
||||||
|
# whether the rules were updated.
|
||||||
|
saved_file_rules = []
|
||||||
|
|
||||||
|
|
||||||
|
def reset():
|
||||||
|
"""Reset Enforcer class."""
|
||||||
|
global _ENFORCER
|
||||||
|
if _ENFORCER:
|
||||||
|
_ENFORCER.clear()
|
||||||
|
_ENFORCER = None
|
||||||
|
|
||||||
|
|
||||||
|
def init(policy_file=None, rules=None, default_rule=None, use_conf=True):
|
||||||
|
"""Init an Enforcer class.
|
||||||
|
|
||||||
|
:param policy_file: Custom policy file to use, if none is specified,
|
||||||
|
`CONF.policy_file` will be used.
|
||||||
|
:param rules: Default dictionary / Rules to use. It will be
|
||||||
|
considered just in the first instantiation.
|
||||||
|
:param default_rule: Default rule to use, CONF.default_rule will
|
||||||
|
be used if none is specified.
|
||||||
|
:param use_conf: Whether to load rules from config file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
global _ENFORCER
|
||||||
|
global saved_file_rules
|
||||||
|
|
||||||
|
if not _ENFORCER:
|
||||||
|
_ENFORCER = policy.Enforcer(CONF,
|
||||||
|
policy_file=policy_file,
|
||||||
|
rules=rules,
|
||||||
|
default_rule=default_rule,
|
||||||
|
use_conf=use_conf
|
||||||
|
)
|
||||||
|
register_rules(_ENFORCER)
|
||||||
|
_ENFORCER.load_rules()
|
||||||
|
# Only the rules which are loaded from file may be changed
|
||||||
|
current_file_rules = _ENFORCER.file_rules
|
||||||
|
current_file_rules = _serialize_rules(current_file_rules)
|
||||||
|
|
||||||
|
if saved_file_rules != current_file_rules:
|
||||||
|
_warning_for_deprecated_user_based_rules(current_file_rules)
|
||||||
|
saved_file_rules = copy.deepcopy(current_file_rules)
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize_rules(rules):
|
||||||
|
"""Serialize all the Rule object as string.
|
||||||
|
|
||||||
|
New string is used to compare the rules list.
|
||||||
|
"""
|
||||||
|
result = [(rule_name, str(rule)) for rule_name, rule in rules.items()]
|
||||||
|
return sorted(result, key=lambda rule: rule[0])
|
||||||
|
|
||||||
|
|
||||||
|
def _warning_for_deprecated_user_based_rules(rules):
|
||||||
|
"""Warning user based policy enforcement used in the rule but the rule
|
||||||
|
doesn't support it.
|
||||||
|
"""
|
||||||
|
for rule in rules:
|
||||||
|
# We will skip the warning for the resources which support user based
|
||||||
|
# policy enforcement.
|
||||||
|
if [resource for resource in USER_BASED_RESOURCES
|
||||||
|
if resource in rule[0]]:
|
||||||
|
continue
|
||||||
|
if 'user_id' in KEY_EXPR.findall(rule[1]):
|
||||||
|
LOG.warning(_LW("The user_id attribute isn't supported in the "
|
||||||
|
"rule '%s'. All the user_id based policy "
|
||||||
|
"enforcement will be removed in the "
|
||||||
|
"future."), rule[0])
|
||||||
|
|
||||||
|
|
||||||
|
def register_rules(enforcer):
|
||||||
|
"""Register default policy rules."""
|
||||||
|
rules = POLICIES.list_rules()
|
||||||
|
enforcer.register_defaults(rules)
|
||||||
|
|
||||||
|
|
||||||
|
def authorize(context, action, target, do_raise=True):
|
||||||
|
"""Verify that the action is valid on the target in this context.
|
||||||
|
|
||||||
|
:param context: monasca project context
|
||||||
|
:param action: String representing the action to be checked. This
|
||||||
|
should be colon separated for clarity.
|
||||||
|
:param target: Dictionary representing the object of the action for
|
||||||
|
object creation. This should be a dictionary representing
|
||||||
|
the location of the object e.g.
|
||||||
|
``{'project_id': 'context.project_id'}``
|
||||||
|
:param do_raise: if True (the default), raises PolicyNotAuthorized,
|
||||||
|
if False returns False
|
||||||
|
:type context: object
|
||||||
|
:type action: str
|
||||||
|
:type target: dict
|
||||||
|
:type do_raise: bool
|
||||||
|
:return: returns a non-False value (not necessarily True) if authorized,
|
||||||
|
and the False if not authorized and do_raise if False
|
||||||
|
|
||||||
|
:raises oslo_policy.policy.PolicyNotAuthorized: if verification fails
|
||||||
|
"""
|
||||||
|
init()
|
||||||
|
credentials = context.to_policy_values()
|
||||||
|
try:
|
||||||
|
result = _ENFORCER.authorize(action, target, credentials,
|
||||||
|
do_raise=do_raise, action=action)
|
||||||
|
return result
|
||||||
|
except policy.PolicyNotRegistered:
|
||||||
|
LOG.exception('Policy not registered')
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
LOG.debug('Policy check for %(action)s failed with credentials '
|
||||||
|
'%(credentials)s',
|
||||||
|
{'action': action, 'credentials': credentials})
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def check_is_admin(context):
|
||||||
|
"""Check if roles contains 'admin' role according to policy settings."""
|
||||||
|
init()
|
||||||
|
credentials = context.to_policy_values()
|
||||||
|
target = credentials
|
||||||
|
return _ENFORCER.authorize('admin_required', target, credentials)
|
||||||
|
|
||||||
|
|
||||||
|
def set_rules(rules, overwrite=True, use_conf=False): # pragma: no cover
|
||||||
|
"""Set rules based on the provided dict of rules.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Used in tests only.
|
||||||
|
|
||||||
|
:param rules: New rules to use. It should be an instance of dict
|
||||||
|
:param overwrite: Whether to overwrite current rules or update them
|
||||||
|
with the new rules.
|
||||||
|
:param use_conf: Whether to reload rules from config file.
|
||||||
|
"""
|
||||||
|
init(use_conf=False)
|
||||||
|
_ENFORCER.set_rules(rules, overwrite, use_conf)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_deprecated_policy(old_policy, new_policy, default_rule, context):
|
||||||
|
"""Check the rule of the deprecated policy action
|
||||||
|
|
||||||
|
If the current rule of the deprecated policy action is set to a non-default
|
||||||
|
value, then a warning message is logged stating that the new policy
|
||||||
|
action should be used to dictate permissions as the old policy action is
|
||||||
|
being deprecated.
|
||||||
|
|
||||||
|
:param old_policy: policy action that is being deprecated
|
||||||
|
:param new_policy: policy action that is replacing old_policy
|
||||||
|
:param default_rule: the old_policy action default rule value
|
||||||
|
:param context: the monasca context
|
||||||
|
"""
|
||||||
|
|
||||||
|
if _ENFORCER:
|
||||||
|
current_rule = str(_ENFORCER.rules[old_policy])
|
||||||
|
else:
|
||||||
|
current_rule = None
|
||||||
|
|
||||||
|
if current_rule != default_rule:
|
||||||
|
LOG.warning("Start using the new action '{0}'. The existing "
|
||||||
|
"action '{1}' is being deprecated and will be "
|
||||||
|
"removed in future release.".format(new_policy,
|
||||||
|
old_policy))
|
||||||
|
target = {'project_id': context.project_id,
|
||||||
|
'user_id': context.user_id}
|
||||||
|
|
||||||
|
return authorize(context=context, action=old_policy, target=target)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_rules():
|
||||||
|
if _ENFORCER:
|
||||||
|
return _ENFORCER.rules
|
||||||
|
|
||||||
|
|
||||||
|
def get_enforcer():
|
||||||
|
# This method is for use by oslopolicy CLI scripts. Those scripts need the
|
||||||
|
# 'output-file' and 'namespace' options, but having those in sys.argv means
|
||||||
|
# loading the project config options will fail as those are not expected to
|
||||||
|
# be present. So we pass in an arg list with those stripped out.
|
||||||
|
conf_args = []
|
||||||
|
# Start at 1 because cfg.CONF expects the equivalent of sys.argv[1:]
|
||||||
|
i = 1
|
||||||
|
while i < len(sys.argv):
|
||||||
|
if sys.argv[i].strip('-') in ['namespace', 'output-file']:
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
conf_args.append(sys.argv[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
cfg.CONF(conf_args, project='monasca')
|
||||||
|
init()
|
||||||
|
return _ENFORCER
|
||||||
|
|
||||||
|
|
||||||
|
@policy.register('is_admin')
|
||||||
|
class IsAdminCheck(policy.Check):
|
||||||
|
"""An explicit check for is_admin."""
|
||||||
|
|
||||||
|
def __init__(self, kind, match):
|
||||||
|
"""Initialize the check."""
|
||||||
|
|
||||||
|
self.expected = (match.lower() == 'true')
|
||||||
|
|
||||||
|
super(IsAdminCheck, self).__init__(kind, str(self.expected))
|
||||||
|
|
||||||
|
def __call__(self, target, creds, enforcer):
|
||||||
|
"""Determine whether is_admin matches the requested value."""
|
||||||
|
|
||||||
|
return creds['is_admin'] == self.expected
|
0
monasca_common/tests/policy/__init__.py
Normal file
0
monasca_common/tests/policy/__init__.py
Normal file
102
monasca_common/tests/policy/base.py
Normal file
102
monasca_common/tests/policy/base.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Copyright 2017 OP5 AB
|
||||||
|
# Copyright 2010 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Base classes for policy unit tests."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import fixtures
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_config import fixture as config_fixture
|
||||||
|
from oslo_policy import opts as policy_opts
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
from oslotest import base
|
||||||
|
|
||||||
|
from monasca_common.policy import policy_engine
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class FakePolicy(object):
|
||||||
|
def list_rules(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFixture(config_fixture.Config):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ConfigFixture, self).setUp()
|
||||||
|
CONF(args=[],
|
||||||
|
prog='common',
|
||||||
|
project='monasca',
|
||||||
|
version=0,
|
||||||
|
description='Testing monasca-common')
|
||||||
|
policy_opts.set_defaults(CONF)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestCase(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseTestCase, self).setUp()
|
||||||
|
self.useFixture(ConfigFixture(CONF))
|
||||||
|
self.useFixture(EmptyPolicyFixture())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def conf_override(**kw):
|
||||||
|
"""Override flag variables for a test."""
|
||||||
|
group = kw.pop('group', None)
|
||||||
|
for k, v in kw.items():
|
||||||
|
CONF.set_override(k, v, group)
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyPolicyFixture(fixtures.Fixture):
|
||||||
|
"""Override the policy with an empty policy file.
|
||||||
|
|
||||||
|
This overrides the policy with a completely fake and synthetic
|
||||||
|
policy file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
super(EmptyPolicyFixture, self).setUp()
|
||||||
|
self._prepare_policy()
|
||||||
|
policy_engine.POLICIES = FakePolicy()
|
||||||
|
policy_engine.reset()
|
||||||
|
policy_engine.init()
|
||||||
|
self.addCleanup(policy_engine.reset)
|
||||||
|
|
||||||
|
def _prepare_policy(self):
|
||||||
|
|
||||||
|
policy_dir = self.useFixture(fixtures.TempDir())
|
||||||
|
policy_file = os.path.join(policy_dir.path, 'policy.yaml')
|
||||||
|
|
||||||
|
policy_rules = jsonutils.loads('{}')
|
||||||
|
|
||||||
|
self.add_missing_default_rules(policy_rules)
|
||||||
|
|
||||||
|
with open(policy_file, 'w') as f:
|
||||||
|
jsonutils.dump(policy_rules, f)
|
||||||
|
|
||||||
|
BaseTestCase.conf_override(policy_file=policy_file,
|
||||||
|
group='oslo_policy')
|
||||||
|
BaseTestCase.conf_override(policy_dirs=[], group='oslo_policy')
|
||||||
|
|
||||||
|
def add_missing_default_rules(self, rules):
|
||||||
|
policies = FakePolicy()
|
||||||
|
|
||||||
|
for rule in policies.list_rules():
|
||||||
|
if rule.name not in rules:
|
||||||
|
rules[rule.name] = rule.check_str
|
275
monasca_common/tests/policy/test_policy.py
Normal file
275
monasca_common/tests/policy/test_policy.py
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
# Copyright 2017 OP5 AB
|
||||||
|
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# 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 mock
|
||||||
|
import requests_mock
|
||||||
|
|
||||||
|
from oslo_context import context
|
||||||
|
from oslo_policy import policy as os_policy
|
||||||
|
|
||||||
|
from monasca_common.policy import policy_engine
|
||||||
|
from monasca_common.tests.policy import base
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyFileTestCase(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(PolicyFileTestCase, self).setUp()
|
||||||
|
self.context = context.RequestContext(user='fake',
|
||||||
|
tenant='fake',
|
||||||
|
is_admin=False)
|
||||||
|
self.target = {}
|
||||||
|
|
||||||
|
def test_modified_policy_reloads(self):
|
||||||
|
tmp_file = \
|
||||||
|
self.create_tempfiles(files=[('policies', '{}')], ext='.yaml')[0]
|
||||||
|
base.BaseTestCase.conf_override(policy_file=tmp_file,
|
||||||
|
group='oslo_policy')
|
||||||
|
|
||||||
|
policy_engine.reset()
|
||||||
|
policy_engine.init()
|
||||||
|
|
||||||
|
action = 'example:test'
|
||||||
|
rule = os_policy.RuleDefault(action, '')
|
||||||
|
policy_engine._ENFORCER.register_defaults([rule])
|
||||||
|
|
||||||
|
with open(tmp_file, 'w') as policy_file:
|
||||||
|
policy_file.write('{"example:test": ""}')
|
||||||
|
policy_engine.authorize(self.context, action, self.target)
|
||||||
|
|
||||||
|
with open(tmp_file, 'w') as policy_file:
|
||||||
|
policy_file.write('{"example:test": "!"}')
|
||||||
|
policy_engine._ENFORCER.load_rules(True)
|
||||||
|
self.assertRaises(os_policy.PolicyNotAuthorized,
|
||||||
|
policy_engine.authorize,
|
||||||
|
self.context, action, self.target)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyTestCase(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(PolicyTestCase, self).setUp()
|
||||||
|
rules = [
|
||||||
|
os_policy.RuleDefault("true", "@"),
|
||||||
|
os_policy.RuleDefault("example:allowed", "@"),
|
||||||
|
os_policy.RuleDefault("example:denied", "!"),
|
||||||
|
os_policy.RuleDefault("old_action_not_default", "@"),
|
||||||
|
os_policy.RuleDefault("new_action", "@"),
|
||||||
|
os_policy.RuleDefault("old_action_default", "rule:admin_api"),
|
||||||
|
os_policy.RuleDefault("example:lowercase_admin",
|
||||||
|
"role:admin or role:sysadmin"),
|
||||||
|
os_policy.RuleDefault("example:uppercase_admin",
|
||||||
|
"role:ADMIN or role:sysadmin"),
|
||||||
|
os_policy.RuleDefault("example:get_http",
|
||||||
|
"http://www.example.com"),
|
||||||
|
os_policy.RuleDefault("example:my_file",
|
||||||
|
"role:compute_admin or "
|
||||||
|
"project_id:%(project_id)s"),
|
||||||
|
os_policy.RuleDefault("example:early_and_fail", "! and @"),
|
||||||
|
os_policy.RuleDefault("example:early_or_success", "@ or !"),
|
||||||
|
]
|
||||||
|
policy_engine.reset()
|
||||||
|
policy_engine.init()
|
||||||
|
|
||||||
|
self.context = context.RequestContext(user='fake',
|
||||||
|
tenant='fake',
|
||||||
|
is_admin=False)
|
||||||
|
policy_engine._ENFORCER.register_defaults(rules)
|
||||||
|
self.target = {}
|
||||||
|
|
||||||
|
def test_authorize_nonexistent_action_throws(self):
|
||||||
|
|
||||||
|
action = 'example:noexists'
|
||||||
|
self.assertRaises(os_policy.PolicyNotRegistered, policy_engine.authorize,
|
||||||
|
self.context, action, self.target)
|
||||||
|
|
||||||
|
def test_authorize_bad_action_throws(self):
|
||||||
|
action = 'example:denied'
|
||||||
|
self.assertRaises(os_policy.PolicyNotAuthorized, policy_engine.authorize,
|
||||||
|
self.context, action, self.target)
|
||||||
|
|
||||||
|
def test_authorize_bad_action_noraise(self):
|
||||||
|
action = "example:denied"
|
||||||
|
result = policy_engine.authorize(self.context, action, self.target, False)
|
||||||
|
self.assertFalse(result)
|
||||||
|
|
||||||
|
def test_authorize_good_action(self):
|
||||||
|
action = "example:allowed"
|
||||||
|
result = policy_engine.authorize(self.context, action, self.target)
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_authorize_http_true(self, req_mock):
|
||||||
|
req_mock.post('http://www.example.com/',
|
||||||
|
text='True')
|
||||||
|
action = "example:get_http"
|
||||||
|
target = {}
|
||||||
|
result = policy_engine.authorize(self.context, action, target)
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_authorize_http_false(self, req_mock):
|
||||||
|
req_mock.post('http://www.example.com/',
|
||||||
|
text='False')
|
||||||
|
action = "example:get_http"
|
||||||
|
target = {}
|
||||||
|
self.assertRaises(os_policy.PolicyNotAuthorized, policy_engine.authorize,
|
||||||
|
self.context, action, target)
|
||||||
|
|
||||||
|
def test_templatized_authorization(self):
|
||||||
|
target_mine = {'project_id': 'fake'}
|
||||||
|
target_not_mine = {'project_id': 'another'}
|
||||||
|
action = "example:my_file"
|
||||||
|
policy_engine.authorize(self.context, action, target_mine)
|
||||||
|
self.assertRaises(os_policy.PolicyNotAuthorized, policy_engine.authorize,
|
||||||
|
self.context, action, target_not_mine)
|
||||||
|
|
||||||
|
def test_early_AND_authorization(self):
|
||||||
|
action = "example:early_and_fail"
|
||||||
|
self.assertRaises(os_policy.PolicyNotAuthorized, policy_engine.authorize,
|
||||||
|
self.context, action, self.target)
|
||||||
|
|
||||||
|
def test_early_OR_authorization(self):
|
||||||
|
action = "example:early_or_success"
|
||||||
|
policy_engine.authorize(self.context, action, self.target)
|
||||||
|
|
||||||
|
def test_ignore_case_role_check(self):
|
||||||
|
lowercase_action = "example:lowercase_admin"
|
||||||
|
uppercase_action = "example:uppercase_admin"
|
||||||
|
# NOTE(dprince) we mix case in the Admin role here to ensure
|
||||||
|
# case is ignored
|
||||||
|
admin_context = context.RequestContext('admin',
|
||||||
|
'fake',
|
||||||
|
roles=['AdMiN'])
|
||||||
|
policy_engine.authorize(admin_context, lowercase_action, self.target)
|
||||||
|
policy_engine.authorize(admin_context, uppercase_action, self.target)
|
||||||
|
|
||||||
|
@mock.patch.object(policy_engine.LOG, 'warning')
|
||||||
|
def test_warning_when_deprecated_user_based_rule_used(self, mock_warning):
|
||||||
|
policy_engine._warning_for_deprecated_user_based_rules(
|
||||||
|
[("os_compute_api:servers:index",
|
||||||
|
"project_id:%(project_id)s or user_id:%(user_id)s")])
|
||||||
|
mock_warning.assert_called_once_with(
|
||||||
|
u"The user_id attribute isn't supported in the rule "
|
||||||
|
"'%s'. All the user_id based policy enforcement will be removed "
|
||||||
|
"in the future.", "os_compute_api:servers:index")
|
||||||
|
|
||||||
|
@mock.patch.object(policy_engine.LOG, 'warning')
|
||||||
|
def test_no_warning_for_user_based_resource(self, mock_warning):
|
||||||
|
policy_engine._warning_for_deprecated_user_based_rules(
|
||||||
|
[("os_compute_api:os-keypairs:index",
|
||||||
|
"user_id:%(user_id)s")])
|
||||||
|
mock_warning.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch.object(policy_engine.LOG, 'warning')
|
||||||
|
def test_no_warning_for_no_user_based_rule(self, mock_warning):
|
||||||
|
policy_engine._warning_for_deprecated_user_based_rules(
|
||||||
|
[("os_compute_api:servers:index",
|
||||||
|
"project_id:%(project_id)s")])
|
||||||
|
mock_warning.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch.object(policy_engine.LOG, 'warning')
|
||||||
|
def test_verify_deprecated_policy_using_old_action(self, mock_warning):
|
||||||
|
policy_engine._ENFORCER.load_rules(True)
|
||||||
|
old_policy = "old_action_not_default"
|
||||||
|
new_policy = "new_action"
|
||||||
|
default_rule = "rule:admin_api"
|
||||||
|
|
||||||
|
using_old_action = policy_engine.verify_deprecated_policy(
|
||||||
|
old_policy, new_policy, default_rule, self.context)
|
||||||
|
|
||||||
|
mock_warning.assert_called_once_with(
|
||||||
|
"Start using the new action '{0}'. The existing action '{1}' is "
|
||||||
|
"being deprecated and will be removed in "
|
||||||
|
"future release.".format(new_policy, old_policy))
|
||||||
|
self.assertTrue(using_old_action)
|
||||||
|
|
||||||
|
def test_verify_deprecated_policy_using_new_action(self):
|
||||||
|
policy_engine._ENFORCER.load_rules(True)
|
||||||
|
old_policy = "old_action_default"
|
||||||
|
new_policy = "new_action"
|
||||||
|
default_rule = "rule:admin_api"
|
||||||
|
|
||||||
|
using_old_action = policy_engine.verify_deprecated_policy(
|
||||||
|
old_policy, new_policy, default_rule, self.context)
|
||||||
|
|
||||||
|
self.assertFalse(using_old_action)
|
||||||
|
|
||||||
|
|
||||||
|
class IsAdminCheckTestCase(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(IsAdminCheckTestCase, self).setUp()
|
||||||
|
policy_engine.init()
|
||||||
|
|
||||||
|
def test_init_true(self):
|
||||||
|
check = policy_engine.IsAdminCheck('is_admin', 'True')
|
||||||
|
|
||||||
|
self.assertEqual(check.kind, 'is_admin')
|
||||||
|
self.assertEqual(check.match, 'True')
|
||||||
|
self.assertTrue(check.expected)
|
||||||
|
|
||||||
|
def test_init_false(self):
|
||||||
|
check = policy_engine.IsAdminCheck('is_admin', 'nottrue')
|
||||||
|
|
||||||
|
self.assertEqual(check.kind, 'is_admin')
|
||||||
|
self.assertEqual(check.match, 'False')
|
||||||
|
self.assertFalse(check.expected)
|
||||||
|
|
||||||
|
def test_call_true(self):
|
||||||
|
check = policy_engine.IsAdminCheck('is_admin', 'True')
|
||||||
|
|
||||||
|
self.assertTrue(check('target', dict(is_admin=True),
|
||||||
|
policy_engine._ENFORCER))
|
||||||
|
self.assertFalse(check('target', dict(is_admin=False),
|
||||||
|
policy_engine._ENFORCER))
|
||||||
|
|
||||||
|
def test_call_false(self):
|
||||||
|
check = policy_engine.IsAdminCheck('is_admin', 'False')
|
||||||
|
|
||||||
|
self.assertFalse(check('target', dict(is_admin=True),
|
||||||
|
policy_engine._ENFORCER))
|
||||||
|
self.assertTrue(check('target', dict(is_admin=False),
|
||||||
|
policy_engine._ENFORCER))
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRolePolicyTestCase(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(AdminRolePolicyTestCase, self).setUp()
|
||||||
|
self.noadmin_context = context.RequestContext('fake', 'fake',
|
||||||
|
roles=['member'])
|
||||||
|
self.admin_context = context.RequestContext('fake', 'fake',
|
||||||
|
roles=['admin'])
|
||||||
|
|
||||||
|
admin_rule = [
|
||||||
|
os_policy.RuleDefault('example.admin', 'role:admin'),
|
||||||
|
]
|
||||||
|
policy_engine.reset()
|
||||||
|
policy_engine.init(policy_file=None)
|
||||||
|
policy_engine._ENFORCER.register_defaults(admin_rule)
|
||||||
|
policy_engine._ENFORCER.load_rules(True)
|
||||||
|
self.target = {}
|
||||||
|
|
||||||
|
def test_authorize_admin_actions_with_admin_context(self):
|
||||||
|
for action in policy_engine.get_rules().keys():
|
||||||
|
policy_engine.authorize(self.admin_context, action, self.target)
|
||||||
|
|
||||||
|
def test_authorize_admin_actions_with_nonadmin_context_throws(self):
|
||||||
|
"""Check if non-admin context passed to admin actions throws
|
||||||
|
Policy not authorized exception
|
||||||
|
"""
|
||||||
|
for action in policy_engine.get_rules().keys():
|
||||||
|
self.assertRaises(os_policy.PolicyNotAuthorized,
|
||||||
|
policy_engine.authorize,
|
||||||
|
self.noadmin_context, action, self.target)
|
@ -6,6 +6,7 @@ kazoo>=2.2 # Apache-2.0
|
|||||||
pykafka>=2.5.0 # Apache 2.0 License
|
pykafka>=2.5.0 # Apache 2.0 License
|
||||||
PyMySQL>=0.7.6 # MIT License
|
PyMySQL>=0.7.6 # MIT License
|
||||||
oslo.config>=5.1.0 # Apache-2.0
|
oslo.config>=5.1.0 # Apache-2.0
|
||||||
|
oslo.policy>=1.30.0 # Apache-2.0
|
||||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
pyparsing>=2.1.0 # MIT
|
pyparsing>=2.1.0 # MIT
|
||||||
ujson>=1.35 # BSD
|
ujson>=1.35 # BSD
|
||||||
|
@ -10,8 +10,10 @@ fixtures>=3.0.0 # Apache-2.0/BSD
|
|||||||
httplib2>=0.9.1 # MIT
|
httplib2>=0.9.1 # MIT
|
||||||
mock>=2.0.0 # BSD
|
mock>=2.0.0 # BSD
|
||||||
mox>=0.5.3 # Apache-2.0
|
mox>=0.5.3 # Apache-2.0
|
||||||
|
oslo.context>=2.19.2 # Apache-2.0
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
os-testr>=1.0.0 # Apache-2.0
|
os-testr>=1.0.0 # Apache-2.0
|
||||||
|
requests-mock>=1.1.0 # Apache-2.0
|
||||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||||
testscenarios>=0.4 # Apache-2.0/BSD
|
testscenarios>=0.4 # Apache-2.0/BSD
|
||||||
testtools>=2.2.0 # MIT
|
testtools>=2.2.0 # MIT
|
||||||
|
Loading…
x
Reference in New Issue
Block a user