Ignore:
Timestamp:
Jan 10, 2012 5:28:15 PM (12 years ago)
Author:
Ted Faber <faber@…>
Branches:
compt_changes, info-ops, master
Children:
f77a256
Parents:
d2e86f6
Message:

Deactivate legacy authorization and dynamic projects

File:
1 edited

Legend:

Unmodified
Added
Removed
  • fedd/federation/emulab_access.py

    rd2e86f6 ree950c2  
    1616
    1717from access import access_base
    18 from legacy_access import legacy_access
    1918
    2019from util import *
    21 from allocate_project import allocate_project_local, allocate_project_remote
    2220from fedid import fedid, generate_fedid
    2321from authorizer import authorizer, abac_authorizer
     
    4240fl.addHandler(nullHandler())
    4341
    44 class access(access_base, legacy_access):
     42class access(access_base):
    4543    """
    4644    The implementation of access control based on mapping users to projects.
     
    109107        # authorization information
    110108        self.auth_type = config.get('access', 'auth_type') \
    111                 or 'legacy'
     109                or 'abac'
    112110        self.auth_dir = config.get('access', 'auth_dir')
    113111        accessdb = config.get("access", "accessdb")
    114112        # initialize the authorization system
    115         if self.auth_type == 'legacy':
    116             self.access = { }
    117             if accessdb:
    118                 self.legacy_read_access(accessdb, self.legacy_access_tuple)
    119         elif self.auth_type == 'abac':
     113        if self.auth_type == 'abac':
    120114            self.auth = abac_authorizer(load=self.auth_dir)
    121115            self.access = [ ]
     
    128122        # read_state in the base_class
    129123        self.state_lock.acquire()
    130         for a  in ('allocation', 'projects', 'keys', 'types'):
    131             if a not in self.state:
    132                 self.state[a] = { }
     124        if 'allocation' not in self.state: self.state['allocation']= { }
    133125        self.allocation = self.state['allocation']
    134         self.projects = self.state['projects']
    135         self.keys = self.state['keys']
    136         self.types = self.state['types']
    137         if self.auth_type == "legacy":
    138             # Add the ownership attributes to the authorizer.  Note that the
    139             # indices of the allocation dict are strings, but the attributes are
    140             # fedids, so there is a conversion.
    141             for k in self.allocation.keys():
    142                 for o in self.allocation[k].get('owners', []):
    143                     self.auth.set_attribute(o, fedid(hexstr=k))
    144                 if self.allocation[k].has_key('userconfig'):
    145                     sfid = self.allocation[k]['userconfig']
    146                     fid = fedid(hexstr=sfid)
    147                     self.auth.set_attribute(fid, "/%s" % sfid)
    148126        self.state_lock.release()
    149127        self.exports = {
     
    195173        self.call_GetValue = service_caller('GetValue', log=self.log)
    196174
    197         if not config.has_option("allocate", "uri"):
    198             self.allocate_project = \
    199                 allocate_project_local(config, auth)
    200         else:
    201             self.allocate_project = \
    202                 allocate_project_remote(config, auth)
    203 
    204 
    205         # If the project allocator exports services, put them in this object's
    206         # maps so that classes that instantiate this can call the services.
    207         self.soap_services.update(self.allocate_project.soap_services)
    208         self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
    209 
    210     @staticmethod
    211     def legacy_access_tuple(str):
    212         """
    213         Convert a string of the form (id[:resources:resouces], id, id) into a
    214         tuple of the form (project, user, user) where users may be names or
    215         fedids.  The resources strings are obsolete and ignored.
    216         """
    217         def parse_name(n):
    218             if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
    219             else: return n
    220 
    221         str = str.strip()
    222         if str.startswith('(') and str.endswith(')'):
    223             str = str[1:-1]
    224             names = [ s.strip() for s in str.split(",")]
    225             if len(names) > 3:
    226                 raise self.parse_error("More than three fields in name")
    227             first = names[0].split(":")
    228             if first == 'fedid:':
    229                 del first[0]
    230                 first[0] = fedid(hexstr=first[0])
    231             names[0] = first[0]
    232 
    233             for i in range(1,2):
    234                 names[i] = parse_name(names[i])
    235 
    236             return tuple(names)
    237         else:
    238             raise self.parse_error('Bad mapping (unbalanced parens)')
    239 
    240175    @staticmethod
    241176    def access_tuple(str):
     
    243178        Convert a string of the form (id, id) into an access_project.  This is
    244179        called by read_access to convert to local attributes.  It returns
    245         a tuple of the form (project, user, user) where the two users are
    246         always the same.
     180        a tuple of the form (project, user).
    247181        """
    248182
     
    251185            # The slice takes the parens off the string.
    252186            proj, user = str[1:-1].split(',')
    253             return (proj.strip(), user.strip(), user.strip())
     187            return (proj.strip(), user.strip())
    254188        else:
    255189            raise self.parse_error(
     
    258192    # RequestAccess support routines
    259193
    260     def legacy_lookup_access(self, req, fid):
    261         """
    262         Look up the local access control information mapped to this fedid and
    263         credentials.  In this case it is a (project, create_user, access_user)
    264         triple, and a triple of three booleans indicating which, if any will
    265         need to be dynamically created.  Finally a list of owners for that
    266         allocation is returned.
    267 
    268         lookup_access_base pulls the first triple out, and it is parsed by this
    269         routine into the boolean map.  Owners is always the controlling fedid.
    270         """
    271         # Return values
    272         rp = None
    273         ru = None
    274         # This maps a valid user to the Emulab projects and users to use
    275         found, match = self.legacy_lookup_access_base(req, fid)
    276         tb, project, user = match
    277        
    278         if found == None:
    279             raise service_error(service_error.access,
    280                     "Access denied - cannot map access")
    281 
    282         # resolve <dynamic> and <same> in found
    283         dyn_proj = False
    284         dyn_create_user = False
    285         dyn_service_user = False
    286 
    287         if found[0] == "<same>":
    288             if project != None:
    289                 rp = project
    290             else :
    291                 raise service_error(\
    292                         service_error.server_config,
    293                         "Project matched <same> when no project given")
    294         elif found[0] == "<dynamic>":
    295             rp = None
    296             dyn_proj = True
    297         else:
    298             rp = found[0]
    299 
    300         if found[1] == "<same>":
    301             if user_match == "<any>":
    302                 if user != None: rcu = user[0]
    303                 else: raise service_error(\
    304                         service_error.server_config,
    305                         "Matched <same> on anonymous request")
    306             else:
    307                 rcu = user_match
    308         elif found[1] == "<dynamic>":
    309             rcu = None
    310             dyn_create_user = True
    311         else:
    312             rcu = found[1]
    313        
    314         if found[2] == "<same>":
    315             if user_match == "<any>":
    316                 if user != None: rsu = user[0]
    317                 else: raise service_error(\
    318                         service_error.server_config,
    319                         "Matched <same> on anonymous request")
    320             else:
    321                 rsu = user_match
    322         elif found[2] == "<dynamic>":
    323             rsu = None
    324             dyn_service_user = True
    325         else:
    326             rsu = found[2]
    327 
    328         return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
    329                 [ fid ]
    330 
    331     def do_project_allocation(self, dyn, project, user):
    332         """
    333         Call the project allocation routines and return the info.
    334         """
    335         if dyn:
    336             # Compose the dynamic project request
    337             # (only dynamic, dynamic currently allowed)
    338             preq = { 'AllocateProjectRequestBody': \
    339                         { 'project' : {\
    340                             'user': [ \
    341                             { \
    342                                 'access': [ {
    343                                     'sshPubkey': self.ssh_pubkey_file } ],
    344                                  'role': "serviceAccess",\
    345                             }, \
    346                             { \
    347                                 'access': [ {
    348                                     'sshPubkey': self.ssh_pubkey_file } ],
    349                                  'role': "experimentCreation",\
    350                             }, \
    351                             ], \
    352                             }\
    353                         }\
    354                     }
    355             return self.allocate_project.dynamic_project(preq)
    356         else:
    357             preq = {'StaticProjectRequestBody' : \
    358                     { 'project': \
    359                         { 'name' : { 'localname' : project },\
    360                           'user' : [ \
    361                             {\
    362                                 'userID': { 'localname' : user }, \
    363                                 'access': [ {
    364                                     'sshPubkey': self.ssh_pubkey_file } ],
    365                                 'role': 'experimentCreation'\
    366                             },\
    367                             {\
    368                                 'userID': { 'localname' : user}, \
    369                                 'access': [ {
    370                                     'sshPubkey': self.ssh_pubkey_file } ],
    371                                 'role': 'serviceAccess'\
    372                             },\
    373                         ]}\
    374                     }\
    375             }
    376             return self.allocate_project.static_project(preq)
    377 
    378     def save_project_state(self, aid, ap, dyn, owners):
    379         """
    380         Parse out and save the information relevant to the project created for
    381         this experiment.  That info is largely in ap and owners.  dyn indicates
    382         that the project was created dynamically.  Return the user and project
    383         names.
     194    def save_project_state(self, aid, pname, uname, owners):
     195        """
     196        Save the project, user, and owners associated with this allocation.
     197        This creates the allocation entry.
    384198        """
    385199        self.state_lock.acquire()
    386200        self.allocation[aid] = { }
    387         self.allocation[aid]['auth'] = set()
    388         try:
    389             pname = ap['project']['name']['localname']
    390         except KeyError:
    391             pname = None
    392 
    393         if dyn:
    394             if not pname:
    395                 self.state_lock.release()
    396                 raise service_error(service_error.internal,
    397                         "Misformed allocation response?")
    398             if pname in self.projects: self.projects[pname] += 1
    399             else: self.projects[pname] = 1
    400             self.allocation[aid]['project'] = pname
    401         else:
    402             # sproject is a static project associated with this allocation.
    403             self.allocation[aid]['sproject'] = pname
    404 
    405         self.allocation[aid]['keys'] = [ ]
    406 
    407         try:
    408             for u in ap['project']['user']:
    409                 uname = u['userID']['localname']
    410                 if u['role'] == 'experimentCreation':
    411                     self.allocation[aid]['user'] = uname
    412                 for k in [ k['sshPubkey'] for k in u['access'] \
    413                         if k.has_key('sshPubkey') ]:
    414                     kv = "%s:%s" % (uname, k)
    415                     if self.keys.has_key(kv): self.keys[kv] += 1
    416                     else: self.keys[kv] = 1
    417                     self.allocation[aid]['keys'].append((uname, k))
    418         except KeyError:
    419             self.state_lock.release()
    420             raise service_error(service_error.internal,
    421                     "Misformed allocation response?")
    422 
     201        self.allocation[aid]['project'] = pname
     202        self.allocation[aid]['user'] = uname
    423203        self.allocation[aid]['owners'] = owners
    424204        self.write_state()
     
    481261            self.auth.save()
    482262
    483         if self.auth_type == "legacy":
    484             found, dyn, owners= self.legacy_lookup_access(req, fid)
    485             proof = access_proof("me", fid, "create")
    486         elif self.auth_type == 'abac':
    487             found, dyn, owners, proof = self.lookup_access(req, fid, filter=pf)
     263        if self.auth_type == 'abac':
     264            found, owners, proof = self.lookup_access(req, fid, filter=pf)
    488265        else:
    489266            raise service_error(service_error.internal,
     
    491268        ap = None
    492269
    493         # This only happens in legacy lookups, but if this user has access to
    494         # the testbed but not the project to be exported, raise the error.
    495         if ep and ep != found[0]:
    496             raise service_error(service_error.access,
    497                     "Cannot export %s" % ep)
    498 
    499         if self.ssh_pubkey_file:
    500             ap = self.do_project_allocation(dyn[1], found[0], found[1])
    501         else:
    502             raise service_error(service_error.internal,
    503                     "SSH access parameters required")
    504270        # keep track of what's been added
    505271        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
    506272        aid = unicode(allocID)
    507273
    508         pname, uname = self.save_project_state(aid, ap, dyn[1], owners)
     274        pname, uname = self.save_project_state(aid, found[0], found[1], owners)
    509275
    510276        services, svc_state = self.export_services(req.get('service',[]),
     
    526292                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
    527293        resp = self.build_access_response({ 'fedid': allocID } ,
    528                 ap, services, proof)
     294                pname, services, proof)
    529295        return resp
    530 
    531     def do_release_project(self, del_project, del_users, del_types):
    532         """
    533         If a project and users has to be deleted, make the call.
    534         """
    535         msg = { 'project': { }}
    536         if del_project:
    537             msg['project']['name']= {'localname': del_project}
    538         users = [ ]
    539         for u in del_users.keys():
    540             users.append({ 'userID': { 'localname': u },\
    541                 'access' :  \
    542                         [ {'sshPubkey' : s } for s in del_users[u]]\
    543             })
    544         if users:
    545             msg['project']['user'] = users
    546         if len(del_types) > 0:
    547             msg['resources'] = { 'node': \
    548                     [ {'hardware': [ h ] } for h in del_types ]\
    549                 }
    550         if self.allocate_project.release_project:
    551             msg = { 'ReleaseProjectRequestBody' : msg}
    552             self.allocate_project.release_project(msg)
    553296
    554297    def ReleaseAccess(self, req, fid):
     
    579322            raise service_error(service_error.access, "Access Denied")
    580323
    581         # If we know this allocation, reduce the reference counts and
    582         # remove the local allocations.  Otherwise report an error.  If
    583         # there is an allocation to delete, del_users will be a dictonary
    584         # of sets where the key is the user that owns the keys in the set.
    585         # We use a set to avoid duplicates.  del_project is just the name
    586         # of any dynamic project to delete.  We're somewhat lazy about
    587         # deleting authorization attributes.  Having access to something
    588         # that doesn't exist isn't harmful.
    589         del_users = { }
    590         del_project = None
    591         del_types = set()
    592 
    593324        self.state_lock.acquire()
    594325        if aid in self.allocation:
    595326            self.log.debug("Found allocation for %s" %aid)
    596327            self.clear_allocation_authorization(aid, state_attr='allocation')
    597             for k in self.allocation[aid]['keys']:
    598                 kk = "%s:%s" % k
    599                 self.keys[kk] -= 1
    600                 if self.keys[kk] == 0:
    601                     if not del_users.has_key(k[0]):
    602                         del_users[k[0]] = set()
    603                     del_users[k[0]].add(k[1])
    604                     del self.keys[kk]
    605 
    606             if 'project' in self.allocation[aid]:
    607                 pname = self.allocation[aid]['project']
    608                 self.projects[pname] -= 1
    609                 if self.projects[pname] == 0:
    610                     del_project = pname
    611                     del self.projects[pname]
    612 
    613             if 'types' in self.allocation[aid]:
    614                 for t in self.allocation[aid]['types']:
    615                     self.types[t] -= 1
    616                     if self.types[t] == 0:
    617                         if not del_project: del_project = t[0]
    618                         del_types.add(t[1])
    619                         del self.types[t]
    620 
    621328            del self.allocation[aid]
    622329            self.write_state()
    623330            self.state_lock.release()
    624             # If we actually have resources to deallocate, prepare the call.
    625             if del_project or del_users:
    626                 self.do_release_project(del_project, del_users, del_types)
    627             # And remove the access cert
     331            # Remove the access cert
    628332            cf = "%s/%s.pem" % (self.certdir, aid)
    629333            self.log.debug("Removing %s" % cf)
     
    939643        if aid in self.allocation:
    940644            proj = self.allocation[aid].get('project', None)
    941             if not proj:
    942                 proj = self.allocation[aid].get('sproject', None)
    943645        self.state_lock.release()
    944646
     
    1215917        if aid in self.allocation:
    1216918            proj = self.allocation[aid].get('project', None)
    1217             if not proj:
    1218                 proj = self.allocation[aid].get('sproject', None)
    1219919            user = self.allocation[aid].get('user', None)
    1220920            ename = self.allocation[aid].get('experiment', None)
     
    1265965            if topo: topo = topo.clone()
    1266966            proj = self.allocation[aid].get('project', None)
    1267             if not proj:
    1268                 proj = self.allocation[aid].get('sproject', None)
    1269967            user = self.allocation[aid].get('user', None)
    1270968            ename = self.allocation[aid].get('experiment', None)
Note: See TracChangeset for help on using the changeset viewer.