Changeset fa4a4a8 for fedd/federation
- Timestamp:
- May 28, 2010 4:02:42 AM (14 years ago)
- Branches:
- axis_example, compt_changes, info-ops, master, version-3.01, version-3.02
- Children:
- c200d36
- Parents:
- 2c1fd21
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
fedd/federation/deter_internal_access.py
r2c1fd21 rfa4a4a8 29 29 import local_emulab_segment 30 30 31 from access import access_base 31 32 32 33 # Make log messages disappear if noone configures a fedd logger … … 37 38 fl.addHandler(nullHandler()) 38 39 39 class access: 40 class parse_error(RuntimeError): pass 41 42 43 proxy_RequestAccess= service_caller('RequestAccess') 44 proxy_ReleaseAccess= service_caller('ReleaseAccess') 45 40 class access(access_base): 46 41 @staticmethod 47 42 def parse_vlans(v, log=None): … … 73 68 """ 74 69 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) 83 71 self.domain = config.get("access", "domain") 84 72 vlan_str = config.get("access", "vlans") … … 94 82 self.log = logging.getLogger("fedd.access") 95 83 set_log_level(config, "access", self.log) 96 self.state_lock = Lock()97 98 if auth: self.auth = auth99 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 = [ ]107 84 108 85 if config.has_option("access", "accessdb"): 109 86 self.read_access(config.get("access", "accessdb")) 110 87 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 124 104 125 105 self.call_GetValue= service_caller('GetValue') … … 143 123 } 144 124 145 def read_access(self, config):146 """147 Read a configuration file and set internal parameters.148 149 There are access lines of the150 form (tb, proj, user) -> user that map the first tuple of151 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 the153 fields to be empty. Similarly aproj or auser can be <SAME> or154 <DYNAMIC> indicating that either the matching key is to be used or a155 dynamic user or project will be created. These names can also be156 federated IDs (fedid's) if prefixed with fedid:. The user is the repo157 directory that contains the DRAGON user credentials.158 Testbed attributes outside the forms above can be given using the159 format attribute: name value: value. The name is a single word and the160 value continues to the end of the line. Empty lines and lines startin161 with a # are ignored.162 163 Parsing errors result in a self.parse_error exception being raised.164 """165 lineno=0166 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 n178 179 def auth_name(n):180 if isinstance(n, basestring):181 if n =='<any>' or n =='<none>': return None182 else: return unicode(n)183 else:184 return n185 186 f = open(config, "r");187 for line in f:188 lineno += 1189 line = line.strip();190 if len(line) == 0 or line.startswith('#'):191 continue192 193 # Extended (attribute: x value: y) attribute line194 m = attr_re.match(line)195 if m != None:196 attr, val = m.group(1,2)197 self.attrs[attr] = val198 continue199 200 # Access line (t, p, u) -> (a) line201 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_name208 self.auth.set_attribute(auth_key, "access")209 continue210 211 # Nothing matched to here: unknown line - raise exception212 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 the255 # indices of the allocation dict are strings, but the attributes are256 # 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 the262 # available vlans263 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 wildcard273 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 matching287 user (repo dir).288 289 NB, if the initial tuple fails to match we start inserting wildcards in290 an order determined by self.project_priority. Try the list of users in291 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 request299 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 None307 308 def lookup_access(self, req, fid):309 """310 Determine the allowed access for this request. Return the access and311 which fields are dynamic.312 313 The fedid is needed to construct the request314 """315 # Search keys316 tb = None317 project = None318 user = None319 # Return values320 rp = access_project(None, ())321 ru = None322 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 = None333 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 shows341 # up as a project or a singleton user, let that stand. If neither the342 # 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 tuple346 # passed in to the authorizer)347 owners = user_fedids348 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 = fid355 owners = user_fedids356 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 singleton363 owners = user_fedids364 # Confirm authorization365 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 break372 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 use378 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, owners384 385 125 def RequestAccess(self, req, fid): 386 126 """ … … 397 137 raise service_error(service_error.req, "No request!?") 398 138 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 } } 440 162 441 163 def ReleaseAccess(self, req, fid): … … 446 168 raise service_error(service_error.req, "No request!?") 447 169 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") 499 202 500 203 def extract_parameters(self, top): 501 204 """ 502 D RAGON currently supports a fixed capacity link between two endpoints.503 Those endpoints may specify a VPN (or list or range) to use. This504 extracts the DRAGON endpoints and vpn preferences from the segments (as505 attributes) and the capacity of the connection as given by the205 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 506 209 substrate. The two endpoints VLAN choices are intersected to get set 507 210 of VLANs that are acceptable (no VLAN requiremnets means any is … … 556 259 self.log.error("Bad export request: %s" % p) 557 260 558 def import_store_info(self, cf, connInfo):559 """560 Pull any import parameters in connInfo in. We translate them either561 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'] = v581 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 603 261 def StartSegment(self, req, fid): 604 262 err = None # Any service_error generated after tmpdir is created … … 607 265 try: 608 266 req = req['StartSegmentRequestBody'] 267 topref = req['segmentdescription']['topdldescription'] 609 268 except KeyError: 610 269 raise service_error(server_error.req, "Badly formed request") … … 629 288 certfile = "%s/%s.pem" % (self.certdir, aid) 630 289 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) 635 291 else: 636 292 raise service_error(service_error.req, … … 656 312 657 313 314 # This annotation isn't strictly necessary, but may help in debugging 658 315 rtopo = topo.clone() 659 316 for s in rtopo.substrates: … … 671 328 'allocID': req['allocID'], 672 329 'allocationLog': logv, 673 'segmentdescription': { 'topdldescription': topo.to_dict() }330 'segmentdescription': { 'topdldescription': rtopo.to_dict() } 674 331 } 675 332 retval = copy.deepcopy(self.state[aid]['started'])
Note: See TracChangeset
for help on using the changeset viewer.