~ubuntu-cloud-archive/ubuntu/precise/nova/trunk

« back to all changes in this revision

Viewing changes to nova/openstack/common/policy.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short, Adam Gandelman
  • Date: 2012-06-22 12:39:57 UTC
  • mfrom: (1.1.57)
  • Revision ID: package-import@ubuntu.com-20120622123957-hbzwg84nt9rqwg8r
Tags: 2012.2~f2~20120621.14517-0ubuntu1
[ Chuck Short ]
* New upstream version.

[ Adam Gandelman ]
* debian/rules: Temporarily disable test suite while blocking
  tests are investigated. 
* debian/patches/kombu_tests_timeout.patch: Dropped.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright (c) 2011 OpenStack, LLC.
 
4
# All Rights Reserved.
 
5
#
 
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
 
9
#
 
10
#         http://www.apache.org/licenses/LICENSE-2.0
 
11
#
 
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
 
16
#    under the License.
 
17
 
 
18
"""Common Policy Engine Implementation"""
 
19
 
 
20
import logging
 
21
import urllib
 
22
import urllib2
 
23
 
 
24
from nova.openstack.common import jsonutils
 
25
 
 
26
 
 
27
LOG = logging.getLogger(__name__)
 
28
 
 
29
 
 
30
_BRAIN = None
 
31
 
 
32
 
 
33
def set_brain(brain):
 
34
    """Set the brain used by enforce().
 
35
 
 
36
    Defaults use Brain() if not set.
 
37
 
 
38
    """
 
39
    global _BRAIN
 
40
    _BRAIN = brain
 
41
 
 
42
 
 
43
def reset():
 
44
    """Clear the brain used by enforce()."""
 
45
    global _BRAIN
 
46
    _BRAIN = None
 
47
 
 
48
 
 
49
def enforce(match_list, target_dict, credentials_dict, exc=None,
 
50
            *args, **kwargs):
 
51
    """Enforces authorization of some rules against credentials.
 
52
 
 
53
    :param match_list: nested tuples of data to match against
 
54
 
 
55
        The basic brain supports three types of match lists:
 
56
 
 
57
            1) rules
 
58
 
 
59
                looks like: ``('rule:compute:get_instance',)``
 
60
 
 
61
                Retrieves the named rule from the rules dict and recursively
 
62
                checks against the contents of the rule.
 
63
 
 
64
            2) roles
 
65
 
 
66
                looks like: ``('role:compute:admin',)``
 
67
 
 
68
                Matches if the specified role is in credentials_dict['roles'].
 
69
 
 
70
            3) generic
 
71
 
 
72
                looks like: ``('tenant_id:%(tenant_id)s',)``
 
73
 
 
74
                Substitutes values from the target dict into the match using
 
75
                the % operator and matches them against the creds dict.
 
76
 
 
77
        Combining rules:
 
78
 
 
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
 
85
            'compute_sysadmin':
 
86
 
 
87
            ::
 
88
 
 
89
                {
 
90
                    "rule:combined": (
 
91
                        'role:admin',
 
92
                        ('tenant_id:%(tenant_id)s', 'role:compute_sysadmin')
 
93
                    )
 
94
                }
 
95
 
 
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
 
99
        reserved word.
 
100
 
 
101
    :param target_dict: dict of object properties
 
102
 
 
103
      Target dicts contain as much information as we can about the object being
 
104
      operated on.
 
105
 
 
106
    :param credentials_dict: dict of actor properties
 
107
 
 
108
      Credentials dicts contain as much information as we can about the user
 
109
      performing the action.
 
110
 
 
111
    :param exc: exception to raise
 
112
 
 
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
 
116
      False.
 
117
 
 
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
 
120
    """
 
121
    global _BRAIN
 
122
    if not _BRAIN:
 
123
        _BRAIN = Brain()
 
124
    if not _BRAIN.check(match_list, target_dict, credentials_dict):
 
125
        if exc:
 
126
            raise exc(*args, **kwargs)
 
127
        return False
 
128
    return True
 
129
 
 
130
 
 
131
class Brain(object):
 
132
    """Implements policy checking."""
 
133
    @classmethod
 
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)
 
138
 
 
139
    def __init__(self, rules=None, default_rule=None):
 
140
        self.rules = rules or {}
 
141
        self.default_rule = default_rule
 
142
 
 
143
    def add_rule(self, key, match):
 
144
        self.rules[key] = match
 
145
 
 
146
    def _check(self, match, target_dict, cred_dict):
 
147
        try:
 
148
            match_kind, match_value = match.split(':', 1)
 
149
        except Exception:
 
150
            LOG.exception(_("Failed to understand rule %(match)r") % locals())
 
151
            # If the rule is invalid, fail closed
 
152
            return False
 
153
        try:
 
154
            f = getattr(self, '_check_%s' % match_kind)
 
155
        except AttributeError:
 
156
            if not self._check_generic(match, target_dict, cred_dict):
 
157
                return False
 
158
        else:
 
159
            if not f(match_value, target_dict, cred_dict):
 
160
                return False
 
161
        return True
 
162
 
 
163
    def check(self, match_list, target_dict, cred_dict):
 
164
        """Checks authorization of some rules against credentials.
 
165
 
 
166
        Detailed description of the check with examples in policy.enforce().
 
167
 
 
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
 
171
 
 
172
        :returns: True if the check passes
 
173
 
 
174
        """
 
175
        if not match_list:
 
176
            return True
 
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]):
 
182
                return True
 
183
        return False
 
184
 
 
185
    def _check_rule(self, match, target_dict, cred_dict):
 
186
        """Recursively checks credentials based on the brains rules."""
 
187
        try:
 
188
            new_match_list = self.rules[match]
 
189
        except KeyError:
 
190
            if self.default_rule and match != self.default_rule:
 
191
                new_match_list = ('rule:%s' % self.default_rule,)
 
192
            else:
 
193
                return False
 
194
 
 
195
        return self.check(new_match_list, target_dict, cred_dict)
 
196
 
 
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']]
 
200
 
 
201
    def _check_generic(self, match, target_dict, cred_dict):
 
202
        """Check an individual match.
 
203
 
 
204
        Matches look like:
 
205
 
 
206
            tenant:%(tenant_id)s
 
207
            role:compute:admin
 
208
 
 
209
        """
 
210
 
 
211
        # TODO(termie): do dict inspection via dot syntax
 
212
        match = match % target_dict
 
213
        key, value = match.split(':', 1)
 
214
        if key in cred_dict:
 
215
            return value == cred_dict[key]
 
216
        return False
 
217
 
 
218
 
 
219
class HttpBrain(Brain):
 
220
    """A brain that can check external urls for policy.
 
221
 
 
222
    Posts json blobs for target and credentials.
 
223
 
 
224
    """
 
225
 
 
226
    def _check_http(self, match, target_dict, cred_dict):
 
227
        """Check http: rules by calling to a remote server.
 
228
 
 
229
        This example implementation simply verifies that the response is
 
230
        exactly 'True'. A custom brain using response codes could easily
 
231
        be implemented.
 
232
 
 
233
        """
 
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"