Merge "Add default policy in code for the plan resource"
This commit is contained in:
commit
3f3df9b761
4
etc/README-policy.yaml.txt
Normal file
4
etc/README-policy.yaml.txt
Normal file
@ -0,0 +1,4 @@
|
||||
To generate the sample policy.yaml file, run the following command from the top
|
||||
level of the karbor directory:
|
||||
|
||||
tox -egenpolicy
|
3
etc/karbor-policy-generator.conf
Normal file
3
etc/karbor-policy-generator.conf
Normal file
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/policy.yaml.sample
|
||||
namespace = karbor
|
@ -1,16 +1,4 @@
|
||||
{
|
||||
"context_is_admin": "role:admin",
|
||||
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
|
||||
"default": "rule:admin_or_owner",
|
||||
|
||||
"admin_api": "is_admin:True",
|
||||
|
||||
"plan:create": "rule:admin_or_owner",
|
||||
"plan:update": "rule:admin_or_owner",
|
||||
"plan:delete": "rule:admin_or_owner",
|
||||
"plan:get": "rule:admin_or_owner",
|
||||
"plan:get_all": "rule:admin_or_owner",
|
||||
|
||||
"restore:create": "rule:admin_or_owner",
|
||||
"restore:update": "rule:admin_or_owner",
|
||||
"restore:get": "rule:admin_or_owner",
|
||||
|
@ -19,7 +19,6 @@ from oslo_utils import uuidutils
|
||||
|
||||
from webob import exc
|
||||
|
||||
import karbor
|
||||
from karbor.api import common
|
||||
from karbor.api.openstack import wsgi
|
||||
from karbor.common import constants
|
||||
@ -28,7 +27,7 @@ from karbor.i18n import _
|
||||
|
||||
from karbor import objects
|
||||
from karbor.objects import base as objects_base
|
||||
import karbor.policy
|
||||
from karbor.policies import plans as plan_policy
|
||||
from karbor.services.operationengine import api as operationengine_api
|
||||
from karbor.services.protection import api as protection_api
|
||||
from karbor import utils
|
||||
@ -49,23 +48,6 @@ CONF.register_opt(query_plan_filters_opt)
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_policy(context, action, target_obj=None):
|
||||
target = {
|
||||
'project_id': context.project_id,
|
||||
'user_id': context.user_id,
|
||||
}
|
||||
|
||||
if isinstance(target_obj, objects_base.KarborObject):
|
||||
# Turn object into dict so target.update can work
|
||||
target.update(
|
||||
target_obj.obj_to_primitive() or {})
|
||||
else:
|
||||
target.update(target_obj or {})
|
||||
|
||||
_action = 'plan:%s' % action
|
||||
karbor.policy.enforce(context, _action, target)
|
||||
|
||||
|
||||
class PlanViewBuilder(common.ViewBuilder):
|
||||
"""Model a server API response as a python dictionary."""
|
||||
|
||||
@ -170,7 +152,7 @@ class PlansController(wsgi.Controller):
|
||||
except exception.PlanNotFound as error:
|
||||
raise exc.HTTPNotFound(explanation=error.msg)
|
||||
|
||||
check_policy(context, 'delete', plan)
|
||||
context.can(plan_policy.DELETE_POLICY, target_obj=plan)
|
||||
plan.destroy()
|
||||
LOG.info("Delete plan request issued successfully.",
|
||||
resource={'id': plan.id})
|
||||
@ -205,7 +187,7 @@ class PlansController(wsgi.Controller):
|
||||
|
||||
def _get_all(self, context, marker=None, limit=None, sort_keys=None,
|
||||
sort_dirs=None, filters=None, offset=None):
|
||||
check_policy(context, 'get_all')
|
||||
context.can(plan_policy.GET_ALL_POLICY)
|
||||
|
||||
if filters is None:
|
||||
filters = {}
|
||||
@ -253,7 +235,7 @@ class PlansController(wsgi.Controller):
|
||||
|
||||
LOG.debug('Create plan request body: %s', body)
|
||||
context = req.environ['karbor.context']
|
||||
check_policy(context, 'create')
|
||||
context.can(plan_policy.CREATE_POLICY)
|
||||
plan = body['plan']
|
||||
LOG.debug('Create plan request plan: %s', plan)
|
||||
|
||||
@ -347,8 +329,7 @@ class PlansController(wsgi.Controller):
|
||||
plan = self._plan_get(context, id)
|
||||
except exception.PlanNotFound as error:
|
||||
raise exc.HTTPNotFound(explanation=error.msg)
|
||||
|
||||
check_policy(context, 'update', plan)
|
||||
context.can(plan_policy.UPDATE_POLICY, target_obj=plan)
|
||||
self._plan_update(context, plan, update_dict)
|
||||
|
||||
plan.update(update_dict)
|
||||
@ -363,7 +344,7 @@ class PlansController(wsgi.Controller):
|
||||
|
||||
plan = objects.Plan.get_by_id(context, plan_id)
|
||||
try:
|
||||
check_policy(context, 'get', plan)
|
||||
context.can(plan_policy.GET_POLICY, target_obj=plan)
|
||||
except exception.PolicyNotAuthorized:
|
||||
# raise PlanNotFound instead to make sure karbor behaves
|
||||
# as it used to
|
||||
|
@ -21,7 +21,9 @@ from oslo_context import context
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
|
||||
from karbor import exception
|
||||
from karbor.i18n import _
|
||||
from karbor.objects import base as objects_base
|
||||
from karbor import policy
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -85,7 +87,7 @@ class RequestContext(context.RequestContext):
|
||||
# when policy.check_is_admin invokes request logging
|
||||
# to make it loggable.
|
||||
if self.is_admin is None:
|
||||
self.is_admin = policy.check_is_admin(self.roles, self)
|
||||
self.is_admin = policy.check_is_admin(self)
|
||||
elif self.is_admin and 'admin' not in self.roles:
|
||||
self.roles.append('admin')
|
||||
|
||||
@ -143,6 +145,42 @@ class RequestContext(context.RequestContext):
|
||||
kwargs = {k: values[k] for k in values if k in allowed_keys}
|
||||
return cls(**kwargs)
|
||||
|
||||
def can(self, action, target_obj=None, fatal=True):
|
||||
"""Verifies that the given action is valid on the target in this context.
|
||||
|
||||
:param action: string representing the action to be checked.
|
||||
: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}``.
|
||||
If None, then this default target will be considered:
|
||||
{'project_id': self.project_id, 'user_id': self.user_id}
|
||||
:param: target_obj: dictionary representing the object which will be
|
||||
used to update target.
|
||||
:param fatal: if False, will return False when an
|
||||
exception.NotAuthorized occurs.
|
||||
|
||||
:raises nova.exception.Forbidden: if verification fails and fatal is
|
||||
True.
|
||||
|
||||
:return: returns a non-False value (not necessarily "True") if
|
||||
authorized and False if not authorized and fatal is False.
|
||||
"""
|
||||
target = {'project_id': self.project_id,
|
||||
'user_id': self.user_id}
|
||||
if isinstance(target_obj, objects_base.KarborObject):
|
||||
# Turn object into dict so target.update can work
|
||||
target.update(
|
||||
target_obj.obj_to_primitive()['karbor_object.data'] or {})
|
||||
else:
|
||||
target.update(target_obj or {})
|
||||
|
||||
try:
|
||||
return policy.authorize(self, action, target)
|
||||
except exception.NotAuthorized:
|
||||
if fatal:
|
||||
raise
|
||||
return False
|
||||
|
||||
def to_policy_values(self):
|
||||
policy = super(RequestContext, self).to_policy_values()
|
||||
|
||||
|
25
karbor/policies/__init__.py
Normal file
25
karbor/policies/__init__.py
Normal file
@ -0,0 +1,25 @@
|
||||
# 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 itertools
|
||||
|
||||
from karbor.policies import base
|
||||
from karbor.policies import plans
|
||||
|
||||
|
||||
def list_rules():
|
||||
return itertools.chain(
|
||||
base.list_rules(),
|
||||
plans.list_rules()
|
||||
)
|
34
karbor/policies/base.py
Normal file
34
karbor/policies/base.py
Normal file
@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
|
||||
from oslo_policy import policy
|
||||
|
||||
RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
|
||||
RULE_ADMIN_API = 'rule:admin_api'
|
||||
|
||||
rules = [
|
||||
policy.RuleDefault('context_is_admin', 'role:admin'),
|
||||
policy.RuleDefault('admin_or_owner',
|
||||
'is_admin:True or (role:admin and '
|
||||
'is_admin_project:True) or project_id:%(project_id)s'),
|
||||
policy.RuleDefault('default',
|
||||
'rule:admin_or_owner'),
|
||||
policy.RuleDefault('admin_api',
|
||||
'is_admin:True or (role:admin and '
|
||||
'is_admin_project:True)'),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return rules
|
82
karbor/policies/plans.py
Normal file
82
karbor/policies/plans.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
|
||||
from oslo_policy import policy
|
||||
|
||||
from karbor.policies import base
|
||||
|
||||
|
||||
CREATE_POLICY = 'plan:create'
|
||||
UPDATE_POLICY = 'plan:update'
|
||||
DELETE_POLICY = 'plan:delete'
|
||||
GET_POLICY = 'plan:get'
|
||||
GET_ALL_POLICY = 'plan:get_all'
|
||||
|
||||
plans_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name=CREATE_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="""Create a plan.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/plans'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=UPDATE_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="""Update a plan.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/plans/{plan_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=DELETE_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="""Delete a plan.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'path': '/plans/{plan_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=GET_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="""Get a plan.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/plans/{plan_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=GET_ALL_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="""Get plans.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/plans'
|
||||
}
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return plans_policies
|
146
karbor/policy.py
146
karbor/policy.py
@ -13,25 +13,54 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Policy Engine For karbor"""
|
||||
"""Policy Engine For Karbor"""
|
||||
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_policy import opts as policy_opts
|
||||
from oslo_policy import policy
|
||||
from oslo_utils import excutils
|
||||
|
||||
from karbor import exception
|
||||
from karbor import policies
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
policy_opts.set_defaults(cfg.CONF, 'policy.json')
|
||||
|
||||
_ENFORCER = None
|
||||
|
||||
|
||||
def init():
|
||||
def reset():
|
||||
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
|
||||
if not _ENFORCER:
|
||||
_ENFORCER = policy.Enforcer(CONF)
|
||||
_ENFORCER = policy.Enforcer(CONF,
|
||||
policy_file=policy_file,
|
||||
rules=rules,
|
||||
default_rule=default_rule,
|
||||
use_conf=use_conf)
|
||||
register_rules(_ENFORCER)
|
||||
_ENFORCER.load_rules()
|
||||
|
||||
|
||||
def enforce_action(context, action):
|
||||
@ -55,9 +84,9 @@ def enforce(context, action, target):
|
||||
``compute:attach_volume``,
|
||||
``volume:attach_volume``
|
||||
|
||||
:param target: dictionary representing the target of the action
|
||||
for target creation this should be a dictionary representing the
|
||||
location of the target e.g. ``{'project_id': context.project_id}``
|
||||
: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}``
|
||||
|
||||
:raises PolicyNotAuthorized: if verification fails.
|
||||
|
||||
@ -72,19 +101,100 @@ def enforce(context, action, target):
|
||||
action=action)
|
||||
|
||||
|
||||
def check_is_admin(roles, context=None):
|
||||
def set_rules(rules, overwrite=True, use_conf=False):
|
||||
"""Set rules based on the provided dict of rules.
|
||||
|
||||
: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 get_rules():
|
||||
if _ENFORCER:
|
||||
return _ENFORCER.rules
|
||||
|
||||
|
||||
def register_rules(enforcer):
|
||||
enforcer.register_defaults(policies.list_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 Karbor 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='karbor')
|
||||
init()
|
||||
return _ENFORCER
|
||||
|
||||
|
||||
def authorize(context, action, target, do_raise=True, exc=None):
|
||||
"""Verifies that the action is valid on the target in this context.
|
||||
|
||||
:param context: karbor context
|
||||
:param action: string representing the action to be checked
|
||||
this should be colon separated for clarity.
|
||||
i.e. ``compute:create_instance``,
|
||||
``plan:create``,
|
||||
``plan:get``
|
||||
: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
|
||||
:param exc: Class of the exception to raise if the check fails.
|
||||
Any remaining arguments passed to :meth:`authorize` (both
|
||||
positional and keyword arguments) will be passed to
|
||||
the exception class. If not specified,
|
||||
:class:`PolicyNotAuthorized` will be used.
|
||||
|
||||
:raises karbor.exception.PolicyNotAuthorized: if verification fails
|
||||
and do_raise is True. Or if 'exc' is specified it will raise an
|
||||
exception of that type.
|
||||
|
||||
:return: returns a non-False value (not necessarily "True") if
|
||||
authorized, and the exact value False if not authorized and
|
||||
do_raise is False.
|
||||
"""
|
||||
init()
|
||||
credentials = context.to_policy_values()
|
||||
if not exc:
|
||||
exc = exception.PolicyNotAuthorized
|
||||
try:
|
||||
result = _ENFORCER.authorize(action, target, credentials,
|
||||
do_raise=do_raise, exc=exc, action=action)
|
||||
except policy.PolicyNotRegistered:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception('Policy not registered')
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.debug('Policy check for %(action)s failed with credentials '
|
||||
'%(credentials)s',
|
||||
{'action': action, 'credentials': credentials})
|
||||
return result
|
||||
|
||||
|
||||
def check_is_admin(context):
|
||||
"""Whether or not user is admin according to policy setting.
|
||||
|
||||
"""
|
||||
init()
|
||||
|
||||
# include project_id on target to avoid KeyError if context_is_admin
|
||||
# policy definition is missing, and default admin_or_owner rule
|
||||
# attempts to apply.
|
||||
target = {'project_id': ''}
|
||||
if context is None:
|
||||
credentials = {'roles': roles}
|
||||
else:
|
||||
credentials = context.to_dict()
|
||||
|
||||
return _ENFORCER.enforce('context_is_admin', target, credentials)
|
||||
# the target is user-self
|
||||
credentials = context.to_policy_values()
|
||||
target = credentials
|
||||
return _ENFORCER.authorize('context_is_admin', target, credentials)
|
||||
|
@ -14,6 +14,7 @@ import logging
|
||||
import os
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_messaging import conffixture as messaging_conffixture
|
||||
from oslo_utils import timeutils
|
||||
@ -73,6 +74,7 @@ class TestCase(base.BaseTestCase):
|
||||
self.messaging_conf.transport_driver = 'fake'
|
||||
self.messaging_conf.response_timeout = 15
|
||||
self.useFixture(self.messaging_conf)
|
||||
|
||||
rpc.init(CONF)
|
||||
|
||||
conf_fixture.set_defaults(CONF)
|
||||
@ -112,3 +114,17 @@ class TestCase(base.BaseTestCase):
|
||||
"""Override CONF variables for a test."""
|
||||
for k, v in kw.items():
|
||||
self.override_config(k, v)
|
||||
|
||||
def mock_object(self, obj, attr_name, new_attr=None, **kwargs):
|
||||
"""Use python mock to mock an object attribute
|
||||
|
||||
Mocks the specified objects attribute with the given value.
|
||||
Automatically performs 'addCleanup' for the mock.
|
||||
|
||||
"""
|
||||
if not new_attr:
|
||||
new_attr = mock.Mock()
|
||||
patcher = mock.patch.object(obj, attr_name, new_attr, **kwargs)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
return new_attr
|
||||
|
@ -38,6 +38,8 @@ class PlanApiTest(base.TestCase):
|
||||
super(PlanApiTest, self).setUp()
|
||||
self.controller = plans.PlansController()
|
||||
self.ctxt = context.RequestContext('demo', 'fakeproject', True)
|
||||
self.mock_policy_check = self.mock_object(
|
||||
context.RequestContext, 'can')
|
||||
|
||||
@mock.patch(
|
||||
'karbor.services.protection.rpcapi.ProtectionAPI.show_provider')
|
||||
@ -50,6 +52,7 @@ class PlanApiTest(base.TestCase):
|
||||
mock_provider.return_value = fakes.PROVIDER_OS
|
||||
self.controller.create(req, body)
|
||||
self.assertTrue(mock_plan_create.called)
|
||||
self.assertTrue(self.mock_policy_check.called)
|
||||
|
||||
def test_plan_create_InvalidBody(self):
|
||||
plan = self._plan_in_request_body()
|
||||
@ -206,12 +209,10 @@ class PlanApiTest(base.TestCase):
|
||||
exc.HTTPBadRequest, self.controller.delete,
|
||||
req, "1")
|
||||
|
||||
@mock.patch(
|
||||
'karbor.api.v1.plans.check_policy')
|
||||
@mock.patch(
|
||||
'karbor.api.v1.plans.PlansController._plan_get')
|
||||
def test_plan_update_InvalidStatus(
|
||||
self, mock_plan_get, mock_check_policy):
|
||||
self, mock_plan_get):
|
||||
plan = self._plan_in_request_body(
|
||||
name=DEFAULT_NAME,
|
||||
description=DEFAULT_DESCRIPTION,
|
||||
|
@ -187,9 +187,11 @@ class ScheduledOperationApiTest(base.TestCase):
|
||||
req = fakes.HTTPRequest.blank('/v1/triggers')
|
||||
return controller.create(req, create_trigger_param)
|
||||
|
||||
@mock.patch(
|
||||
'karbor.context.RequestContext.can')
|
||||
@mock.patch(
|
||||
'karbor.services.protection.rpcapi.ProtectionAPI.show_provider')
|
||||
def _create_plan(self, provider_id, mock_provider):
|
||||
def _create_plan(self, provider_id, mock_provider, mock_policy):
|
||||
create_plan_param = {
|
||||
'plan': {
|
||||
'name': '123',
|
||||
|
131
karbor/tests/unit/test_policy.py
Normal file
131
karbor/tests/unit/test_policy.py
Normal file
@ -0,0 +1,131 @@
|
||||
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
|
||||
# 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 os.path
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as config_fixture
|
||||
from oslo_policy import policy as oslo_policy
|
||||
|
||||
from karbor import context
|
||||
from karbor import exception
|
||||
from karbor.tests import base
|
||||
from karbor import utils
|
||||
|
||||
from karbor import policy
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class PolicyFileTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PolicyFileTestCase, self).setUp()
|
||||
self.context = context.get_admin_context()
|
||||
self.target = {}
|
||||
self.fixture = self.useFixture(config_fixture.Config(CONF))
|
||||
self.addCleanup(policy.reset)
|
||||
|
||||
def test_modified_policy_reloads(self):
|
||||
with utils.tempdir() as tmpdir:
|
||||
tmpfilename = os.path.join(tmpdir, 'policy')
|
||||
self.fixture.config(policy_file=tmpfilename, group='oslo_policy')
|
||||
policy.reset()
|
||||
policy.init()
|
||||
rule = oslo_policy.RuleDefault('example:test', "")
|
||||
policy._ENFORCER.register_defaults([rule])
|
||||
|
||||
action = "example:test"
|
||||
with open(tmpfilename, "w") as policyfile:
|
||||
policyfile.write('{"example:test": ""}')
|
||||
policy.authorize(self.context, action, self.target)
|
||||
with open(tmpfilename, "w") as policyfile:
|
||||
policyfile.write('{"example:test": "!"}')
|
||||
policy._ENFORCER.load_rules(True)
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
policy.authorize,
|
||||
self.context, action, self.target)
|
||||
|
||||
|
||||
class PolicyTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PolicyTestCase, self).setUp()
|
||||
rules = [
|
||||
oslo_policy.RuleDefault("true", '@'),
|
||||
oslo_policy.RuleDefault("test:allowed", '@'),
|
||||
oslo_policy.RuleDefault("test:denied", "!"),
|
||||
oslo_policy.RuleDefault("test:my_file",
|
||||
"role:compute_admin or "
|
||||
"project_id:%(project_id)s"),
|
||||
oslo_policy.RuleDefault("test:early_and_fail", "! and @"),
|
||||
oslo_policy.RuleDefault("test:early_or_success", "@ or !"),
|
||||
oslo_policy.RuleDefault("test:lowercase_admin",
|
||||
"role:admin"),
|
||||
oslo_policy.RuleDefault("test:uppercase_admin",
|
||||
"role:ADMIN"),
|
||||
]
|
||||
policy.reset()
|
||||
policy.init()
|
||||
# before a policy rule can be used, its default has to be registered.
|
||||
policy._ENFORCER.register_defaults(rules)
|
||||
self.context = context.RequestContext('fake', 'fake', roles=['member'])
|
||||
self.target = {}
|
||||
self.addCleanup(policy.reset)
|
||||
|
||||
def test_authorize_nonexistent_action_throws(self):
|
||||
action = "test:noexist"
|
||||
self.assertRaises(oslo_policy.PolicyNotRegistered, policy.authorize,
|
||||
self.context, action, self.target)
|
||||
|
||||
def test_authorize_bad_action_throws(self):
|
||||
action = "test:denied"
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.authorize,
|
||||
self.context, action, self.target)
|
||||
|
||||
def test_authorize_bad_action_noraise(self):
|
||||
action = "test:denied"
|
||||
result = policy.authorize(self.context, action, self.target, False)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_authorize_good_action(self):
|
||||
action = "test:allowed"
|
||||
result = policy.authorize(self.context, action, self.target)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_templatized_authorization(self):
|
||||
target_mine = {'project_id': 'fake'}
|
||||
target_not_mine = {'project_id': 'another'}
|
||||
action = "test:my_file"
|
||||
policy.authorize(self.context, action, target_mine)
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.authorize,
|
||||
self.context, action, target_not_mine)
|
||||
|
||||
def test_early_AND_authorization(self):
|
||||
action = "test:early_and_fail"
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.authorize,
|
||||
self.context, action, self.target)
|
||||
|
||||
def test_early_OR_authorization(self):
|
||||
action = "test:early_or_success"
|
||||
policy.authorize(self.context, action, self.target)
|
||||
|
||||
def test_ignore_case_role_check(self):
|
||||
lowercase_action = "test:lowercase_admin"
|
||||
uppercase_action = "test:uppercase_admin"
|
||||
admin_context = context.RequestContext('admin',
|
||||
'fake',
|
||||
roles=['AdMiN'])
|
||||
policy.authorize(admin_context, lowercase_action, self.target)
|
||||
policy.authorize(admin_context, uppercase_action, self.target)
|
@ -12,7 +12,11 @@
|
||||
|
||||
"""Utilities and helper functions."""
|
||||
import ast
|
||||
import contextlib
|
||||
import os
|
||||
import shutil
|
||||
import six
|
||||
import tempfile
|
||||
import webob.exc
|
||||
|
||||
from keystoneclient import discover as ks_discover
|
||||
@ -175,3 +179,16 @@ def walk_class_hierarchy(clazz, encountered=None):
|
||||
for subsubclass in walk_class_hierarchy(subclass, encountered):
|
||||
yield subsubclass
|
||||
yield subclass
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def tempdir(**kwargs):
|
||||
tmpdir = tempfile.mkdtemp(**kwargs)
|
||||
try:
|
||||
yield tmpdir
|
||||
finally:
|
||||
try:
|
||||
shutil.rmtree(tmpdir)
|
||||
except OSError as e:
|
||||
LOG.debug('Could not remove tmpdir: %s',
|
||||
six.text_type(e))
|
||||
|
@ -30,6 +30,14 @@ console_scripts =
|
||||
karbor-protection = karbor.cmd.protection:main
|
||||
oslo.config.opts =
|
||||
karbor.common.opts = karbor.common.opts:list_opts
|
||||
oslo.policy.enforcer =
|
||||
karbor = karbor.policy:get_enforcer
|
||||
oslo.policy.policies =
|
||||
# The sample policies will be ordered by entry point and then by list
|
||||
# returned from that entry point. If more control is desired split out each
|
||||
# list_rules method into a separate entry point rather than using the
|
||||
# aggregate method.
|
||||
karbor = karbor.policies:list_rules
|
||||
wsgi_scripts =
|
||||
karbor-wsgi = karbor.wsgi.wsgi:initialize_application
|
||||
karbor.database.migration_backend =
|
||||
|
3
tox.ini
3
tox.ini
@ -60,6 +60,9 @@ commands = sphinx-build -a -E -d releasenotes/build/doctrees -b html releasenote
|
||||
[testenv:genconfig]
|
||||
commands = oslo-config-generator --config-file etc/oslo-config-generator/karbor.conf
|
||||
|
||||
[testenv:genpolicy]
|
||||
commands = oslopolicy-sample-generator --config-file=etc/karbor-policy-generator.conf
|
||||
|
||||
[flake8]
|
||||
show-source = True
|
||||
ignore =
|
||||
|
Loading…
Reference in New Issue
Block a user