source: fedd/federation/authorizer.py @ 5c0d244

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

Beginnings of an ABAC authorizer

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