Merge "[policy in code] Add support for attachment resource"
This commit is contained in:
commit
e912ced6e1
@ -25,7 +25,9 @@ from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.objects import base as objects_base
|
||||
from cinder import policy
|
||||
|
||||
context_opts = [
|
||||
@ -94,7 +96,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')
|
||||
|
||||
@ -145,6 +147,42 @@ class RequestContext(context.RequestContext):
|
||||
user_domain=values.get('user_domain'),
|
||||
project_domain=values.get('project_domain'))
|
||||
|
||||
def authorize(self, action, target=None, 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 cinder.exception.NotAuthorized: 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.
|
||||
"""
|
||||
if target is None:
|
||||
target = {'project_id': self.project_id,
|
||||
'user_id': self.user_id}
|
||||
if isinstance(target_obj, objects_base.CinderObject):
|
||||
# Turn object into dict so target.update can work
|
||||
target.update(
|
||||
target_obj.obj_to_primitive()['versioned_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()
|
||||
|
||||
|
26
cinder/policies/__init__.py
Normal file
26
cinder/policies/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# 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 itertools
|
||||
|
||||
from cinder.policies import attachments
|
||||
from cinder.policies import base
|
||||
|
||||
|
||||
def list_rules():
|
||||
return itertools.chain(
|
||||
base.list_rules(),
|
||||
attachments.list_rules()
|
||||
)
|
60
cinder/policies/attachments.py
Normal file
60
cinder/policies/attachments.py
Normal file
@ -0,0 +1,60 @@
|
||||
# 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 cinder.policies import base
|
||||
|
||||
|
||||
CREATE_POLICY = 'volume:attachment_create'
|
||||
UPDATE_POLICY = 'volume:attachment_update'
|
||||
DELETE_POLICY = 'volume:attachment_delete'
|
||||
|
||||
attachments_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name=CREATE_POLICY,
|
||||
check_str="",
|
||||
description="""Create attachment.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/attachments'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=UPDATE_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="""Update attachment.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/attachments/{attachment_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=DELETE_POLICY,
|
||||
check_str=base.RULE_ADMIN_OR_OWNER,
|
||||
description="""Delete attachment.""",
|
||||
operations=[
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'path': '/attachments/{attachment_id}'
|
||||
}
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return attachments_policies
|
35
cinder/policies/base.py
Normal file
35
cinder/policies/base.py
Normal file
@ -0,0 +1,35 @@
|
||||
# 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
|
||||
|
||||
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
|
138
cinder/policy.py
138
cinder/policy.py
@ -15,23 +15,52 @@
|
||||
|
||||
"""Policy Engine For Cinder"""
|
||||
|
||||
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 cinder import exception
|
||||
from cinder 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):
|
||||
@ -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 Cinder 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='cinder')
|
||||
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: cinder context
|
||||
:param action: string representing the action to be checked
|
||||
this should be colon separated for clarity.
|
||||
i.e. ``compute:create_instance``,
|
||||
``compute:attach_volume``,
|
||||
``volume:attach_volume``
|
||||
: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 cinder.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.error('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)
|
||||
|
@ -313,8 +313,9 @@ class TestCase(testtools.TestCase):
|
||||
|
||||
def flags(self, **kw):
|
||||
"""Override CONF variables for a test."""
|
||||
group = kw.pop('group', None)
|
||||
for k, v in kw.items():
|
||||
self.override_config(k, v)
|
||||
CONF.set_override(k, v, group)
|
||||
|
||||
def start_service(self, name, host=None, **kwargs):
|
||||
host = host if host else uuid.uuid4().hex
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"context_is_admin": "role:admin",
|
||||
"admin_api": "is_admin:True",
|
||||
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
|
||||
|
||||
@ -113,9 +112,6 @@
|
||||
"backup:update": "rule:admin_or_owner",
|
||||
"backup:backup_project_attribute": "rule:admin_api",
|
||||
|
||||
"volume:attachment_create": "",
|
||||
"volume:attachment_update": "rule:admin_or_owner",
|
||||
"volume:attachment_delete": "rule:admin_or_owner",
|
||||
|
||||
"consistencygroup:create" : "",
|
||||
"consistencygroup:delete": "",
|
||||
|
131
cinder/tests/unit/test_policy.py
Normal file
131
cinder/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 cinder import context
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder import utils
|
||||
|
||||
from cinder import policy
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class PolicyFileTestCase(test.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(test.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)
|
@ -1,55 +0,0 @@
|
||||
# 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.
|
||||
"""Tests for volume policy."""
|
||||
import mock
|
||||
|
||||
from cinder import context
|
||||
from cinder import test
|
||||
|
||||
import cinder.policy
|
||||
|
||||
|
||||
class VolumePolicyTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VolumePolicyTestCase, self).setUp()
|
||||
|
||||
cinder.policy.init()
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
def test_check_policy(self):
|
||||
target = {
|
||||
'project_id': self.context.project_id,
|
||||
'user_id': self.context.user_id,
|
||||
}
|
||||
with mock.patch.object(cinder.policy, 'enforce') as mock_enforce:
|
||||
cinder.volume.api.check_policy(self.context, 'attach')
|
||||
mock_enforce.assert_called_once_with(self.context,
|
||||
'volume:attach',
|
||||
target)
|
||||
|
||||
def test_check_policy_with_target(self):
|
||||
target = {
|
||||
'project_id': self.context.project_id,
|
||||
'user_id': self.context.user_id,
|
||||
'id': 2,
|
||||
}
|
||||
with mock.patch.object(cinder.policy, 'enforce') as mock_enforce:
|
||||
cinder.volume.api.check_policy(self.context, 'attach', {'id': 2})
|
||||
mock_enforce.assert_called_once_with(self.context,
|
||||
'volume:attach',
|
||||
target)
|
@ -43,6 +43,7 @@ from cinder import keymgr as key_manager
|
||||
from cinder import objects
|
||||
from cinder.objects import base as objects_base
|
||||
from cinder.objects import fields
|
||||
from cinder.policies import attachments as attachment_policy
|
||||
import cinder.policy
|
||||
from cinder import quota
|
||||
from cinder import quota_utils
|
||||
@ -1986,13 +1987,13 @@ class API(base.Base):
|
||||
db_ref = self.db.volume_attach(ctxt.elevated(), values)
|
||||
return objects.VolumeAttachment.get_by_id(ctxt, db_ref['id'])
|
||||
|
||||
@wrap_check_policy
|
||||
def attachment_create(self,
|
||||
ctxt,
|
||||
volume_ref,
|
||||
instance_uuid,
|
||||
connector=None):
|
||||
"""Create an attachment record for the specified volume."""
|
||||
ctxt.authorize(attachment_policy.CREATE_POLICY, target_obj=volume_ref)
|
||||
connection_info = {}
|
||||
attachment_ref = self._attachment_reserve(ctxt,
|
||||
volume_ref,
|
||||
@ -2007,7 +2008,6 @@ class API(base.Base):
|
||||
attachment_ref.save()
|
||||
return attachment_ref
|
||||
|
||||
@wrap_check_policy
|
||||
def attachment_update(self, ctxt, attachment_ref, connector):
|
||||
"""Update an existing attachment record."""
|
||||
# Valid items to update (connector includes mode and mountpoint):
|
||||
@ -2019,6 +2019,8 @@ class API(base.Base):
|
||||
# We fetch the volume object and pass it to the rpc call because we
|
||||
# need to direct this to the correct host/backend
|
||||
|
||||
ctxt.authorize(attachment_policy.UPDATE_POLICY,
|
||||
target_obj=attachment_ref)
|
||||
volume_ref = objects.Volume.get_by_id(ctxt, attachment_ref.volume_id)
|
||||
connection_info = (
|
||||
self.volume_rpcapi.attachment_update(ctxt,
|
||||
@ -2029,8 +2031,9 @@ class API(base.Base):
|
||||
attachment_ref.save()
|
||||
return attachment_ref
|
||||
|
||||
@wrap_check_policy
|
||||
def attachment_delete(self, ctxt, attachment):
|
||||
ctxt.authorize(attachment_policy.DELETE_POLICY,
|
||||
target_obj=attachment)
|
||||
volume = objects.Volume.get_by_id(ctxt, attachment.volume_id)
|
||||
if attachment.attach_status == 'reserved':
|
||||
self.db.volume_detached(ctxt.elevated(), attachment.volume_id,
|
||||
|
13
etc/cinder/README-policy.generate.md
Normal file
13
etc/cinder/README-policy.generate.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Generate policy file
|
||||
To generate the sample policy yaml file, run the following command from the top
|
||||
level of the cinder directory:
|
||||
|
||||
tox -egenpolicy
|
||||
|
||||
# Use generated policy file
|
||||
Cinder recognizes ``/etc/cinder/policy.yaml`` as the default policy file.
|
||||
To specify your own policy file in order to overwrite the default policy value,
|
||||
add this in Cinder config file:
|
||||
|
||||
[oslo_policy]
|
||||
policy_file = path/to/policy/file
|
3
etc/cinder/cinder-policy-generator.conf
Normal file
3
etc/cinder/cinder-policy-generator.conf
Normal file
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/cinder/policy.yaml.sample
|
||||
namespace = cinder
|
@ -1,9 +1,4 @@
|
||||
{
|
||||
"admin_or_owner": "is_admin:True or (role:admin and is_admin_project:True) or project_id:%(project_id)s",
|
||||
"default": "rule:admin_or_owner",
|
||||
|
||||
"admin_api": "is_admin:True or (role:admin and is_admin_project:True)",
|
||||
|
||||
"volume:create": "",
|
||||
"volume:create_from_image": "",
|
||||
"volume:delete": "rule:admin_or_owner",
|
||||
@ -105,10 +100,6 @@
|
||||
"backup:update": "rule:admin_or_owner",
|
||||
"backup:backup_project_attribute": "rule:admin_api",
|
||||
|
||||
"volume:attachment_create": "",
|
||||
"volume:attachment_update": "rule:admin_or_owner",
|
||||
"volume:attachment_delete": "rule:admin_or_owner",
|
||||
|
||||
"snapshot_extension:snapshot_actions:update_snapshot_status": "",
|
||||
"snapshot_extension:snapshot_manage": "rule:admin_api",
|
||||
"snapshot_extension:snapshot_unmanage": "rule:admin_api",
|
||||
|
@ -51,6 +51,14 @@ oslo.config.opts =
|
||||
cinder = cinder.opts:list_opts
|
||||
oslo.config.opts.defaults =
|
||||
cinder = cinder.common.config:set_middleware_defaults
|
||||
oslo.policy.enforcer =
|
||||
cinder = cinder.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.
|
||||
cinder = cinder.policies:list_rules
|
||||
console_scripts =
|
||||
cinder-api = cinder.cmd.api:main
|
||||
cinder-backup = cinder.cmd.backup:main
|
||||
|
4
tox.ini
4
tox.ini
@ -83,6 +83,10 @@ sitepackages = False
|
||||
envdir = {toxworkdir}/pep8
|
||||
commands = oslo-config-generator --config-file=tools/config/cinder-config-generator.conf
|
||||
|
||||
|
||||
[testenv:genpolicy]
|
||||
commands = oslopolicy-sample-generator --config-file=etc/cinder/cinder-policy-generator.conf
|
||||
|
||||
[testenv:genopts]
|
||||
sitepackages = False
|
||||
envdir = {toxworkdir}/pep8
|
||||
|
Loading…
Reference in New Issue
Block a user