Changeset 3551ae1 for fedd/federation
- Timestamp:
- May 28, 2010 10:12:41 AM (15 years ago)
- Branches:
- axis_example, compt_changes, info-ops, master, version-3.01, version-3.02
- Children:
- 703859f
- Parents:
- 623a2c9
- Location:
- fedd/federation
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
fedd/federation/protogeni_access.py
r623a2c9 r3551ae1 10 10 import logging 11 11 import subprocess 12 import traceback 12 13 13 14 from threading import * … … 25 26 from urlparse import urlparse 26 27 28 from access import access_base 29 27 30 import topdl 28 31 import list_log … … 37 40 fl.addHandler(nullHandler()) 38 41 39 class access :42 class access(access_base): 40 43 """ 41 44 The implementation of access control based on mapping users to projects. … … 44 47 dynamically. This implements both direct requests and proxies. 45 48 """ 46 47 class parse_error(RuntimeError): pass48 49 50 proxy_RequestAccess= service_caller('RequestAccess')51 proxy_ReleaseAccess= service_caller('ReleaseAccess')52 49 53 50 def __init__(self, config=None, auth=None): … … 56 53 """ 57 54 58 def software_list(v): 59 l = [ ] 60 if v: 61 ps = v.split(" ") 62 while len(ps): 63 loc, file = ps[0:2] 64 del ps[0:2] 65 if loc == 'rpm': 66 loc = None 67 l.append((loc, file)) 68 return l 69 70 # Make sure that the configuration is in place 71 if not config: 72 raise RunTimeError("No config to fedd.access") 73 74 self.project_priority = config.getboolean("access", "project_priority") 75 self.allow_proxy = config.getboolean("access", "allow_proxy") 55 access_base.__init__(self, config, auth) 76 56 77 57 self.domain = config.get("access", "domain") 78 self.certdir = config.get("access","certdir")79 58 self.userconfdir = config.get("access","userconfdir") 80 59 self.userconfcmd = config.get("access","userconfcmd") … … 85 64 self.sshd = config.get("access","sshd") 86 65 self.sshd_config = config.get("access", "sshd_config") 87 self.create_debug = config.getboolean("access", "create_debug")88 self.cleanup = not config.getboolean("access", "leave_tmpfiles")89 66 self.access_type = config.get("access", "type") 90 67 self.staging_dir = config.get("access", "staging_dir") or "/tmp" … … 106 83 self.node_startcommand = config.get("access", "node_startcommand") 107 84 108 self.federation_software = s oftware_list(self.federation_software)109 self.portal_software = s oftware_list(self.portal_software)110 self.local_seer_software = s oftware_list(self.local_seer_software)85 self.federation_software = self.software_list(self.federation_software) 86 self.portal_software = self.software_list(self.portal_software) 87 self.local_seer_software = self.software_list(self.local_seer_software) 111 88 112 89 self.renewal_interval = config.get("access", "renewal") or (3 * 60 ) … … 117 94 self.cm_url = config.get("access", "cm_url") 118 95 119 self.access = { }120 96 self.restricted = [ ] 121 self.projects = { } 122 self.keys = { } 123 self.types = { } 124 self.allocation = { } 125 self.state = { 126 'projects': self.projects, 127 'allocation' : self.allocation, 128 'keys' : self.keys, 129 'types': self.types 130 } 97 98 # read_state in the base_class 99 self.state_lock.acquire() 100 for a in ('allocation', 'projects', 'keys', 'types'): 101 if a not in self.state: 102 self.state[a] = { } 103 self.allocation = self.state['allocation'] 104 self.projects = self.state['projects'] 105 self.keys = self.state['keys'] 106 self.types = self.state['types'] 107 # Add the ownership attributes to the authorizer. Note that the 108 # indices of the allocation dict are strings, but the attributes are 109 # fedids, so there is a conversion. 110 for k in self.state.get('allocation', {}).keys(): 111 for o in self.state['allocation'][k].get('owners', []): 112 self.auth.set_attribute(o, fedid(hexstr=k)) 113 self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k)) 114 115 self.state_lock.release() 116 117 131 118 self.log = logging.getLogger("fedd.access") 132 119 set_log_level(config, "access", self.log) 133 self.state_lock = Lock() 134 # XXX: Configurable 135 self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig')) 136 self.imports = set(('SMB', 'seer', 'userconfig')) 137 138 if auth: self.auth = auth 139 else: 140 self.log.error(\ 141 "[access]: No authorizer initialized, creating local one.") 142 auth = authorizer() 143 144 tb = config.get('access', 'testbed') 145 if tb: self.testbed = [ t.strip() for t in tb.split(',') ] 146 else: self.testbed = [ ] 147 120 121 self.access = { } 148 122 if config.has_option("access", "accessdb"): 149 self.read_access(config.get("access", "accessdb")) 150 151 self.state_filename = config.get("access", "access_state") 152 print "Calling read_state %s" % self.state_filename 153 self.read_state() 154 155 # Keep cert_file and cert_pwd coming from the same place 156 self.cert_file = config.get("access", "cert_file") 157 if self.cert_file: 158 self.sert_pwd = config.get("access", "cert_pw") 159 else: 160 self.cert_file = config.get("globals", "cert_file") 161 self.sert_pwd = config.get("globals", "cert_pw") 162 163 self.trusted_certs = config.get("access", "trusted_certs") or \ 164 config.get("globals", "trusted_certs") 123 self.read_access(config.get("access", "accessdb"), 124 access_obj=self.make_access_info) 165 125 166 126 self.start_segment = proxy_protogeni_segment.start_segment … … 168 128 self.renew_segment = proxy_protogeni_segment.renew_segment 169 129 130 self.lookup_access = self.lookup_access_base 131 170 132 self.call_SetValue = service_caller('SetValue') 171 133 self.call_GetValue = service_caller('GetValue') 134 self.exports = { 135 'local_seer_control': self.export_local_seer, 136 'seer_master': self.export_seer_master, 137 'hide_hosts': self.export_hide_hosts, 138 } 139 140 if not self.local_seer_image or not self.local_seer_software or \ 141 not self.local_seer_start: 142 if 'local_seer_control' in self.exports: 143 del self.exports['local_seer_control'] 144 145 if not self.local_seer_image or not self.local_seer_software or \ 146 not self.seer_master_start: 147 if 'seer_master' in self.exports: 148 del self.exports['seer_master'] 172 149 173 150 self.RenewSlices() … … 190 167 } 191 168 192 def read_access(self, config): 193 """ 194 Read a configuration file and set internal parameters. 195 196 There are access lines of the 197 form (tb, proj, user) -> user that map the first tuple of 198 names to the user for for access purposes. Names in the key (left side) 199 can include "<NONE> or <ANY>" to act as wildcards or to require the 200 fields to be empty. Similarly aproj or auser can be <SAME> or 201 <DYNAMIC> indicating that either the matching key is to be used or a 202 dynamic user or project will be created. These names can also be 203 federated IDs (fedid's) if prefixed with fedid:. The user is the 204 ProtoGENI identity certificate. 205 Testbed attributes outside the forms above can be given using the 206 format attribute: name value: value. The name is a single word and the 207 value continues to the end of the line. Empty lines and lines startin 208 with a # are ignored. 209 210 Parsing errors result in a self.parse_error exception being raised. 211 """ 212 lineno=0 213 name_expr = "["+string.ascii_letters + string.digits + "\/\.\-_]+" 214 fedid_expr = "fedid:[" + string.hexdigits + "]+" 215 key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")" 216 217 access_str = '\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+ \ 218 key_name+'\s*\)\s*->\s*\(('+name_expr +')\s*,\s*('\ 219 + name_expr + ')\s*,\s*('+name_expr+')\s*,?\s*(' + \ 220 name_expr+ ')?\)' 221 access_re = re.compile(access_str, re.IGNORECASE) 222 223 224 def parse_name(n): 225 if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):]) 226 else: return n 227 228 def auth_name(n): 229 if isinstance(n, basestring): 230 if n =='<any>' or n =='<none>': return None 231 else: return unicode(n) 232 else: 233 return n 234 235 f = open(config, "r"); 236 for line in f: 237 lineno += 1 238 line = line.strip(); 239 if len(line) == 0 or line.startswith('#'): 240 continue 241 242 # Access line (t, p, u) -> (a, pw) line 243 m = access_re.match(line) 244 if m != None: 245 access_key = tuple([ parse_name(x) for x in m.group(1,2,3)]) 246 auth_key = tuple([ auth_name(x) for x in access_key]) 247 cert = auth_name(parse_name(m.group(4))) 248 user_name = auth_name(parse_name(m.group(5))) 249 ssh_key = unicode(m.group(6)) 250 if m.group(6): pw = unicode(m.group(7)) 251 else: pw = None 252 253 self.access[access_key] = (cert, user_name, ssh_key, pw) 254 self.auth.set_attribute(auth_key, "access") 255 continue 256 257 # Nothing matched to here: unknown line - raise exception 258 f.close() 259 raise self.parse_error("Unknown statement at line %d of %s" % \ 260 (lineno, config)) 261 f.close() 262 263 def write_state(self): 264 if self.state_filename: 265 try: 266 f = open(self.state_filename, 'w') 267 pickle.dump(self.state, f) 268 except EnvironmentError, e: 269 self.log.error("Can't write file %s: %s" % \ 270 (self.state_filename, e)) 271 except pickle.PicklingError, e: 272 self.log.error("Pickling problem: %s" % e) 273 except TypeError, e: 274 self.log.error("Pickling problem (TypeError): %s" % e) 275 276 277 def read_state(self): 278 """ 279 Read a new copy of access state. Old state is overwritten. 280 281 State format is a simple pickling of the state dictionary. 282 """ 283 if self.state_filename: 284 try: 285 f = open(self.state_filename, "r") 286 self.state = pickle.load(f) 287 self.log.debug("[read_state]: Read state from %s" % \ 288 self.state_filename) 289 except EnvironmentError, e: 290 self.log.warning(("[read_state]: No saved state: " +\ 291 "Can't open %s: %s") % (self.state_filename, e)) 292 except EOFError, e: 293 self.log.warning(("[read_state]: " +\ 294 "Empty or damaged state file: %s:") % \ 295 self.state_filename) 296 except pickle.UnpicklingError, e: 297 self.log.warning(("[read_state]: No saved state: " + \ 298 "Unpickling failed: %s") % e) 299 300 # Add the ownership attributes to the authorizer. Note that the 301 # indices of the allocation dict are strings, but the attributes are 302 # fedids, so there is a conversion. 303 for k in self.state.get('allocation', {}).keys(): 304 for o in self.state['allocation'][k].get('owners', []): 305 self.auth.set_attribute(o, fedid(hexstr=k)) 306 self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k)) 307 308 if self.allocation != self.state['allocation']: 309 self.allocation = self.state['allocation'] 310 311 def permute_wildcards(self, a, p): 312 """Return a copy of a with various fields wildcarded. 313 314 The bits of p control the wildcards. A set bit is a wildcard 315 replacement with the lowest bit being user then project then testbed. 316 """ 317 if p & 1: user = ["<any>"] 318 else: user = a[2] 319 if p & 2: proj = "<any>" 320 else: proj = a[1] 321 if p & 4: tb = "<any>" 322 else: tb = a[0] 323 324 return (tb, proj, user) 325 326 327 def find_access(self, search): 328 """ 329 Search the access DB for a match on this tuple. Return the matching 330 user (protoGENI cert). 331 332 NB, if the initial tuple fails to match we start inserting wildcards in 333 an order determined by self.project_priority. Try the list of users in 334 order (when wildcarded, there's only one user in the list). 335 """ 336 if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7) 337 else: perm = (0, 2, 1, 3, 4, 6, 5, 7) 338 339 for p in perm: 340 s = self.permute_wildcards(search, p) 341 # s[2] is None on an anonymous, unwildcarded request 342 if s[2] != None: 343 for u in s[2]: 344 if self.access.has_key((s[0], s[1], u)): 345 return self.access[(s[0], s[1], u)] 346 else: 347 if self.access.has_key(s): 348 return self.access[s] 349 return None 350 351 def lookup_access(self, req, fid): 352 """ 353 Determine the allowed access for this request. Return the access and 354 which fields are dynamic. 355 356 The fedid is needed to construct the request 357 """ 358 # Search keys 359 tb = None 360 project = None 361 user = None 362 # Return values 363 rp = access_project(None, ()) 364 ru = None 365 user_re = re.compile("user:\s(.*)") 366 project_re = re.compile("project:\s(.*)") 367 368 user = [ user_re.findall(x)[0] for x in req.get('credential', []) \ 369 if user_re.match(x)] 370 project = [ project_re.findall(x)[0] \ 371 for x in req.get('credential', []) \ 372 if project_re.match(x)] 373 374 if len(project) == 1: project = project[0] 375 elif len(project) == 0: project = None 376 else: 377 raise service_error(service_error.req, 378 "More than one project credential") 379 380 381 user_fedids = [ u for u in user if isinstance(u, fedid)] 382 383 # Determine how the caller is representing itself. If its fedid shows 384 # up as a project or a singleton user, let that stand. If neither the 385 # usernames nor the project name is a fedid, the caller is a testbed. 386 if project and isinstance(project, fedid): 387 if project == fid: 388 # The caller is the project (which is already in the tuple 389 # passed in to the authorizer) 390 owners = user_fedids 391 owners.append(project) 392 else: 393 raise service_error(service_error.req, 394 "Project asserting different fedid") 395 else: 396 if fid not in user_fedids: 397 tb = fid 398 owners = user_fedids 399 owners.append(fid) 400 else: 401 if len(fedids) > 1: 402 raise service_error(service_error.req, 403 "User asserting different fedid") 404 else: 405 # Which is a singleton 406 owners = user_fedids 407 # Confirm authorization 408 409 for u in user: 410 self.log.debug("[lookup_access] Checking access for %s" % \ 411 ((tb, project, u),)) 412 if self.auth.check_attribute((tb, project, u), 'access'): 413 self.log.debug("[lookup_access] Access granted") 414 break 415 else: 416 self.log.debug("[lookup_access] Access Denied") 417 else: 418 raise service_error(service_error.access, "Access denied") 419 420 # This maps a valid user to the ProtoGENI credentials to use 421 found = self.find_access((tb, project, user)) 422 423 if found == None: 424 raise service_error(service_error.access, 425 "Access denied - cannot map access") 426 return found, owners 169 @staticmethod 170 def make_access_info(s): 171 """ 172 Split a string of the form (id, id, id, id) ito its constituent tuples 173 and return them as a tuple. Use to import access info from the 174 access_db. 175 """ 176 177 ss = s.strip() 178 if ss.startswith('(') and ss.endswith(')'): 179 l = [ s.strip() for s in ss[1:-1].split(",")] 180 if len(l) == 4: 181 return tuple(l) 182 else: 183 raise self.parse_error( 184 "Exactly 4 elements in access info required") 185 else: 186 raise self.parse_error("Expecting parenthezied values") 187 427 188 428 189 def get_handler(self, path, fid): … … 433 194 return (None, None) 434 195 435 def export_userconf(self, project): 436 dev_null = None 437 confid, confcert = generate_fedid("test", dir=self.userconfdir, 438 log=self.log) 439 conffilename = "%s/%s" % (self.userconfdir, str(confid)) 440 cf = None 441 try: 442 cf = open(conffilename, "w") 443 os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR) 444 except EnvironmentError, e: 445 raise service_error(service_error.internal, 446 "Cannot create user configuration data") 447 448 try: 449 dev_null = open("/dev/null", "a") 450 except EnvironmentError, e: 451 self.log.error("export_userconf: can't open /dev/null: %s" % e) 452 453 cmd = "%s %s" % (self.userconfcmd, project) 454 conf = subprocess.call(cmd.split(" "), 455 stdout=cf, stderr=dev_null, close_fds=True) 456 457 self.auth.set_attribute(confid, "/%s" % str(confid)) 458 459 return confid, confcert 460 461 462 def export_SMB(self, id, state, project, user): 463 return { 464 'id': id, 465 'name': 'SMB', 466 'visibility': 'export', 467 'server': 'http://fs:139', 468 'fedAttr': [ 469 { 'attribute': 'SMBSHARE', 'value': 'USERS' }, 470 { 'attribute': 'SMBUSER', 'value': user }, 471 { 'attribute': 'SMBPROJ', 'value': project }, 472 ] 473 } 474 475 def export_seer(self, id, state, project, user): 476 return { 477 'id': id, 478 'name': 'seer', 479 'visibility': 'export', 480 'server': 'http://control:16606', 481 } 482 483 def export_tmcd(self, id, state, project, user): 484 return { 485 'id': id, 486 'name': 'seer', 487 'visibility': 'export', 488 'server': 'http://boss:7777', 489 } 490 491 def export_userconfig(self, id, state, project, user): 492 if self.userconfdir and self.userconfcmd \ 493 and self.userconfurl: 494 cid, cert = self.export_userconf(project) 495 state['userconfig'] = unicode(cid) 496 return { 497 'id': id, 498 'name': 'userconfig', 499 'visibility': 'export', 500 'server': "%s/%s" % (self.userconfurl, str(cid)), 501 'fedAttr': [ 502 { 'attribute': 'cert', 'value': cert }, 503 ] 504 } 505 else: 506 return None 507 508 def export_services(self, sreq, project, user): 509 exp = [ ] 510 state = { } 511 # XXX: Filthy shortcut here using http: so urlparse will give the right 512 # answers. 513 for s in sreq: 514 sname = s.get('name', '') 515 svis = s.get('visibility', '') 516 if svis == 'export': 517 if sname in self.exports: 518 id = s.get('id', 'no_id') 519 if sname == 'SMB': 520 exp.append(self.export_SMB(id, state, project, user)) 521 elif sname == 'seer': 522 exp.append(self.export_seer(id, state, project, user)) 523 elif sname == 'tmcd': 524 exp.append(self.export_tmcd(id, state, project, user)) 525 elif sname == 'userconfig': 526 exp.append(self.export_userconfig(id, state, 527 project, user)) 528 elif sname == 'project_export': 529 exp.append(self.export_SMB(id, state, project, user)) 530 exp.append(self.export_seer(id, state, project, user)) 531 exp.append(self.export_userconfig(id, state, 532 project, user)) 533 return (exp, state) 534 535 def build_response(self, alloc_id, ap, services): 196 def build_access_response(self, alloc_id, services): 536 197 """ 537 198 Create the SOAP response. … … 548 209 'fedAttr': [ 549 210 { 'attribute': 'domain', 'value': self.domain } , 550 { 'attribute': 'project', 'value':551 ap['project'].get('name', {}).get('localname', "???") },552 211 ] 553 212 } … … 556 215 'value': self.dragon_endpoint}) 557 216 if self.deter_internal: 558 print 'adding internal'559 217 msg['fedAttr'].append({'attribute': 'deter_internal', 560 218 'value': self.deter_internal}) 561 else: print "internal: %s" % self.deter_internal562 219 #XXX: ?? 563 220 if self.dragon_vlans: … … 571 228 def RequestAccess(self, req, fid): 572 229 """ 573 Handle the access request. Proxy if not for us. 574 575 Parse out the fields and make the allocations or rejections if for us, 576 otherwise, assuming we're willing to proxy, proxy the request out. 230 Handle the access request. 577 231 """ 578 232 … … 586 240 dt = unpack_id(req['destinationTestbed']) 587 241 588 if dt == None or dt in self.testbed: 589 # Request for this fedd 590 found, owners = self.lookup_access(req, fid) 591 # keep track of what's been added 592 allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log) 593 aid = unicode(allocID) 594 595 self.state_lock.acquire() 596 self.allocation[aid] = { } 597 # The protoGENI certificate 598 self.allocation[aid]['credentials'] = found 599 # The list of owner FIDs 600 self.allocation[aid]['owners'] = owners 601 self.write_state() 602 self.state_lock.release() 603 for o in owners: 604 self.auth.set_attribute(o, allocID) 605 self.auth.set_attribute(allocID, allocID) 606 607 try: 608 f = open("%s/%s.pem" % (self.certdir, aid), "w") 609 print >>f, alloc_cert 610 f.close() 611 except EnvironmentError, e: 612 raise service_error(service_error.internal, 613 "Can't open %s/%s : %s" % (self.certdir, aid, e)) 614 return { 'allocID': { 'fedid': allocID } } 615 else: 616 if self.allow_proxy: 617 resp = self.proxy_RequestAccess.call_service(dt, req, 618 self.cert_file, self.cert_pwd, 619 self.trusted_certs) 620 if resp.has_key('RequestAccessResponseBody'): 621 return resp['RequestAccessResponseBody'] 622 else: 623 return None 624 else: 625 raise service_error(service_error.access, 626 "Access proxying denied") 242 # Request for this fedd 243 found, match = self.lookup_access(req, fid) 244 services, svc_state = self.export_services(req.get('service',[]), 245 None, None) 246 # keep track of what's been added 247 allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log) 248 aid = unicode(allocID) 249 250 self.state_lock.acquire() 251 self.allocation[aid] = { } 252 # The protoGENI certificate 253 self.allocation[aid]['credentials'] = found 254 # The list of owner FIDs 255 self.allocation[aid]['owners'] = [ fid ] 256 self.write_state() 257 self.state_lock.release() 258 self.auth.set_attribute(fid, allocID) 259 self.auth.set_attribute(allocID, allocID) 260 261 try: 262 f = open("%s/%s.pem" % (self.certdir, aid), "w") 263 print >>f, alloc_cert 264 f.close() 265 except EnvironmentError, e: 266 raise service_error(service_error.internal, 267 "Can't open %s/%s : %s" % (self.certdir, aid, e)) 268 return self.build_access_response({ 'fedid': allocID }, None) 627 269 628 270 … … 634 276 raise service_error(service_error.req, "No request!?") 635 277 636 if req.has_key('destinationTestbed'): 637 dt = unpack_id(req['destinationTestbed']) 638 else: 639 dt = None 640 641 if dt == None or dt in self.testbed: 642 # Local request 643 try: 644 if req['allocID'].has_key('localname'): 645 auth_attr = aid = req['allocID']['localname'] 646 elif req['allocID'].has_key('fedid'): 647 aid = unicode(req['allocID']['fedid']) 648 auth_attr = req['allocID']['fedid'] 649 else: 650 raise service_error(service_error.req, 651 "Only localnames and fedids are understood") 652 except KeyError: 653 raise service_error(service_error.req, "Badly formed request") 654 655 self.log.debug("[access] deallocation requested for %s", aid) 656 if not self.auth.check_attribute(fid, auth_attr): 657 self.log.debug("[access] deallocation denied for %s", aid) 658 raise service_error(service_error.access, "Access Denied") 659 660 self.state_lock.acquire() 661 if self.allocation.has_key(aid): 662 self.log.debug("Found allocation for %s" %aid) 663 del self.allocation[aid] 664 self.write_state() 665 self.state_lock.release() 666 # And remove the access cert 667 cf = "%s/%s.pem" % (self.certdir, aid) 668 self.log.debug("Removing %s" % cf) 669 os.remove(cf) 670 return { 'allocID': req['allocID'] } 671 else: 672 self.state_lock.release() 673 raise service_error(service_error.req, "No such allocation") 674 675 else: 676 if self.allow_proxy: 677 resp = self.proxy_ReleaseAccess.call_service(dt, req, 678 self.cert_file, self.cert_pwd, 679 self.trusted_certs) 680 if resp.has_key('ReleaseAccessResponseBody'): 681 return resp['ReleaseAccessResponseBody'] 682 else: 683 return None 684 else: 685 raise service_error(service_error.access, 686 "Access proxying denied") 687 688 def import_store_info(self, cf, connInfo): 689 """ 690 Pull any import parameters in connInfo in. We translate them either 691 into known member names or fedAddrs. 692 """ 693 694 for c in connInfo: 695 for p in [ p for p in c.get('parameter', []) \ 696 if p.get('type', '') == 'input']: 697 name = p.get('name', None) 698 key = p.get('key', None) 699 store = p.get('store', None) 700 701 if name and key and store : 702 req = { 'name': key, 'wait': True } 703 r = self.call_GetValue(store, req, cf) 704 r = r.get('GetValueResponseBody', None) 705 if r : 706 if r.get('name', '') == key: 707 v = r.get('value', None) 708 if v is not None: 709 if name == 'peer': 710 c['peer'] = v 711 else: 712 if c.has_key('fedAttr'): 713 c['fedAttr'].append({ 714 'attribute': name, 'value': v}) 715 else: 716 c['fedAttr']= [{ 717 'attribute': name, 'value': v}] 718 else: 719 raise service_error(service_error.internal, 720 'None value exported for %s' % key) 721 else: 722 raise service_error(service_error.internal, 723 'Different name returned for %s: %s' \ 724 % (key, r.get('name',''))) 725 else: 726 raise service_error(service_error.internal, 727 'Badly formatted response: no GetValueResponseBody') 728 else: 729 raise service_error(service_error.internal, 730 'Bad Services missing info for import %s' % c) 731 732 def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 733 tmpdir, master, leid, connInfo, services): 734 735 def conninfo_to_dict(key, info): 736 """ 737 Make a cpoy of the connection information about key, and flatten it 738 into a single dict by parsing out any feddAttrs. 739 """ 740 741 rv = None 742 for i in info: 743 if key == i.get('portal', "") or \ 744 key in [e.get('element', "") \ 745 for e in i.get('member', [])]: 746 rv = i.copy() 747 break 748 278 # Local request 279 try: 280 if req['allocID'].has_key('localname'): 281 auth_attr = aid = req['allocID']['localname'] 282 elif req['allocID'].has_key('fedid'): 283 aid = unicode(req['allocID']['fedid']) 284 auth_attr = req['allocID']['fedid'] 749 285 else: 750 return rv751 752 if 'fedAttr' in rv:753 for a in rv['fedAttr']:754 attr = a.get('attribute', "")755 val = a.get('value', "")756 if attr and attr not in rv:757 rv[attr] = val758 del rv['fedAttr']759 return rv760 761 # XXX: un hardcode this762 def client_null(f, s):763 print >>f, "Service: %s" % s['name']764 765 def client_smb(f, s):766 print >>f, "Service: %s" % s['name']767 smbshare = None768 smbuser = None769 smbproj = None770 for a in s.get('fedAttr', []):771 if a.get('attribute', '') == 'SMBSHARE':772 smbshare = a.get('value', None)773 elif a.get('attribute', '') == 'SMBUSER':774 smbuser = a.get('value', None)775 elif a.get('attribute', '') == 'SMBPROJ':776 smbproj = a.get('value', None)777 778 if all((smbshare, smbuser, smbproj)):779 print >>f, "SMBshare: %s" % smbshare780 print >>f, "ProjectUser: %s" % smbuser781 print >>f, "ProjectName: %s" % smbproj782 783 client_service_out = {784 'SMB': client_smb,785 'tmcd': client_null,786 'seer': client_null,787 'userconfig': client_null,788 }789 # XXX: end un hardcode this790 791 792 seer_out = False793 client_out = False794 for e in [ e for e in topo.elements \795 if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:796 myname = e.name[0]797 type = e.get_attribute('portal_type')798 799 info = conninfo_to_dict(myname, connInfo)800 801 if not info:802 286 raise service_error(service_error.req, 803 "No connectivity info for %s" % myname) 804 805 peer = info.get('peer', "") 806 ldomain = self.domain; 807 808 mexp = info.get('masterexperiment',"") 809 mproj, meid = mexp.split("/", 1) 810 mdomain = info.get('masterdomain',"") 811 muser = info.get('masteruser','root') 812 smbshare = info.get('smbshare', 'USERS') 813 ssh_port = info.get('ssh_port', '22') 814 815 active = info.get('active', 'False') 816 817 cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower()) 818 tunnelconfig = self.tunnel_config 819 try: 820 f = open(cfn, "w") 821 if active == 'True': 822 print >>f, "active: True" 823 print >>f, "ssh_port: %s" % ssh_port 824 if type in ('control', 'both'): 825 for s in [s for s in services \ 826 if s.get('name', "") in self.imports]: 827 p = urlparse(s.get('server', 'http://localhost')) 828 print >>f, 'port: remote:%s:%s:%s' % \ 829 (p.port, p.hostname, p.port) 830 831 if tunnelconfig: 832 print >>f, "tunnelip: %s" % tunnelconfig 833 # XXX: send this an fedattr 834 #print >>f, "seercontrol: control.%s.%s%s" % \ 835 #(meid.lower(), mproj.lower(), mdomain) 836 print >>f, "peer: %s" % peer.lower() 837 print >>f, "ssh_pubkey: /usr/local/federation/etc/%s" % \ 838 pubkey_base 839 print >>f, "ssh_privkey: /usr/local/federation/etc/%s" % \ 840 secretkey_base 841 f.close() 842 except EnvironmentError, e: 843 raise service_error(service_error.internal, 844 "Can't write protal config %s: %s" % (cfn, e)) 845 846 # XXX: This little seer config file needs to go away. 847 if not seer_out: 848 try: 849 seerfn = "%s/seer.conf" % tmpdir 850 f = open(seerfn, "w") 851 if not master: 852 print >>f, "ControlNode: control.%s.%s%s" % \ 853 (meid.lower(), mproj.lower(), mdomain) 854 print >>f, "ExperimentID: %s" % mexp 855 f.close() 856 except EnvironmentError, e: 857 raise service_error(service_error.internal, 858 "Can't write seer.conf: %s" %e) 859 seer_out = True 860 861 if not client_out and type in ('control', 'both'): 862 try: 863 f = open("%s/client.conf" % tmpdir, "w") 864 print >>f, "ControlGateway: %s%s" % \ 865 (myname.lower(), ldomain.lower()) 866 for s in services: 867 if s.get('name',"") in self.imports and \ 868 s.get('visibility','') == 'import': 869 client_service_out[s['name']](f, s) 870 # Does seer need this? 871 # print >>f, "ExperimentID: %s/%s" % (mproj, meid) 872 f.close() 873 except EnvironmentError, e: 874 raise service_error(service_error.internal, 875 "Cannot write client.conf: %s" %s) 876 client_out = True 877 878 879 def generate_rspec(self, topo, softdir, master, connInfo): 287 "Only localnames and fedids are understood") 288 except KeyError: 289 raise service_error(service_error.req, "Badly formed request") 290 291 self.log.debug("[access] deallocation requested for %s", aid) 292 if not self.auth.check_attribute(fid, auth_attr): 293 self.log.debug("[access] deallocation denied for %s", aid) 294 raise service_error(service_error.access, "Access Denied") 295 296 self.state_lock.acquire() 297 if self.allocation.has_key(aid): 298 self.log.debug("Found allocation for %s" %aid) 299 del self.allocation[aid] 300 self.write_state() 301 self.state_lock.release() 302 # And remove the access cert 303 cf = "%s/%s.pem" % (self.certdir, aid) 304 self.log.debug("Removing %s" % cf) 305 os.remove(cf) 306 return { 'allocID': req['allocID'] } 307 else: 308 self.state_lock.release() 309 raise service_error(service_error.req, "No such allocation") 310 311 def generate_rspec(self, topo, softdir, connInfo): 880 312 t = topo.clone() 881 313 … … 928 360 return exp_rspec 929 361 362 def retrieve_software(self, topo, certfile, softdir): 363 """ 364 Collect the software that nodes in the topology need loaded and stage 365 it locally. This implies retrieving it from the experiment_controller 366 and placing it into softdir. Certfile is used to prove that this node 367 has access to that data (it's the allocation/segment fedid). Finally 368 local portal and federation software is also copied to the same staging 369 directory for simplicity - all software needed for experiment creation 370 is in softdir. 371 """ 372 sw = set() 373 for e in topo.elements: 374 for s in getattr(e, 'software', []): 375 sw.add(s.location) 376 os.mkdir(softdir) 377 for s in sw: 378 self.log.debug("Retrieving %s" % s) 379 try: 380 get_url(s, certfile, softdir) 381 except: 382 t, v, st = sys.exc_info() 383 raise service_error(service_error.internal, 384 "Error retrieving %s: %s" % (s, v)) 385 386 # Copy local portal node software to the tempdir 387 for s in (self.portal_software, self.federation_software): 388 for l, f in s: 389 base = os.path.basename(f) 390 copy_file(f, "%s/%s" % (softdir, base)) 391 392 # Ick. Put this python rpm in a place that it will get moved into 393 # the staging area. It's a hack to install a modern (in a Roman 394 # sense of modern) python on ProtoGENI 395 python_rpm ="python2.4-2.4-1pydotorg.i586.rpm" 396 if os.access("./%s" % python_rpm, os.R_OK): 397 copy_file("./%s" % python_rpm, "%s/%s" % (softdir, python_rpm)) 398 399 400 def initialize_experiment_info(self, attrs, aid, certfile, tmpdir): 401 """ 402 Gather common configuration files, retrieve or create an experiment 403 name and project name, and return the ssh_key filenames. Create an 404 allocation log bound to the state log variable as well. 405 """ 406 configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey')) 407 ename = None 408 pubkey_base = None 409 secretkey_base = None 410 alloc_log = None 411 412 for a in attrs: 413 if a['attribute'] in configs: 414 try: 415 self.log.debug("Retrieving %s" % a['value']) 416 get_url(a['value'], certfile, tmpdir) 417 except: 418 t, v, st = sys.exc_info() 419 raise service_error(service_error.internal, 420 "Error retrieving %s: %s" % (a.get('value', ""), v)) 421 if a['attribute'] == 'ssh_pubkey': 422 pubkey_base = a['value'].rpartition('/')[2] 423 if a['attribute'] == 'ssh_secretkey': 424 secretkey_base = a['value'].rpartition('/')[2] 425 if a['attribute'] == 'experiment_name': 426 ename = a['value'] 427 428 if not ename: 429 ename = "" 430 for i in range(0,5): 431 ename += random.choice(string.ascii_letters) 432 self.log.warn("No experiment name: picked one randomly: %s" \ 433 % ename) 434 435 self.state_lock.acquire() 436 if self.allocation.has_key(aid): 437 cf, user, ssh_key, cpw = self.allocation[aid]['credentials'] 438 self.allocation[aid]['experiment'] = ename 439 self.allocation[aid]['log'] = [ ] 440 # Create a logger that logs to the experiment's state object as 441 # well as to the main log file. 442 alloc_log = logging.getLogger('fedd.access.%s' % ename) 443 h = logging.StreamHandler( 444 list_log.list_log(self.allocation[aid]['log'])) 445 # XXX: there should be a global one of these rather than 446 # repeating the code. 447 h.setFormatter(logging.Formatter( 448 "%(asctime)s %(name)s %(message)s", 449 '%d %b %y %H:%M:%S')) 450 alloc_log.addHandler(h) 451 self.write_state() 452 else: 453 self.log.error("No allocation for %s!?" % aid) 454 self.state_lock.release() 455 456 return (ename, pubkey_base, secretkey_base, cf, user, ssh_key, 457 cpw, alloc_log) 458 459 def finalize_experiment(self, topo, starter, aid, alloc_id): 460 # Copy the assigned names into the return topology 461 rvtopo = topo.clone() 462 embedding = [ ] 463 for n in starter.node: 464 embedding.append({ 465 'toponame': n, 466 'physname': ["%s%s" % (starter.node[n], self.domain)], 467 }) 468 # Grab the log (this is some anal locking, but better safe than 469 # sorry) 470 self.state_lock.acquire() 471 logv = "".join(self.allocation[aid]['log']) 472 # It's possible that the StartSegment call gets retried (!). 473 # if the 'started' key is in the allocation, we'll return it rather 474 # than redo the setup. 475 self.allocation[aid]['started'] = { 476 'allocID': alloc_id, 477 'allocationLog': logv, 478 'segmentdescription': { 479 'topdldescription': rvtopo.to_dict() }, 480 'embedding': embedding, 481 } 482 retval = copy.deepcopy(self.allocation[aid]['started']) 483 self.write_state() 484 self.state_lock.release() 485 486 return retval 487 930 488 def StartSegment(self, req, fid): 931 932 configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))933 934 489 err = None # Any service_error generated after tmpdir is created 935 490 rv = None # Return value from segment creation … … 937 492 try: 938 493 req = req['StartSegmentRequestBody'] 494 topref = req['segmentdescription']['topdldescription'] 939 495 except KeyError: 940 496 raise service_error(service_error.req, "Badly formed request") … … 959 515 return retval 960 516 961 962 if req.has_key('segmentdescription') and \ 963 req['segmentdescription'].has_key('topdldescription'): 964 topo = \ 965 topdl.Topology(**req['segmentdescription']['topdldescription']) 517 if topref: 518 topo = topdl.Topology(**topref) 966 519 else: 967 520 raise service_error(service_error.req, 968 521 "Request missing segmentdescription'") 969 970 master = req.get('master', False)971 522 972 523 certfile = "%s/%s.pem" % (self.certdir, auth_attr) … … 979 530 # Try block alllows us to clean up temporary files. 980 531 try: 981 sw = set() 982 for e in topo.elements: 983 for s in getattr(e, 'software', []): 984 sw.add(s.location) 985 os.mkdir(softdir) 986 for s in sw: 987 self.log.debug("Retrieving %s" % s) 988 try: 989 get_url(s, certfile, softdir) 990 except: 991 t, v, st = sys.exc_info() 992 raise service_error(service_error.internal, 993 "Error retrieving %s: %s" % (s, v)) 994 995 # Copy local portal node software to the tempdir 996 for s in (self.portal_software, self.federation_software): 997 for l, f in s: 998 base = os.path.basename(f) 999 copy_file(f, "%s/%s" % (softdir, base)) 1000 1001 # Ick. Put this python rpm in a place that it will get moved into 1002 # the staging area. It's a hack to install a modern (in a Roman 1003 # sense of modern) python on ProtoGENI 1004 python_rpm ="python2.4-2.4-1pydotorg.i586.rpm" 1005 if os.access("./%s" % python_rpm, os.R_OK): 1006 copy_file("./%s" % python_rpm, "%s/%s" % (softdir, python_rpm)) 1007 1008 for a in attrs: 1009 if a['attribute'] in configs: 1010 try: 1011 self.log.debug("Retrieving %s" % a['value']) 1012 get_url(a['value'], certfile, tmpdir) 1013 except: 1014 t, v, st = sys.exc_info() 1015 raise service_error(service_error.internal, 1016 "Error retrieving %s: %s" % (s, v)) 1017 if a['attribute'] == 'ssh_pubkey': 1018 pubkey_base = a['value'].rpartition('/')[2] 1019 if a['attribute'] == 'ssh_secretkey': 1020 secretkey_base = a['value'].rpartition('/')[2] 1021 if a['attribute'] == 'experiment_name': 1022 ename = a['value'] 1023 1024 # If the userconf service was imported, collect the configuration 1025 # data. 1026 for s in services: 1027 if s.get("name", "") == 'userconfig' \ 1028 and s.get('visibility',"") == 'import': 1029 1030 # Collect ther server and certificate info. 1031 u = s.get('server', None) 1032 for a in s.get('fedAttr', []): 1033 if a.get('attribute',"") == 'cert': 1034 cert = a.get('value', None) 1035 break 1036 else: 1037 cert = None 1038 1039 if cert: 1040 # Make a temporary certificate file for get_url. The 1041 # finally clause removes it whether something goes 1042 # wrong (including an exception from get_url) or not. 1043 try: 1044 tfos, tn = tempfile.mkstemp(suffix=".pem") 1045 tf = os.fdopen(tfos, 'w') 1046 print >>tf, cert 1047 tf.close() 1048 get_url(u, tn, tmpdir, "userconf") 1049 except EnvironmentError, e: 1050 raise service_error(service.error.internal, 1051 "Cannot create temp file for " + 1052 "userconfig certificates: %s e") 1053 except: 1054 t, v, st = sys.exc_info() 1055 raise service_error(service_error.internal, 1056 "Error retrieving %s: %s" % (u, v)) 1057 finally: 1058 if tn: os.remove(tn) 1059 else: 1060 raise service_error(service_error.req, 1061 "No certificate for retreiving userconfig") 1062 break 1063 1064 self.state_lock.acquire() 1065 if self.allocation.has_key(aid): 1066 cf, user, ssh_key, cpw = self.allocation[aid]['credentials'] 1067 self.allocation[aid]['experiment'] = ename 1068 self.allocation[aid]['log'] = [ ] 1069 # Create a logger that logs to the experiment's state object as 1070 # well as to the main log file. 1071 alloc_log = logging.getLogger('fedd.access.%s' % ename) 1072 h = logging.StreamHandler( 1073 list_log.list_log(self.allocation[aid]['log'])) 1074 # XXX: there should be a global one of these rather than 1075 # repeating the code. 1076 h.setFormatter(logging.Formatter( 1077 "%(asctime)s %(name)s %(message)s", 1078 '%d %b %y %H:%M:%S')) 1079 alloc_log.addHandler(h) 1080 self.write_state() 1081 else: 1082 self.log.error("No allocation for %s!?" % aid) 1083 self.state_lock.release() 1084 532 self.retrieve_software(topo, certfile, softdir) 533 self.configure_userconf(services, tmpdir) 534 ename, pubkey_base, secretkey_base, cf, user, ssh_key, \ 535 cpw, alloc_log = self.initialize_experiment_info(attrs, 536 aid, certfile, tmpdir) 1085 537 # XXX: we really need to put the import and connection info 1086 538 # generation off longer. 1087 539 self.import_store_info(certfile, connInfo) 1088 #self.generate_portal_configs(topo, pubkey_base,1089 #secretkey_base, tmpdir, master, ename, connInfo,1090 #services)1091 540 rspec = self.generate_rspec(topo, "%s/%s/" \ 1092 % (self.staging_dir, ename), master,connInfo)541 % (self.staging_dir, ename), connInfo) 1093 542 1094 543 starter = self.start_segment(keyfile=ssh_key, … … 1097 546 cm_url=self.cm_url) 1098 547 rv = starter(self, aid, user, rspec, pubkey_base, secretkey_base, 1099 master,ename,548 ename, 1100 549 "%s/%s" % (self.staging_dir, ename), tmpdir, cf, cpw, 1101 550 certfile, topo, connInfo, services) 1102 # Copy the assigned names into the return topology 1103 rvtopo = topo.clone() 1104 for e in rvtopo.elements: 1105 if isinstance(e, topdl.Computer) and e.get_attribute('testbed'): 1106 myname = e.get_attribute('testbed') 1107 break 1108 else: myname = None 1109 1110 embedding = [ ] 1111 for n in starter.node: 1112 embedding.append({ 1113 'toponame': n, 1114 'physname': ["%s%s" % (starter.node[n], self.domain)], 1115 }) 1116 551 except EnvironmentError: 552 err = service_error(service_error.internal, "%s" % e) 1117 553 except service_error, e: 1118 554 err = e 1119 except e: 1120 err = service_error(service_error.internal, str(e)) 555 except: 556 t, v, st = sys.exc_info() 557 err = service_error(service_error.internal, "%s: %s" % \ 558 (v, traceback.extract_tb(st))) 1121 559 1122 560 # Walk up tmpdir, deleting as we go 1123 if self.cleanup: 1124 self.log.debug("[StartSegment]: removing %s" % tmpdir) 1125 for path, dirs, files in os.walk(tmpdir, topdown=False): 1126 for f in files: 1127 os.remove(os.path.join(path, f)) 1128 for d in dirs: 1129 os.rmdir(os.path.join(path, d)) 1130 os.rmdir(tmpdir) 1131 else: 1132 self.log.debug("[StartSegment]: not removing %s" % tmpdir) 561 if self.cleanup: self.remove_dirs(tmpdir) 562 else: self.log.debug("[StartSegment]: not removing %s" % tmpdir) 1133 563 1134 564 if rv: 1135 # Grab the log (this is some anal locking, but better safe than 1136 # sorry) 1137 self.state_lock.acquire() 1138 logv = "".join(self.allocation[aid]['log']) 1139 # It's possible that the StartSegment call gets retried (!). 1140 # if the 'started' key is in the allocation, we'll return it rather 1141 # than redo the setup. 1142 self.allocation[aid]['started'] = { 1143 'allocID': req['allocID'], 1144 'allocationLog': logv, 1145 'segmentdescription': { 1146 'topdldescription': rvtopo.to_dict() }, 1147 'embedding': embedding, 1148 } 1149 self.write_state() 1150 self.state_lock.release() 1151 1152 return retval 565 return self.finalize_experiment(topo, starter, aid, req['allocID']) 1153 566 elif err: 1154 567 raise service_error(service_error.federant, … … 1207 620 scred = None 1208 621 self.state_lock.release() 622 623 if not os.access(cf, os.R_OK): 624 self.log.error( 625 "[RenewSlices] cred.file %s unreadable, ignoring" % cf) 626 continue 1209 627 1210 628 # There's a ProtoGENI slice associated with the segment; renew it. -
fedd/federation/proxy_protogeni_segment.py
r623a2c9 r3551ae1 139 139 140 140 def generate_portal_configs(self, parent, topo, pubkey_base, 141 secretkey_base, tmpdir, master,leid, connInfo, services, nodes):141 secretkey_base, tmpdir, leid, connInfo, services, nodes): 142 142 143 143 def conninfo_to_dict(key, info): … … 218 218 for e in [ e for e in topo.elements \ 219 219 if isinstance(e, topdl.Computer) and e.get_attribute('portal')]: 220 myname = e.name [0]220 myname = e.name 221 221 type = e.get_attribute('portal_type') 222 222 testbed = e.get_attribute('testbed') … … 243 243 244 244 cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower()) 245 tunnelconfig = parent. attrs.has_key('TunnelCfg')245 tunnelconfig = parent.tunnel_config 246 246 try: 247 247 f = open(cfn, "w") … … 277 277 # dir. 278 278 print >>f, "ExperimentID: %s/%s" % (mproj, meid) 279 if testbed == master:280 print >>f, "SEERBase: True"281 279 f.close() 282 280 except EnvironmentError, e: … … 370 368 371 369 for e in [ e for e in topo.elements if isinstance(e, topdl.Computer)]: 372 vname = e.name [0]370 vname = e.name 373 371 node = nodes.get(vname, {}) 374 372 pname = node.get('hostname', None) … … 471 469 472 470 473 def __call__(self, parent, aid, user, rspec, pubkey, secretkey, master,471 def __call__(self, parent, aid, user, rspec, pubkey, secretkey, 474 472 ename, stagingdir, tmpdir, certfile, certpw, export_certfile, topo, 475 473 connInfo, services, timeout=0): … … 489 487 490 488 host = parent.staging_host 489 if not os.access(certfile, os.R_OK): 490 self.log.error("[start_segment]: Cannot read certfile: %s" % \ 491 certfile) 492 return False 491 493 ctxt = fedd_ssl_context(my_cert=certfile, password=certpw) 492 494 # Local software dir … … 599 601 connInfo) 600 602 self.generate_portal_configs(parent, topo, pubkey, secretkey, tmpdir, 601 master,ename, connInfo, services, nodes)603 ename, connInfo, services, nodes) 602 604 603 605 # Copy software to the staging machine (done after generation to copy … … 651 653 parent.state_lock.release() 652 654 653 # The startcmds for portals and standard nodes (the Master Slave654 # distinction is going away)655 gate_cmd = parent.attrs.get('SlaveConnectorStartCmd', '/bin/true')656 node_cmd = parent.attrs.get('SlaveNodeStartCmd', 'bin/true')657 658 655 # Now we have configuration to do for ProtoGENI 659 656 self.configure_nodes(topo, nodes, user, parent.staging_host, 660 parent.sshd, parent.sshd_config, gate_cmd, node_cmd, 657 parent.sshd, parent.sshd_config, parent.portal_startcommand, 658 parent.node_startcommand, 661 659 pubkey, secretkey, parent.federation_software, 662 660 parent.portal_software, stagingdir, tmpdir)
Note: See TracChangeset
for help on using the changeset viewer.