source: fedd/federation/authorizer.py @ 1fc09db

axis_examplecompt_changesinfo-ops
Last change on this file since 1fc09db was 1fc09db, checked in by Ted Faber <faber@…>, 14 years ago

unset_attribute added

  • Property mode set to 100644
File size: 10.4 KB
Line 
1#/usr/local/bin/python
2
3from string import join
4from tempfile import mkstemp
5from subprocess import call
6from threading import Lock
7
8from fedid import fedid
9from remote_service import service_caller
10from abac_remote_service import abac_service_caller
11from service_error import service_error
12
13
14import ABAC
15import pickle
16
17import sys
18import os
19import re
20
21class authorizer_base:
22    """
23    Classes based on this one keep track of authorization attributes for the
24    various modules running.  This base class holds some utility functions that
25    they all potentially use.
26    """
27
28    # general error exception for badly formed names. 
29    class bad_name(RuntimeError): pass
30    # difficulty creating an attribute
31    class attribute_error(RuntimeError): pass
32
33    @staticmethod
34    def auth_name(name):
35        """
36        Helper to convert a non-unicode local name to a unicode string.  Mixed
37        representations can needlessly confuse the authorizer.
38        """
39        if isinstance(name, basestring):
40            if not isinstance(name, unicode): return unicode(name)
41            else: return name
42        else: return name
43
44    @staticmethod
45    def valid_name(name):
46        """
47        Ensure that the given name is valid.  A valid name can either be a
48        triple of strings and fedids representing one of our generalized Emulab
49        names or a single fedid.  Compound names can include wildcards (None)
50        and must anchor to a fedid at their highest level (unless they're all
51        None)
52
53        This either returns True or throws an exception.  More an assertion
54        than a function.
55        """
56        if isinstance(name, tuple) and len(name) == 3:
57            for n in name:
58                if n: 
59                    if not (isinstance(n, basestring) or isinstance(n, fedid)):
60                        raise authorizer_base.bad_name(
61                                "names must be either a triple or a fedid")
62            for n in name: 
63                if n:
64                    if isinstance(n, fedid):
65                        return True
66                    else:
67                        raise authorizer_base.bad_name(
68                                "Compound names must be " + \
69                                "rooted in fedids: %s" % str(name))
70
71            return True
72        elif isinstance(name, fedid):
73            return True
74        else:
75            raise authorizer_base.bad_name(
76                    "Names must be a triple or a fedid (%s)" % name)
77
78
79class authorizer(authorizer_base):
80    """
81    This class keeps track of authorization attributes for the various modules
82    running.  When it gets smarter it will be the basis for a real
83    attribute-based authentication system.
84    """
85    def __init__(self, def_attr="testbed"):
86        self.attrs = { }
87        self.globals=set()
88
89    def set_attribute(self, name, attr):
90        """
91        Attach attr to name.  Multiple attrs can be attached.
92        """
93        self.valid_name(name)
94        if isinstance(name, tuple):
95            aname = tuple([ self.auth_name(n) for n in name])
96        else:
97            aname = self.auth_name(name)
98
99        if not self.attrs.has_key(aname):
100            self.attrs[aname] = set()
101        self.attrs[aname].add(attr)
102
103    def unset_attribute(self, name, attr):
104        """
105        Remove an attribute from name
106        """
107        self.valid_name(name)
108        if isinstance(name, tuple):
109            aname = tuple([ self.auth_name(n) for n in name])
110        else:
111            aname = self.auth_name(name)
112
113        attrs = self.attrs.get(aname, None)
114        if attrs: attrs.discard(attr)
115
116    def check_attribute(self, name, attr):
117        """
118        Return True if name has attr (or if attr is global).  Tuple names match
119        any tuple name that matches all names present and has None entries in
120        other fileds.  For tuple names True implies that there is a matching
121        tuple name with the attribute.
122        """
123        def tup(tup, i, p):
124            mask = 1 << i
125            if p & mask : return authorizer.auth_name(tup[i])
126            else: return None
127
128        self.valid_name(name)
129        if attr in self.globals:
130            return True
131
132        if isinstance(name, tuple):
133            for p in range(0,8):
134                lookup = ( tup(name, 0, p), tup(name,1, p), tup(name,2,p))
135                if self.attrs.has_key(lookup):
136                    if attr in self.attrs[lookup]:
137                        return True
138        else:
139            return  attr in self.attrs.get(self.auth_name(name), set())
140
141    def set_global_attribute(self, attr):
142        """
143        Set a global attribute.  All names, even those otherwise unknown to the
144        authorizer have this attribute.
145        """
146        self.globals.add(attr)
147
148    def unset_global_attribute(self, attr):
149        """
150        Remove a global attribute
151        """
152
153        self.globals.discard(attr)
154
155    def __str__(self):
156        rv = ""
157        rv += "attrs %s\n" % self.attrs
158        rv += "globals %s" % self.globals
159        return rv
160
161    def clone(self):
162        rv = authorizer()
163        rv.attrs = self.attrs.copy()
164        rv.globals = self.globals.copy()
165        return rv
166
167    def save(self, fn):
168        f = open(fn, "w")
169        pickle.dump(self, f)
170        f.close()
171
172    def load(self, fn):
173        f = open(fn, "r")
174        a = pickle.load(f)
175        f.close()
176        self.attrs = a.attrs
177        self.globals = a.globals
178       
179
180class abac_authorizer(authorizer_base):
181    """
182    Use the ABAC authorization system to make attribute decisions.
183    """
184
185    def __init__(self, certs=None, me=None, key=None):
186        self.bad_name = authorizer_base.bad_name
187        self.attribute_error = authorizer_base.attribute_error
188        self.globals = set()
189        self.creddy = '/usr/local/bin/creddy'
190        self.me = me
191        self.key = key
192        self.context = ABAC.Context()
193        self.lock = Lock()
194
195        if me:
196            self.fedid = fedid(file=self.me)
197            self.context.load_id_file(self.me)
198
199        for dir in certs or []:
200            self.context.load_directory(dir)
201
202    def set_attribute(self, name=None, attr=None, cert=None):
203        if name and attr:
204            if isinstance(name, tuple):
205                raise self.bad_name("ABAC doesn't understand three-names")
206            if self.me and self.key:
207                # Create a credential and insert it into context
208                # This will simplify when we have libcreddy
209                try:
210                    # create temp file
211                    f, fn = mkstemp()
212                    os.close(f)
213                except EnvironmentError, e:
214                    raise self.attribute_error(
215                            "Cannot create temp file: %s" %e)
216
217                # Create the attribute certificate with creddy
218                cmd = [self.creddy, '--attribute', '--issuer=%s' % self.me, 
219                    '--key=%s' % self.key, '--role=%s' % attr, 
220                    '--subject-id=%s' % name, '--out=%s' % fn]
221                rv = call(cmd)
222                if rv == 0:
223                    self.lock.acquire()
224                    # load it to context and remove the file
225                    rv = self.context.load_attribute_file(fn)
226                    self.lock.release()
227                    os.unlink(fn)
228                else:
229                    os.unlink(fn)
230                    raise self.attribute_error("creddy returned %s" % rv)
231            else:
232                raise self.attribute_error(
233                        "Identity and key not specified on creation")
234        elif cert:
235            # Insert this credential into the context
236            self.lock.acquire()
237            self.context.load_attribute_chunk(cert)
238            self.lock.release()
239        else:
240            raise self.attribute_error("Neither name/attr nor cert is set")
241
242    def unset_attribute(self, name, attr):
243        if isinstance(name, tuple):
244            raise self.bad_name("ABAC doesn't understand three-names")
245        self.lock.acquire()
246        ctxt = ABAC.Context()
247        ids = set()
248        for c in self.context.credentials():
249            h = c.head()
250            t = c.tail()
251            if h.is_role() and t.is_principal():
252                if t.principal() == '%s' % name and \
253                        h.principal() == '%s' % self.fedid and \
254                        h.role_name() == attr:
255                    continue
256
257            id = c.issuer_cert()
258            if id not in ids:
259                ctxt.load_id_chunk(id)
260                ids.add(id)
261            ctxt.load_attribute_chunk(c.attribute_cert())
262        self.context = ctxt
263        self.lock.release()
264
265
266    def check_attribute(self, name, attr):
267        # XXX proof soon
268        if isinstance(name, tuple):
269            raise self.bad_name("ABAC doesn't understand three-names")
270        else:
271           
272            # Naked attributes are attested by this principal
273            if attr.find('.') == -1: a = "%s.%s" % (self.fedid, attr)
274            else: a = attr
275
276            self.lock.acquire()
277            proof, rv = self.context.query(a, name)
278            # XXX delete soon
279            if not rv and attr in self.globals: rv = True
280            self.lock.release()
281
282            return rv
283
284    def set_global_attribute(self, attr):
285        """
286        Set a global attribute.  All names, even those otherwise unknown to the
287        authorizer have this attribute.
288        """
289        self.lock.acquire()
290        self.globals.add(attr)
291        self.lock.release()
292
293    def unset_global_attribute(self, attr):
294        """
295        Remove a global attribute
296        """
297
298        self.lock.acquire()
299        self.globals.discard(attr)
300        self.lock.release()
301
302    def clone(self):
303        self.lock.acquire()
304        rv = abac_authorizer(me=self.me, key=self.key)
305        rv.globals = self.globals.copy()
306        rv.context = ABAC.Context(self.context)
307        self.lock.release()
308        return rv
309
310    def save(self, dir):
311        self.lock.acquire()
312        try:
313            if not os.access(dir, os.F_OK):
314                os.mkdir(dir)
315
316            f = open("%s/globals" % dir, "w")
317            pickle.dump(self.globals, f)
318            f.close()
319
320            if self.me and self.key:
321                f = open("%s/me" % dir, "w")
322                pickle.dump(self.me, f)
323                f.close()
324                f = open("%s/key" % dir, "w")
325                pickle.dump(self.key, f)
326                f.close()
327            if not os.access("%s/certs" %dir, os.F_OK):
328                os.mkdir("%s/certs" % dir)
329            seenit = set()
330            #remove old certs
331            for fn in [ f for f in os.listdir("%s/certs" % dir) \
332                    if re.match('.*\.der$', f)]:
333                os.unlink('%s/certs/%s' % (dir, fn))
334            ii = 0
335            ai = 0
336            for c in self.context.credentials():
337                id = c.issuer_cert()
338                attr = c.attribute_cert()
339                # NB: file naming conventions matter here.  The trailing_ID and
340                # _attr are required by ABAC.COntext.load_directory()
341                if id not in seenit:
342                    f = open("%s/certs/ID_%03d_ID.der" % (dir, ii), "w")
343                    print >>f, id
344                    f.close()
345                    ii += 1
346                    seenit.add(id)
347                if attr:
348                    f = open("%s/certs/attr_%03d_attr.der" % (dir, ai), "w")
349                    print >>f, attr
350                    f.close()
351                    ai += 1
352        except EnvironmentError, e:
353            self.lock.release()
354            raise e
355        except pickle.PickleError, e:
356            self.lock.release()
357            raise e
358        self.lock.release()
359
360    def load(self, dir):
361        self.lock.acquire()
362        try:
363            if os.access("%s/me" % dir, os.R_OK):
364                f = open("%s/me" % dir, "r")
365                self.me = pickle.load(f)
366                f.close()
367                if self.me:
368                    self.fedid = fedid(file=self.me)
369            else:
370                self.me = None
371            if os.access("%s/key" % dir, os.R_OK):
372                f = open("%s/key" % dir, "r")
373                self.key = pickle.load(f)
374                f.close()
375            else:
376                self.key = None
377            f = open("%s/globals" % dir, "r")
378            self.globals = pickle.load(f)
379            f.close()
380            self.context = ABAC.Context()
381            self.context.load_directory("%s/certs" % dir)
382        except EnvironmentError, e:
383            self.lock.release()
384            raise e
385        except pickle.PickleError, e:
386            self.lock.release()
387            raise e
388        self.lock.release()
389
390    def __str__(self):
391        def encode_role(r):
392            if r.is_principal(): 
393                return "%s" % r.principal()
394            elif r.is_role(): 
395                return "%s.%s" % (r.principal(), r.role_name())
396            elif r.is_linking():
397                return "%s.%s.%s" % \
398                        (r.principal(), r.linking_role(), r.role_name())
399
400        self.lock.acquire()
401        rv = "%s" % self.fedid
402        for c in self.context.credentials():
403            rv += '\n%s <- %s' % (encode_role(c.head()), encode_role(c.tail()))
404        self.lock.release()
405        return rv
406
Note: See TracBrowser for help on using the repository browser.