Changeset fa4a4a8 for fedd/federation


Ignore:
Timestamp:
May 28, 2010 4:02:42 AM (15 years ago)
Author:
Ted Faber <faber@…>
Branches:
axis_example, compt_changes, info-ops, master, version-3.01, version-3.02
Children:
c200d36
Parents:
2c1fd21
Message:

Drieve from access. Lots of redundant code excised.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • fedd/federation/deter_internal_access.py

    r2c1fd21 rfa4a4a8  
    2929import local_emulab_segment
    3030
     31from access import access_base
    3132
    3233# Make log messages disappear if noone configures a fedd logger
     
    3738fl.addHandler(nullHandler())
    3839
    39 class access:
    40     class parse_error(RuntimeError): pass
    41 
    42 
    43     proxy_RequestAccess= service_caller('RequestAccess')
    44     proxy_ReleaseAccess= service_caller('ReleaseAccess')
    45 
     40class access(access_base):
    4641    @staticmethod
    4742    def parse_vlans(v, log=None):
     
    7368        """
    7469
    75         # Make sure that the configuration is in place
    76         if not config:
    77             raise RunTimeError("No config to dragon_access.access")
    78 
    79         self.allow_proxy = False
    80         self.project_priority = config.getboolean("access", "project_priority")
    81         self.certdir = config.get("access","certdir")
    82         self.create_debug = config.getboolean("access", "create_debug")
     70        access_base.__init__(self, config, auth)
    8371        self.domain = config.get("access", "domain")
    8472        vlan_str = config.get("access", "vlans")
     
    9482        self.log = logging.getLogger("fedd.access")
    9583        set_log_level(config, "access", self.log)
    96         self.state_lock = Lock()
    97 
    98         if auth: self.auth = auth
    99         else:
    100             self.log.error(\
    101                     "[access]: No authorizer initialized, creating local one.")
    102             auth = authorizer()
    103 
    104         tb = config.get('access', 'testbed')
    105         if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
    106         else: self.testbed = [ ]
    10784
    10885        if config.has_option("access", "accessdb"):
    10986            self.read_access(config.get("access", "accessdb"))
    11087
    111         self.state_filename = config.get("access", "access_state")
    112         self.read_state()
    113 
    114         # Keep cert_file and cert_pwd coming from the same place
    115         self.cert_file = config.get("access", "cert_file")
    116         if self.cert_file:
    117             self.sert_pwd = config.get("access", "cert_pw")
    118         else:
    119             self.cert_file = config.get("globals", "cert_file")
    120             self.sert_pwd = config.get("globals", "cert_pw")
    121 
    122         self.trusted_certs = config.get("access", "trusted_certs") or \
    123                 config.get("globals", "trusted_certs")
     88        # Add the ownership attributes to the authorizer.  Note that the
     89        # indices of the allocation dict are strings, but the attributes are
     90        # fedids, so there is a conversion.
     91        self.state_lock.acquire()
     92        for k in self.state.keys():
     93            for o in self.state[k].get('owners', []):
     94                self.auth.set_attribute(o, fedid(hexstr=k))
     95            self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
     96            # If the allocation has a vlan assigned, remove it from the
     97            # available vlans
     98            v = self.state[k].get('vlan', None)
     99            if v:
     100                self.vlans.discard(v)
     101        self.state_lock.release()
     102
     103        self.lookup_access = self.lookup_access_base
    124104
    125105        self.call_GetValue= service_caller('GetValue')
     
    143123            }
    144124
    145     def read_access(self, config):
    146         """
    147         Read a configuration file and set internal parameters.
    148 
    149         There are access lines of the
    150         form (tb, proj, user) -> user that map the first tuple of
    151         names to the user for for access purposes.  Names in the key (left side)
    152         can include "<NONE> or <ANY>" to act as wildcards or to require the
    153         fields to be empty.  Similarly aproj or auser can be <SAME> or
    154         <DYNAMIC> indicating that either the matching key is to be used or a
    155         dynamic user or project will be created.  These names can also be
    156         federated IDs (fedid's) if prefixed with fedid:.  The user is the repo
    157         directory that contains the DRAGON user credentials.
    158         Testbed attributes outside the forms above can be given using the
    159         format attribute: name value: value.  The name is a single word and the
    160         value continues to the end of the line.  Empty lines and lines startin
    161         with a # are ignored.
    162 
    163         Parsing errors result in a self.parse_error exception being raised.
    164         """
    165         lineno=0
    166         name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
    167         fedid_expr = "fedid:[" + string.hexdigits + "]+"
    168         key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
    169 
    170         attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
    171                 re.IGNORECASE)
    172         access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
    173                 key_name+'\s*\)\s*->\s*\(('+name_expr +')\s*\)', re.IGNORECASE)
    174 
    175         def parse_name(n):
    176             if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
    177             else: return n
    178        
    179         def auth_name(n):
    180             if isinstance(n, basestring):
    181                 if n =='<any>' or n =='<none>': return None
    182                 else: return unicode(n)
    183             else:
    184                 return n
    185 
    186         f = open(config, "r");
    187         for line in f:
    188             lineno += 1
    189             line = line.strip();
    190             if len(line) == 0 or line.startswith('#'):
    191                 continue
    192 
    193             # Extended (attribute: x value: y) attribute line
    194             m = attr_re.match(line)
    195             if m != None:
    196                 attr, val = m.group(1,2)
    197                 self.attrs[attr] = val
    198                 continue
    199 
    200             # Access line (t, p, u) -> (a) line
    201             m = access_re.match(line)
    202             if m != None:
    203                 access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
    204                 auth_key = tuple([ auth_name(x) for x in access_key])
    205                 user_name = auth_name(parse_name(m.group(4)))
    206 
    207                 self.access[access_key] = user_name
    208                 self.auth.set_attribute(auth_key, "access")
    209                 continue
    210 
    211             # Nothing matched to here: unknown line - raise exception
    212             f.close()
    213             raise self.parse_error("Unknown statement at line %d of %s" % \
    214                     (lineno, config))
    215         f.close()
    216 
    217     def write_state(self):
    218         if self.state_filename:
    219             try:
    220                 f = open(self.state_filename, 'w')
    221                 pickle.dump(self.state, f)
    222             except EnvironmentError, e:
    223                 self.log.error("Can't write file %s: %s" % \
    224                         (self.state_filename, e))
    225             except pickle.PicklingError, e:
    226                 self.log.error("Pickling problem: %s" % e)
    227             except TypeError, e:
    228                 self.log.error("Pickling problem (TypeError): %s" % e)
    229 
    230 
    231     def read_state(self):
    232         """
    233         Read a new copy of access state.  Old state is overwritten.
    234 
    235         State format is a simple pickling of the state dictionary.
    236         """
    237         if self.state_filename:
    238             try:
    239                 f = open(self.state_filename, "r")
    240                 self.state = pickle.load(f)
    241                 self.log.debug("[read_state]: Read state from %s" % \
    242                         self.state_filename)
    243             except EnvironmentError, e:
    244                 self.log.warning(("[read_state]: No saved state: " +\
    245                         "Can't open %s: %s") % (self.state_filename, e))
    246             except EOFError, e:
    247                 self.log.warning(("[read_state]: " +\
    248                         "Empty or damaged state file: %s:") % \
    249                         self.state_filename)
    250             except pickle.UnpicklingError, e:
    251                 self.log.warning(("[read_state]: No saved state: " + \
    252                         "Unpickling failed: %s") % e)
    253 
    254             # Add the ownership attributes to the authorizer.  Note that the
    255             # indices of the allocation dict are strings, but the attributes are
    256             # fedids, so there is a conversion.
    257             for k in self.state.keys():
    258                 for o in self.state[k].get('owners', []):
    259                     self.auth.set_attribute(o, fedid(hexstr=k))
    260                 self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
    261                 # If the allocation has a vlan assigned, remove it from the
    262                 # available vlans
    263                 v = self.state[k].get('vlan', None)
    264                 if v:
    265                     self.vlans.discard(v)
    266 
    267 
    268 
    269     def permute_wildcards(self, a, p):
    270         """Return a copy of a with various fields wildcarded.
    271 
    272         The bits of p control the wildcards.  A set bit is a wildcard
    273         replacement with the lowest bit being user then project then testbed.
    274         """
    275         if p & 1: user = ["<any>"]
    276         else: user = a[2]
    277         if p & 2: proj = "<any>"
    278         else: proj = a[1]
    279         if p & 4: tb = "<any>"
    280         else: tb = a[0]
    281 
    282         return (tb, proj, user)
    283 
    284     def find_access(self, search):
    285         """
    286         Search the access DB for a match on this tuple.  Return the matching
    287         user (repo dir).
    288        
    289         NB, if the initial tuple fails to match we start inserting wildcards in
    290         an order determined by self.project_priority.  Try the list of users in
    291         order (when wildcarded, there's only one user in the list).
    292         """
    293         if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
    294         else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
    295 
    296         for p in perm:
    297             s = self.permute_wildcards(search, p)
    298             # s[2] is None on an anonymous, unwildcarded request
    299             if s[2] != None:
    300                 for u in s[2]:
    301                     if self.access.has_key((s[0], s[1], u)):
    302                         return self.access[(s[0], s[1], u)]
    303             else:
    304                 if self.access.has_key(s):
    305                     return self.access[s]
    306         return None
    307 
    308     def lookup_access(self, req, fid):
    309         """
    310         Determine the allowed access for this request.  Return the access and
    311         which fields are dynamic.
    312 
    313         The fedid is needed to construct the request
    314         """
    315         # Search keys
    316         tb = None
    317         project = None
    318         user = None
    319         # Return values
    320         rp = access_project(None, ())
    321         ru = None
    322         user_re = re.compile("user:\s(.*)")
    323         project_re = re.compile("project:\s(.*)")
    324 
    325         user = [ user_re.findall(x)[0] for x in req.get('credential', []) \
    326                 if user_re.match(x)]
    327         project = [ project_re.findall(x)[0] \
    328                 for x in req.get('credential', []) \
    329                     if project_re.match(x)]
    330 
    331         if len(project) == 1: project = project[0]
    332         elif len(project) == 0: project = None
    333         else:
    334             raise service_error(service_error.req,
    335                     "More than one project credential")
    336 
    337 
    338         user_fedids = [ u for u in user if isinstance(u, fedid)]
    339 
    340         # Determine how the caller is representing itself.  If its fedid shows
    341         # up as a project or a singleton user, let that stand.  If neither the
    342         # usernames nor the project name is a fedid, the caller is a testbed.
    343         if project and isinstance(project, fedid):
    344             if project == fid:
    345                 # The caller is the project (which is already in the tuple
    346                 # passed in to the authorizer)
    347                 owners = user_fedids
    348                 owners.append(project)
    349             else:
    350                 raise service_error(service_error.req,
    351                         "Project asserting different fedid")
    352         else:
    353             if fid not in user_fedids:
    354                 tb = fid
    355                 owners = user_fedids
    356                 owners.append(fid)
    357             else:
    358                 if len(fedids) > 1:
    359                     raise service_error(service_error.req,
    360                             "User asserting different fedid")
    361                 else:
    362                     # Which is a singleton
    363                     owners = user_fedids
    364         # Confirm authorization
    365 
    366         for u in user:
    367             self.log.debug("[lookup_access] Checking access for %s" % \
    368                     ((tb, project, u),))
    369             if self.auth.check_attribute((tb, project, u), 'access'):
    370                 self.log.debug("[lookup_access] Access granted")
    371                 break
    372             else:
    373                 self.log.debug("[lookup_access] Access Denied")
    374         else:
    375             raise service_error(service_error.access, "Access denied")
    376 
    377         # This maps a valid user to the Emulab projects and users to use
    378         found = self.find_access((tb, project, user))
    379        
    380         if found == None:
    381             raise service_error(service_error.access,
    382                     "Access denied - cannot map access")
    383         return found, owners
    384 
    385125    def RequestAccess(self, req, fid):
    386126        """
     
    397137            raise service_error(service_error.req, "No request!?")
    398138
    399         if req.has_key('destinationTestbed'):
    400             dt = unpack_id(req['destinationTestbed'])
    401 
    402         if dt == None or dt in self.testbed:
    403             # Request for this fedd
    404             found, owners = self.lookup_access(req, fid)
    405             # keep track of what's been added
    406             allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
    407             aid = unicode(allocID)
    408 
    409             self.state_lock.acquire()
    410             self.state[aid] = { }
    411             self.state[aid]['user'] = found
    412             self.state[aid]['owners'] = owners
    413             self.state[aid]['vlan'] = None
    414             self.write_state()
    415             self.state_lock.release()
    416             for o in owners:
    417                 self.auth.set_attribute(o, allocID)
    418             self.auth.set_attribute(allocID, allocID)
    419 
    420             try:
    421                 f = open("%s/%s.pem" % (self.certdir, aid), "w")
    422                 print >>f, alloc_cert
    423                 f.close()
    424             except EnvironmentError, e:
    425                 raise service_error(service_error.internal,
    426                         "Can't open %s/%s : %s" % (self.certdir, aid, e))
    427             return { 'allocID': { 'fedid': allocID } }
    428         else:
    429             if self.allow_proxy:
    430                 resp = self.proxy_RequestAccess.call_service(dt, req,
    431                             self.cert_file, self.cert_pwd,
    432                             self.trusted_certs)
    433                 if resp.has_key('RequestAccessResponseBody'):
    434                     return resp['RequestAccessResponseBody']
    435                 else:
    436                     return None
    437             else:
    438                 raise service_error(service_error.access,
    439                         "Access proxying denied")
     139        found, match = self.lookup_access(req, fid)
     140        # keep track of what's been added
     141        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
     142        aid = unicode(allocID)
     143
     144        self.state_lock.acquire()
     145        self.state[aid] = { }
     146        self.state[aid]['user'] = found
     147        self.state[aid]['owners'] = [ fid ]
     148        self.state[aid]['vlan'] = None
     149        self.write_state()
     150        self.state_lock.release()
     151        self.auth.set_attribute(fid, allocID)
     152        self.auth.set_attribute(allocID, allocID)
     153
     154        try:
     155            f = open("%s/%s.pem" % (self.certdir, aid), "w")
     156            print >>f, alloc_cert
     157            f.close()
     158        except EnvironmentError, e:
     159            raise service_error(service_error.internal,
     160                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
     161        return { 'allocID': { 'fedid': allocID } }
    440162
    441163    def ReleaseAccess(self, req, fid):
     
    446168            raise service_error(service_error.req, "No request!?")
    447169
    448         if req.has_key('destinationTestbed'):
    449             dt = unpack_id(req['destinationTestbed'])
    450         else:
    451             dt = None
    452 
    453         if dt == None or dt in self.testbed:
    454             # Local request
    455             try:
    456                 if req['allocID'].has_key('localname'):
    457                     auth_attr = aid = req['allocID']['localname']
    458                 elif req['allocID'].has_key('fedid'):
    459                     aid = unicode(req['allocID']['fedid'])
    460                     auth_attr = req['allocID']['fedid']
    461                 else:
    462                     raise service_error(service_error.req,
    463                             "Only localnames and fedids are understood")
    464             except KeyError:
    465                 raise service_error(service_error.req, "Badly formed request")
    466 
    467             self.log.debug("[access] deallocation requested for %s", aid)
    468             if not self.auth.check_attribute(fid, auth_attr):
    469                 self.log.debug("[access] deallocation denied for %s", aid)
    470                 raise service_error(service_error.access, "Access Denied")
    471 
    472             self.state_lock.acquire()
    473             if self.state.has_key(aid):
    474                 self.log.debug("Found allocation for %s" %aid)
    475                 del self.state[aid]
    476                 self.write_state()
    477                 self.state_lock.release()
    478                 # And remove the access cert
    479                 cf = "%s/%s.pem" % (self.certdir, aid)
    480                 self.log.debug("Removing %s" % cf)
    481                 os.remove(cf)
    482                 return { 'allocID': req['allocID'] }
    483             else:
    484                 self.state_lock.release()
    485                 raise service_error(service_error.req, "No such allocation")
    486 
    487         else:
    488             if self.allow_proxy:
    489                 resp = self.proxy_ReleaseAccess.call_service(dt, req,
    490                             self.cert_file, self.cert_pwd,
    491                             self.trusted_certs)
    492                 if resp.has_key('ReleaseAccessResponseBody'):
    493                     return resp['ReleaseAccessResponseBody']
    494                 else:
    495                     return None
    496             else:
    497                 raise service_error(service_error.access,
    498                         "Access proxying denied")
     170        # Local request
     171        try:
     172            if req['allocID'].has_key('localname'):
     173                auth_attr = aid = req['allocID']['localname']
     174            elif req['allocID'].has_key('fedid'):
     175                aid = unicode(req['allocID']['fedid'])
     176                auth_attr = req['allocID']['fedid']
     177            else:
     178                raise service_error(service_error.req,
     179                        "Only localnames and fedids are understood")
     180        except KeyError:
     181            raise service_error(service_error.req, "Badly formed request")
     182
     183        self.log.debug("[access] deallocation requested for %s", aid)
     184        if not self.auth.check_attribute(fid, auth_attr):
     185            self.log.debug("[access] deallocation denied for %s", aid)
     186            raise service_error(service_error.access, "Access Denied")
     187
     188        self.state_lock.acquire()
     189        if self.state.has_key(aid):
     190            self.log.debug("Found allocation for %s" %aid)
     191            del self.state[aid]
     192            self.write_state()
     193            self.state_lock.release()
     194            # And remove the access cert
     195            cf = "%s/%s.pem" % (self.certdir, aid)
     196            self.log.debug("Removing %s" % cf)
     197            os.remove(cf)
     198            return { 'allocID': req['allocID'] }
     199        else:
     200            self.state_lock.release()
     201            raise service_error(service_error.req, "No such allocation")
    499202
    500203    def extract_parameters(self, top):
    501204        """
    502         DRAGON currently supports a fixed capacity link between two endpoints.
    503         Those endpoints may specify a VPN (or list or range) to use.  This
    504         extracts the DRAGON endpoints and vpn preferences from the segments (as
    505         attributes) and the capacity of the connection as given by the
     205        DETER's internal networking currently supports a fixed capacity link
     206        between two endpoints.  Those endpoints may specify a VPN (or list or
     207        range) to use.  This extracts the and vpn preferences from the segments
     208        (as attributes) and the capacity of the connection as given by the
    506209        substrate.  The two endpoints VLAN choices are intersected to get set
    507210        of VLANs that are acceptable (no VLAN requiremnets means any is
     
    556259                    self.log.error("Bad export request: %s" % p)
    557260
    558     def import_store_info(self, cf, connInfo):
    559         """
    560         Pull any import parameters in connInfo in.  We translate them either
    561         into known member names or fedAddrs.
    562         """
    563 
    564         for c in connInfo:
    565             for p in [ p for p in c.get('parameter', []) \
    566                     if p.get('type', '') == 'input']:
    567                 name = p.get('name', None)
    568                 key = p.get('key', None)
    569                 store = p.get('store', None)
    570 
    571                 if name and key and store :
    572                     req = { 'name': key, 'wait': True }
    573                     r = self.call_GetValue(store, req, cf)
    574                     r = r.get('GetValueResponseBody', None)
    575                     if r :
    576                         if r.get('name', '') == key:
    577                             v = r.get('value', None)
    578                             if v is not None:
    579                                 if name == 'peer':
    580                                     c['peer'] = v
    581                                 else:
    582                                     if c.has_key('fedAttr'):
    583                                         c['fedAttr'].append({
    584                                             'attribute': name, 'value': value})
    585                                     else:
    586                                         c['fedAttr']= [{
    587                                             'attribute': name, 'value': value}]
    588                             else:
    589                                 raise service_error(service_error.internal,
    590                                         'None value exported for %s'  % key)
    591                         else:
    592                             raise service_error(service_error.internal,
    593                                     'Different name returned for %s: %s' \
    594                                             % (key, r.get('name','')))
    595                     else:
    596                         raise service_error(service_error.internal,
    597                             'Badly formatted response: no GetValueResponseBody')
    598                 else:
    599                     raise service_error(service_error.internal,
    600                         'Bad Services missing info for import %s' % c)
    601 
    602 
    603261    def StartSegment(self, req, fid):
    604262        err = None  # Any service_error generated after tmpdir is created
     
    607265        try:
    608266            req = req['StartSegmentRequestBody']
     267            topref = req['segmentdescription']['topdldescription']
    609268        except KeyError:
    610269            raise service_error(server_error.req, "Badly formed request")
     
    629288        certfile = "%s/%s.pem" % (self.certdir, aid)
    630289
    631         if req.has_key('segmentdescription') and \
    632                 req['segmentdescription'].has_key('topdldescription'):
    633             topo = \
    634                 topdl.Topology(**req['segmentdescription']['topdldescription'])
     290        if topref: topo = topdl.Topology(**topref)
    635291        else:
    636292            raise service_error(service_error.req,
     
    656312
    657313
     314        # This annotation isn't strictly necessary, but may help in debugging
    658315        rtopo = topo.clone()
    659316        for s in rtopo.substrates:
     
    671328                'allocID': req['allocID'],
    672329                'allocationLog': logv,
    673                 'segmentdescription': { 'topdldescription': topo.to_dict() }
     330                'segmentdescription': { 'topdldescription': rtopo.to_dict() }
    674331                }
    675332        retval = copy.deepcopy(self.state[aid]['started'])
Note: See TracChangeset for help on using the changeset viewer.