#/usr/local/bin/python from tempfile import mkstemp from subprocess import call from fedid import fedid from remote_service import service_caller from abac_remote_service import abac_service_caller from service_error import service_error import ABAC import pickle import sys import os class authorizer_base: """ Classes based on this one keep track of authorization attributes for the various modules running. This base class holds some utility functions that they all potentially use. """ # general error exception for badly formed names. class bad_name(RuntimeError): pass # difficulty creating an attribute class attribute_error(RuntimeError): pass @staticmethod def auth_name(name): """ Helper to convert a non-unicode local name to a unicode string. Mixed representations can needlessly confuse the authorizer. """ if isinstance(name, basestring): if not isinstance(name, unicode): return unicode(name) else: return name else: return name @staticmethod def valid_name(name): """ Ensure that the given name is valid. A valid name can either be a triple of strings and fedids representing one of our generalized Emulab names or a single fedid. Compound names can include wildcards (None) and must anchor to a fedid at their highest level (unless they're all None) This either returns True or throws an exception. More an assertion than a function. """ if isinstance(name, tuple) and len(name) == 3: for n in name: if n: if not (isinstance(n, basestring) or isinstance(n, fedid)): raise authorizer_base.bad_name( "names must be either a triple or a fedid") for n in name: if n: if isinstance(n, fedid): return True else: raise authorizer_base.bad_name( "Compound names must be " + \ "rooted in fedids: %s" % str(name)) return True elif isinstance(name, fedid): return True else: raise authorizer_base.bad_name( "Names must be a triple or a fedid (%s)" % name) class authorizer(authorizer_base): """ This class keeps track of authorization attributes for the various modules running. When it gets smarter it will be the basis for a real attribute-based authentication system. """ def __init__(self, def_attr="testbed"): self.attrs = { } self.globals=set() def set_attribute(self, name, attr): """ Attach attr to name. Multiple attrs can be attached. """ self.valid_name(name) if isinstance(name, tuple): aname = tuple([ self.auth_name(n) for n in name]) else: aname = self.auth_name(name) if not self.attrs.has_key(aname): self.attrs[aname] = set() self.attrs[aname].add(attr) def unset_attribute(self, name, attr): """ Remove an attribute from name """ self.valid_name(name) if isinstance(name, tuple): aname = tuple([ self.auth_name(n) for n in name]) else: aname = self.auth_name(name) attrs = self.attrs.get(aname, None) if attrs: attrs.discard(attr) def check_attribute(self, name, attr): """ Return True if name has attr (or if attr is global). Tuple names match any tuple name that matches all names present and has None entries in other fileds. For tuple names True implies that there is a matching tuple name with the attribute. """ def tup(tup, i, p): mask = 1 << i if p & mask : return authorizer.auth_name(tup[i]) else: return None self.valid_name(name) if attr in self.globals: return True if isinstance(name, tuple): for p in range(0,8): lookup = ( tup(name, 0, p), tup(name,1, p), tup(name,2,p)) if self.attrs.has_key(lookup): if attr in self.attrs[lookup]: return True else: return attr in self.attrs.get(self.auth_name(name), set()) def set_global_attribute(self, attr): """ Set a global attribute. All names, even those otherwise unknown to the authorizer have this attribute. """ self.globals.add(attr) def unset_global_attribute(self, attr): """ Remove a global attribute """ self.globals.discard(attr) def __str__(self): rv = "" rv += "attrs %s\n" % self.attrs rv += "globals %s" % self.globals return rv def clone(self): rv = authorizer() rv.attrs = self.attrs.copy() rv.globals = self.globals.copy() return rv def save(self, fn): f = open(fn, "w") pickle.dump(self, f) f.close() def load(self, fn): f = open(fn, "r") a = pickle.load(f) f.close() self.attrs = a.attrs self.globals = a.globals class abac_authorizer(authorizer_base): """ Use the ABAC authorization system to make attribute decisions. """ def __init__(self, certs=None, me=None, key=None, from_dir=None): self.bad_name = authorizer_base.bad_name self.attribute_error = authorizer_base.attribute_error self.globals = set() self.creddy = '/usr/local/bin/creddy' self.me = me self.my_key = key self.context = ABAC.Context() for dir in certs or []: self.context.load_directory(dir) def set_attribute(self, name=None, attr=None, cert=None): if name and attr: if isinstance(name, tuple): raise self.bad_name("ABAC doesn't understand three-names") if self.me and self.key: # Create a credential and insert it into context # This will simplify when we have libcreddy try: # create temp file f, fn = mkstemp() f.close() except EnvironmentError, e: raise self.attribute_error( "Cannot create temp file: %s" %e) # Create the attribute certificate with creddy rv = call([creddy, '--attribute', '--issuer=%s' % self.me, '--key=%s' % self.key, '--role=%s' % attr, '--subject-id=%s' % name, '--out=%s' % fn]) if rv == 0: # load it to context and remove the file self.context.load_attribute(fn) os.unlink(fn) else: os.unlink(fn) raise self.attribute_error("creddy returned %s" % rv) else: raise self.attribute_error( "Identity and key not specified on creation") elif cert: # Insert this credential into the context self.context.load_attribute_chunk(cert) else: raise self.attribute_error("Neither name/attr nor cert is set") def check_attribute(self, name, attr): # XXX proof soon if isinstance(name, tuple): raise self.bad_name("ABAC doesn't understand three-names") else: proof, rv = self.context.query(attr, name) # XXX delete soon if not rv and attr in self.globals: return True else: return rv def set_global_attribute(self, attr): """ Set a global attribute. All names, even those otherwise unknown to the authorizer have this attribute. """ self.globals.add(attr) def unset_global_attribute(self, attr): """ Remove a global attribute """ self.globals.discard(attr) def clone(self): rv = abac_authorizer(me=self.me, key=self.key) rv.globals = self.globals.copy() rv.context = ABAC.Context(self.context) return rv def save(self, dir): os.mkdir(dir) f = open("%s/globals" % dir, "w") pickle.dump(self.globals, f) f.close() if self.me and self.key: f = open("%s/me" % dir, "w") pickle.dump(self.me, f) f.close() f = open("%s/key" % dir, "w") pickle.dump(self.key, f) f.close() os.mkdir("%s/certs" % dir) seenit = set() ii = 0 ai = 0 for c in self.context.credentials(): id = c.issuer_cert() attr = c.attribute_cert() if id not in seenit: f = open("%s/certs/%03d_ID.der" % (dir, ii), "w") print >>f, id f.close() ii += 1 seenit.add(id) if attr: f = open("%s/certs/%03d_attr.der" % (dir, ai), "w") print >>f, attr f.close() ai += 1 def load(self, dir): if os.access("%s/me" % dir, os.R_OK): f = open("%s/me" % dir, "r") self.me = pickle.load(f) f.close() else: self.me = None if os.access("%s/key" % dir, os.R_OK): f = open("%s/key" % dir, "r") self.key = pickle.load(f) f.close() else: self.key = None f = open("%s/globals" % dir, "r") self.globals = pickle.load(f) f.close() self.context = ABAC.context() self.context.load_directory("%s/certs" % dir)