Skip to content

Commit e912ced

Browse files
Jenkinsopenstack-gerrit
Jenkins
authored andcommitted
Merge "[policy in code] Add support for attachment resource"
2 parents 9c7c241 + 43a3152 commit e912ced

15 files changed

+450
-86
lines changed

Diff for: cinder/context.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
from oslo_utils import timeutils
2626
import six
2727

28+
from cinder import exception
2829
from cinder.i18n import _
30+
from cinder.objects import base as objects_base
2931
from cinder import policy
3032

3133
context_opts = [
@@ -94,7 +96,7 @@ def __init__(self, user_id=None, project_id=None, is_admin=None,
9496
# when policy.check_is_admin invokes request logging
9597
# to make it loggable.
9698
if self.is_admin is None:
97-
self.is_admin = policy.check_is_admin(self.roles, self)
99+
self.is_admin = policy.check_is_admin(self)
98100
elif self.is_admin and 'admin' not in self.roles:
99101
self.roles.append('admin')
100102

@@ -145,6 +147,42 @@ def from_dict(cls, values):
145147
user_domain=values.get('user_domain'),
146148
project_domain=values.get('project_domain'))
147149

150+
def authorize(self, action, target=None, target_obj=None, fatal=True):
151+
"""Verifies that the given action is valid on the target in this context.
152+
153+
:param action: string representing the action to be checked.
154+
:param target: dictionary representing the object of the action
155+
for object creation this should be a dictionary representing the
156+
location of the object e.g. ``{'project_id': context.project_id}``.
157+
If None, then this default target will be considered:
158+
{'project_id': self.project_id, 'user_id': self.user_id}
159+
:param: target_obj: dictionary representing the object which will be
160+
used to update target.
161+
:param fatal: if False, will return False when an
162+
exception.NotAuthorized occurs.
163+
164+
:raises cinder.exception.NotAuthorized: if verification fails and fatal
165+
is True.
166+
167+
:return: returns a non-False value (not necessarily "True") if
168+
authorized and False if not authorized and fatal is False.
169+
"""
170+
if target is None:
171+
target = {'project_id': self.project_id,
172+
'user_id': self.user_id}
173+
if isinstance(target_obj, objects_base.CinderObject):
174+
# Turn object into dict so target.update can work
175+
target.update(
176+
target_obj.obj_to_primitive()['versioned_object.data'] or {})
177+
else:
178+
target.update(target_obj or {})
179+
try:
180+
return policy.authorize(self, action, target)
181+
except exception.NotAuthorized:
182+
if fatal:
183+
raise
184+
return False
185+
148186
def to_policy_values(self):
149187
policy = super(RequestContext, self).to_policy_values()
150188

Diff for: cinder/policies/__init__.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
2+
# All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
import itertools
17+
18+
from cinder.policies import attachments
19+
from cinder.policies import base
20+
21+
22+
def list_rules():
23+
return itertools.chain(
24+
base.list_rules(),
25+
attachments.list_rules()
26+
)

Diff for: cinder/policies/attachments.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
2+
# All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
from oslo_policy import policy
17+
18+
from cinder.policies import base
19+
20+
21+
CREATE_POLICY = 'volume:attachment_create'
22+
UPDATE_POLICY = 'volume:attachment_update'
23+
DELETE_POLICY = 'volume:attachment_delete'
24+
25+
attachments_policies = [
26+
policy.DocumentedRuleDefault(
27+
name=CREATE_POLICY,
28+
check_str="",
29+
description="""Create attachment.""",
30+
operations=[
31+
{
32+
'method': 'POST',
33+
'path': '/attachments'
34+
}
35+
]),
36+
policy.DocumentedRuleDefault(
37+
name=UPDATE_POLICY,
38+
check_str=base.RULE_ADMIN_OR_OWNER,
39+
description="""Update attachment.""",
40+
operations=[
41+
{
42+
'method': 'PUT',
43+
'path': '/attachments/{attachment_id}'
44+
}
45+
]),
46+
policy.DocumentedRuleDefault(
47+
name=DELETE_POLICY,
48+
check_str=base.RULE_ADMIN_OR_OWNER,
49+
description="""Delete attachment.""",
50+
operations=[
51+
{
52+
'method': 'DELETE',
53+
'path': '/attachments/{attachment_id}'
54+
}
55+
]),
56+
]
57+
58+
59+
def list_rules():
60+
return attachments_policies

Diff for: cinder/policies/base.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
2+
# All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
from oslo_policy import policy
17+
18+
RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
19+
RULE_ADMIN_API = 'rule:admin_api'
20+
21+
rules = [
22+
policy.RuleDefault('context_is_admin', 'role:admin'),
23+
policy.RuleDefault('admin_or_owner',
24+
'is_admin:True or (role:admin and '
25+
'is_admin_project:True) or project_id:%(project_id)s'),
26+
policy.RuleDefault('default',
27+
'rule:admin_or_owner'),
28+
policy.RuleDefault('admin_api',
29+
'is_admin:True or (role:admin and '
30+
'is_admin_project:True)'),
31+
]
32+
33+
34+
def list_rules():
35+
return rules

Diff for: cinder/policy.py

+123-13
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,52 @@
1515

1616
"""Policy Engine For Cinder"""
1717

18+
import sys
1819

1920
from oslo_config import cfg
21+
from oslo_log import log as logging
2022
from oslo_policy import opts as policy_opts
2123
from oslo_policy import policy
24+
from oslo_utils import excutils
2225

2326
from cinder import exception
27+
from cinder import policies
2428

2529
CONF = cfg.CONF
30+
LOG = logging.getLogger(__name__)
2631
policy_opts.set_defaults(cfg.CONF, 'policy.json')
2732

2833
_ENFORCER = None
2934

3035

31-
def init():
36+
def reset():
37+
global _ENFORCER
38+
if _ENFORCER:
39+
_ENFORCER.clear()
40+
_ENFORCER = None
41+
42+
43+
def init(policy_file=None, rules=None, default_rule=None, use_conf=True):
44+
"""Init an Enforcer class.
45+
46+
:param policy_file: Custom policy file to use, if none is specified,
47+
`CONF.policy_file` will be used.
48+
:param rules: Default dictionary / Rules to use. It will be
49+
considered just in the first instantiation.
50+
:param default_rule: Default rule to use, CONF.default_rule will
51+
be used if none is specified.
52+
:param use_conf: Whether to load rules from config file.
53+
"""
54+
3255
global _ENFORCER
3356
if not _ENFORCER:
34-
_ENFORCER = policy.Enforcer(CONF)
57+
_ENFORCER = policy.Enforcer(CONF,
58+
policy_file=policy_file,
59+
rules=rules,
60+
default_rule=default_rule,
61+
use_conf=use_conf)
62+
register_rules(_ENFORCER)
63+
_ENFORCER.load_rules()
3564

3665

3766
def enforce_action(context, action):
@@ -72,19 +101,100 @@ def enforce(context, action, target):
72101
action=action)
73102

74103

75-
def check_is_admin(roles, context=None):
76-
"""Whether or not user is admin according to policy setting.
104+
def set_rules(rules, overwrite=True, use_conf=False):
105+
"""Set rules based on the provided dict of rules.
77106
107+
:param rules: New rules to use. It should be an instance of dict.
108+
:param overwrite: Whether to overwrite current rules or update them
109+
with the new rules.
110+
:param use_conf: Whether to reload rules from config file.
78111
"""
112+
113+
init(use_conf=False)
114+
_ENFORCER.set_rules(rules, overwrite, use_conf)
115+
116+
117+
def get_rules():
118+
if _ENFORCER:
119+
return _ENFORCER.rules
120+
121+
122+
def register_rules(enforcer):
123+
enforcer.register_defaults(policies.list_rules())
124+
125+
126+
def get_enforcer():
127+
# This method is for use by oslopolicy CLI scripts. Those scripts need the
128+
# 'output-file' and 'namespace' options, but having those in sys.argv means
129+
# loading the Cinder config options will fail as those are not expected to
130+
# be present. So we pass in an arg list with those stripped out.
131+
conf_args = []
132+
# Start at 1 because cfg.CONF expects the equivalent of sys.argv[1:]
133+
i = 1
134+
while i < len(sys.argv):
135+
if sys.argv[i].strip('-') in ['namespace', 'output-file']:
136+
i += 2
137+
continue
138+
conf_args.append(sys.argv[i])
139+
i += 1
140+
141+
cfg.CONF(conf_args, project='cinder')
79142
init()
143+
return _ENFORCER
80144

81-
# include project_id on target to avoid KeyError if context_is_admin
82-
# policy definition is missing, and default admin_or_owner rule
83-
# attempts to apply.
84-
target = {'project_id': ''}
85-
if context is None:
86-
credentials = {'roles': roles}
87-
else:
88-
credentials = context.to_dict()
89145

90-
return _ENFORCER.enforce('context_is_admin', target, credentials)
146+
def authorize(context, action, target, do_raise=True, exc=None):
147+
"""Verifies that the action is valid on the target in this context.
148+
149+
:param context: cinder context
150+
:param action: string representing the action to be checked
151+
this should be colon separated for clarity.
152+
i.e. ``compute:create_instance``,
153+
``compute:attach_volume``,
154+
``volume:attach_volume``
155+
:param target: dictionary representing the object of the action
156+
for object creation this should be a dictionary representing the
157+
location of the object e.g. ``{'project_id': context.project_id}``
158+
:param do_raise: if True (the default), raises PolicyNotAuthorized;
159+
if False, returns False
160+
:param exc: Class of the exception to raise if the check fails.
161+
Any remaining arguments passed to :meth:`authorize` (both
162+
positional and keyword arguments) will be passed to
163+
the exception class. If not specified,
164+
:class:`PolicyNotAuthorized` will be used.
165+
166+
:raises cinder.exception.PolicyNotAuthorized: if verification fails
167+
and do_raise is True. Or if 'exc' is specified it will raise an
168+
exception of that type.
169+
170+
:return: returns a non-False value (not necessarily "True") if
171+
authorized, and the exact value False if not authorized and
172+
do_raise is False.
173+
"""
174+
init()
175+
credentials = context.to_policy_values()
176+
if not exc:
177+
exc = exception.PolicyNotAuthorized
178+
try:
179+
result = _ENFORCER.authorize(action, target, credentials,
180+
do_raise=do_raise, exc=exc, action=action)
181+
except policy.PolicyNotRegistered:
182+
with excutils.save_and_reraise_exception():
183+
LOG.exception('Policy not registered')
184+
except Exception:
185+
with excutils.save_and_reraise_exception():
186+
LOG.error('Policy check for %(action)s failed with credentials '
187+
'%(credentials)s',
188+
{'action': action, 'credentials': credentials})
189+
return result
190+
191+
192+
def check_is_admin(context):
193+
"""Whether or not user is admin according to policy setting.
194+
195+
"""
196+
init()
197+
# the target is user-self
198+
credentials = context.to_policy_values()
199+
target = credentials
200+
return _ENFORCER.authorize('context_is_admin', target, credentials)

Diff for: cinder/test.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,9 @@ def override_config(self, name, override, group=None):
313313

314314
def flags(self, **kw):
315315
"""Override CONF variables for a test."""
316+
group = kw.pop('group', None)
316317
for k, v in kw.items():
317-
self.override_config(k, v)
318+
CONF.set_override(k, v, group)
318319

319320
def start_service(self, name, host=None, **kwargs):
320321
host = host if host else uuid.uuid4().hex

Diff for: cinder/tests/unit/policy.json

-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
"context_is_admin": "role:admin",
32
"admin_api": "is_admin:True",
43
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
54

@@ -113,9 +112,6 @@
113112
"backup:update": "rule:admin_or_owner",
114113
"backup:backup_project_attribute": "rule:admin_api",
115114

116-
"volume:attachment_create": "",
117-
"volume:attachment_update": "rule:admin_or_owner",
118-
"volume:attachment_delete": "rule:admin_or_owner",
119115

120116
"consistencygroup:create" : "",
121117
"consistencygroup:delete": "",

0 commit comments

Comments
 (0)