1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright (c) 2011 OpenStack, LLC.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
18
"""Common Policy Engine Implementation"""
24
from nova.openstack.common import jsonutils
27
LOG = logging.getLogger(__name__)
34
"""Set the brain used by enforce().
36
Defaults use Brain() if not set.
44
"""Clear the brain used by enforce()."""
49
def enforce(match_list, target_dict, credentials_dict, exc=None,
51
"""Enforces authorization of some rules against credentials.
53
:param match_list: nested tuples of data to match against
55
The basic brain supports three types of match lists:
59
looks like: ``('rule:compute:get_instance',)``
61
Retrieves the named rule from the rules dict and recursively
62
checks against the contents of the rule.
66
looks like: ``('role:compute:admin',)``
68
Matches if the specified role is in credentials_dict['roles'].
72
looks like: ``('tenant_id:%(tenant_id)s',)``
74
Substitutes values from the target dict into the match using
75
the % operator and matches them against the creds dict.
79
The brain returns True if any of the outer tuple of rules
80
match and also True if all of the inner tuples match. You
81
can use this to perform simple boolean logic. For
82
example, the following rule would return True if the creds
83
contain the role 'admin' OR the if the tenant_id matches
84
the target dict AND the the creds contains the role
92
('tenant_id:%(tenant_id)s', 'role:compute_sysadmin')
96
Note that rule and role are reserved words in the credentials match, so
97
you can't match against properties with those names. Custom brains may
98
also add new reserved words. For example, the HttpBrain adds http as a
101
:param target_dict: dict of object properties
103
Target dicts contain as much information as we can about the object being
106
:param credentials_dict: dict of actor properties
108
Credentials dicts contain as much information as we can about the user
109
performing the action.
111
:param exc: exception to raise
113
Class of the exception to raise if the check fails. Any remaining
114
arguments passed to enforce() (both positional and keyword arguments)
115
will be passed to the exception class. If exc is not provided, returns
118
:return: True if the policy allows the action
119
:return: False if the policy does not allow the action and exc is not set
124
if not _BRAIN.check(match_list, target_dict, credentials_dict):
126
raise exc(*args, **kwargs)
132
"""Implements policy checking."""
134
def load_json(cls, data, default_rule=None):
135
"""Init a brain using json instead of a rules dictionary."""
136
rules_dict = jsonutils.loads(data)
137
return cls(rules=rules_dict, default_rule=default_rule)
139
def __init__(self, rules=None, default_rule=None):
140
self.rules = rules or {}
141
self.default_rule = default_rule
143
def add_rule(self, key, match):
144
self.rules[key] = match
146
def _check(self, match, target_dict, cred_dict):
148
match_kind, match_value = match.split(':', 1)
150
LOG.exception(_("Failed to understand rule %(match)r") % locals())
151
# If the rule is invalid, fail closed
154
f = getattr(self, '_check_%s' % match_kind)
155
except AttributeError:
156
if not self._check_generic(match, target_dict, cred_dict):
159
if not f(match_value, target_dict, cred_dict):
163
def check(self, match_list, target_dict, cred_dict):
164
"""Checks authorization of some rules against credentials.
166
Detailed description of the check with examples in policy.enforce().
168
:param match_list: nested tuples of data to match against
169
:param target_dict: dict of object properties
170
:param credentials_dict: dict of actor properties
172
:returns: True if the check passes
177
for and_list in match_list:
178
if isinstance(and_list, basestring):
179
and_list = (and_list,)
180
if all([self._check(item, target_dict, cred_dict)
181
for item in and_list]):
185
def _check_rule(self, match, target_dict, cred_dict):
186
"""Recursively checks credentials based on the brains rules."""
188
new_match_list = self.rules[match]
190
if self.default_rule and match != self.default_rule:
191
new_match_list = ('rule:%s' % self.default_rule,)
195
return self.check(new_match_list, target_dict, cred_dict)
197
def _check_role(self, match, target_dict, cred_dict):
198
"""Check that there is a matching role in the cred dict."""
199
return match.lower() in [x.lower() for x in cred_dict['roles']]
201
def _check_generic(self, match, target_dict, cred_dict):
202
"""Check an individual match.
211
# TODO(termie): do dict inspection via dot syntax
212
match = match % target_dict
213
key, value = match.split(':', 1)
215
return value == cred_dict[key]
219
class HttpBrain(Brain):
220
"""A brain that can check external urls for policy.
222
Posts json blobs for target and credentials.
226
def _check_http(self, match, target_dict, cred_dict):
227
"""Check http: rules by calling to a remote server.
229
This example implementation simply verifies that the response is
230
exactly 'True'. A custom brain using response codes could easily
234
url = match % target_dict
235
data = {'target': jsonutils.dumps(target_dict),
236
'credentials': jsonutils.dumps(cred_dict)}
237
post_data = urllib.urlencode(data)
238
f = urllib2.urlopen(url, post_data)
239
return f.read() == "True"