- Timestamp:
- May 27, 2010 2:43:38 PM (15 years ago)
- Branches:
- axis_example, compt_changes, info-ops, master, version-3.01, version-3.02
- Children:
- 8cf2b90e
- Parents:
- a65a65a
- Location:
- fedd/federation
- Files:
-
- 1 added
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
fedd/federation/emulab_access.py
ra65a65a rf771e2f 14 14 from M2Crypto.SSL import SSLError 15 15 16 from access import access_base 17 16 18 from util import * 17 19 from allocate_project import allocate_project_local, allocate_project_remote … … 39 41 fl.addHandler(nullHandler()) 40 42 41 class access :43 class access(access_base): 42 44 """ 43 45 The implementation of access control based on mapping users to projects. … … 46 48 dynamically. This implements both direct requests and proxies. 47 49 """ 48 49 class parse_error(RuntimeError): pass50 51 50 52 51 proxy_RequestAccess= service_caller('RequestAccess') … … 58 57 """ 59 58 60 def software_list(v): 61 l = [ ] 62 if v: 63 ps = v.split(" ") 64 while len(ps): 65 loc, file = ps[0:2] 66 del ps[0:2] 67 l.append((loc, file)) 68 return l 69 70 71 # Make sure that the configuration is in place 72 if not config: 73 raise RunTimeError("No config to fedd.access") 74 75 self.project_priority = config.getboolean("access", "project_priority") 59 access_base.__init__(self, config, auth) 60 76 61 self.allow_proxy = config.getboolean("access", "allow_proxy") 77 62 … … 81 66 self.fileserver = config.get("access", "fileserver") 82 67 self.eventserver = config.get("access", "eventserver") 83 self.certdir = config.get("access","certdir")84 68 self.userconfdir = config.get("access","userconfdir") 85 69 self.userconfcmd = config.get("access","userconfcmd") … … 94 78 self.ssh_pubkey_file = config.get("access","ssh_pubkey_file") 95 79 self.ssh_port = config.get("access","ssh_port") or "22" 96 self.create_debug = config.getboolean("access", "create_debug")97 self.cleanup = not config.getboolean("access", "leave_tmpfiles")98 self.access_type = config.get("access", "type")99 80 100 81 self.dragon_endpoint = config.get("access", "dragon") … … 109 90 self.node_startcommand = config.get("access", "node_startcommand") 110 91 111 self.federation_software = s oftware_list(self.federation_software)112 self.portal_software = s oftware_list(self.portal_software)113 self.local_seer_software = s oftware_list(self.local_seer_software)92 self.federation_software = self.software_list(self.federation_software) 93 self.portal_software = self.software_list(self.portal_software) 94 self.local_seer_software = self.software_list(self.local_seer_software) 114 95 115 96 self.access_type = self.access_type.lower() … … 124 105 self.stop_segment = None 125 106 126 self.access = { }127 107 self.restricted = [ ] 128 108 self.projects = { } … … 136 116 'types': self.types 137 117 } 138 self.log = logging.getLogger("fedd.access") 139 set_log_level(config, "access", self.log) 140 self.state_lock = Lock() 141 # XXX: Configurable 142 self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig', 143 'project_export', 'local_seer_control', 'seer_master', 144 'hide_hosts')) 145 self.imports = set(('SMB', 'seer', 'userconfig', 'seer_master', 146 'hide_hosts')) 118 self.access = { } 119 if config.has_option("access", "accessdb"): 120 self.read_access(config.get("access", "accessdb")) 147 121 148 122 if not self.local_seer_image or not self.local_seer_software: … … 156 130 self.exports.discard('seer_master') 157 131 158 if auth: self.auth = auth159 else:160 self.log.error(\161 "[access]: No authorizer initialized, creating local one.")162 auth = authorizer()163 164 132 tb = config.get('access', 'testbed') 165 133 if tb: self.testbed = [ t.strip() for t in tb.split(',') ] … … 167 135 168 136 if config.has_option("access", "accessdb"): 169 self.read_access(config.get("access", "accessdb")) 170 171 self.state_filename = config.get("access", "access_state") 172 self.read_state() 173 174 # Keep cert_file and cert_pwd coming from the same place 175 self.cert_file = config.get("access", "cert_file") 176 if self.cert_file: 177 self.sert_pwd = config.get("access", "cert_pw") 178 else: 179 self.cert_file = config.get("globals", "cert_file") 180 self.sert_pwd = config.get("globals", "cert_pw") 181 182 self.trusted_certs = config.get("access", "trusted_certs") or \ 183 config.get("globals", "trusted_certs") 137 self.read_access(config.get("access", "accessdb"), 138 self.make_access_project) 139 140 # read state in the base_class 141 self.state_lock.acquire() 142 self.allocation = self.state['allocation'] 143 self.projects = self.state['projects'] 144 self.keys = self.state['keys'] 145 self.types = self.state['types'] 146 # Add the ownership attributes to the authorizer. Note that the 147 # indices of the allocation dict are strings, but the attributes are 148 # fedids, so there is a conversion. 149 for k in self.allocation.keys(): 150 for o in self.allocation[k].get('owners', []): 151 self.auth.set_attribute(o, fedid(hexstr=k)) 152 if self.allocation[k].has_key('userconfig'): 153 sfid = self.allocation[k]['userconfig'] 154 fid = fedid(hexstr=sfid) 155 self.auth.set_attribute(fid, "/%s" % sfid) 156 self.state_lock.release() 157 184 158 185 159 self.soap_services = {\ … … 217 191 218 192 @staticmethod 219 def add_kit(e, kit): 220 """ 221 Add a Software object created from the list of (install, location) 222 tuples passed as kit to the software attribute of an object e. We 223 do this enough to break out the code, but it's kind of a hack to 224 avoid changing the old tuple rep. 225 """ 226 227 s = [ topdl.Software(install=i, location=l) for i, l in kit] 228 229 if isinstance(e.software, list): e.software.extend(s) 230 else: e.software = s 231 232 233 def read_access(self, config): 234 """ 235 Read a configuration file and set internal parameters. 236 237 The format is more complex than one might hope. The basic format is 238 attribute value pairs separated by colons(:) on a signle line. The 239 attributes in bool_attrs, emulab_attrs and id_attrs can all be set 240 directly using the name: value syntax. E.g. 241 boss: hostname 242 sets self.boss to hostname. In addition, there are access lines of the 243 form (tb, proj, user) -> (aproj, auser) that map the first tuple of 244 names to the second for access purposes. Names in the key (left side) 245 can include "<NONE> or <ANY>" to act as wildcards or to require the 246 fields to be empty. Similarly aproj or auser can be <SAME> or 247 <DYNAMIC> indicating that either the matching key is to be used or a 248 dynamic user or project will be created. These names can also be 249 federated IDs (fedid's) if prefixed with fedid:. Finally, the aproj 250 can be followed with a colon-separated list of node types to which that 251 project has access (or will have access if dynamic). 252 Testbed attributes outside the forms above can be given using the 253 format attribute: name value: value. The name is a single word and the 254 value continues to the end of the line. Empty lines and lines startin 255 with a # are ignored. 256 257 Parsing errors result in a self.parse_error exception being raised. 258 """ 259 lineno=0 260 name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+" 261 fedid_expr = "fedid:[" + string.hexdigits + "]+" 262 key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")" 263 access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \ 264 "<SAME>" + "(?::" + name_expr + ")*|" + \ 265 fedid_expr + "(?::" + name_expr + ")*|" + \ 266 name_expr + "(?::" + name_expr + ")*)" 267 access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")" 268 269 restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE) 270 access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+ 271 key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 272 access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE) 273 193 def make_access_project(str): 274 194 def parse_name(n): 275 195 if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):]) 276 196 else: return n 277 278 def auth_name(n): 279 if isinstance(n, basestring): 280 if n =='<any>' or n =='<none>': return None 281 else: return unicode(n) 282 else: 283 return n 284 285 f = open(config, "r"); 286 for line in f: 287 lineno += 1 288 line = line.strip(); 289 if len(line) == 0 or line.startswith('#'): 290 continue 291 292 # Restricted entry 293 m = restricted_re.match(line) 294 if m != None: 295 val = m.group(1) 296 self.restricted.append(val) 297 continue 298 299 # Access line (t, p, u) -> (ap, cu, su) line 300 m = access_re.match(line) 301 if m != None: 302 access_key = tuple([ parse_name(x) for x in m.group(1,2,3)]) 303 auth_key = tuple([ auth_name(x) for x in access_key]) 304 aps = m.group(4).split(":"); 305 if aps[0] == 'fedid:': 306 del aps[0] 307 aps[0] = fedid(hexstr=aps[0]) 308 309 cu = parse_name(m.group(5)) 310 su = parse_name(m.group(6)) 311 312 access_val = (access_project(aps[0], aps[1:]), 313 parse_name(m.group(5)), parse_name(m.group(6))) 314 315 self.access[access_key] = access_val 316 self.auth.set_attribute(auth_key, "access") 317 continue 318 319 # Nothing matched to here: unknown line - raise exception 320 f.close() 321 raise self.parse_error("Unknown statement at line %d of %s" % \ 322 (lineno, config)) 323 f.close() 324 325 def get_users(self, obj): 326 """ 327 Return a list of the IDs of the users in dict 328 """ 329 if obj.has_key('user'): 330 return [ unpack_id(u['userID']) \ 331 for u in obj['user'] if u.has_key('userID') ] 332 else: 333 return None 334 335 def write_state(self): 336 if self.state_filename: 337 try: 338 f = open(self.state_filename, 'w') 339 pickle.dump(self.state, f) 340 except EnvironmentError, e: 341 self.log.error("Can't write file %s: %s" % \ 342 (self.state_filename, e)) 343 except pickle.PicklingError, e: 344 self.log.error("Pickling problem: %s" % e) 345 except TypeError, e: 346 self.log.error("Pickling problem (TypeError): %s" % e) 347 348 349 def read_state(self): 350 """ 351 Read a new copy of access state. Old state is overwritten. 352 353 State format is a simple pickling of the state dictionary. 354 """ 355 if self.state_filename: 356 try: 357 f = open(self.state_filename, "r") 358 self.state = pickle.load(f) 359 360 self.allocation = self.state['allocation'] 361 self.projects = self.state['projects'] 362 self.keys = self.state['keys'] 363 self.types = self.state['types'] 364 365 self.log.debug("[read_state]: Read state from %s" % \ 366 self.state_filename) 367 except EnvironmentError, e: 368 self.log.warning(("[read_state]: No saved state: " +\ 369 "Can't open %s: %s") % (self.state_filename, e)) 370 except EOFError, e: 371 self.log.warning(("[read_state]: " +\ 372 "Empty or damaged state file: %s:") % \ 373 self.state_filename) 374 except pickle.UnpicklingError, e: 375 self.log.warning(("[read_state]: No saved state: " + \ 376 "Unpickling failed: %s") % e) 377 378 # Add the ownership attributes to the authorizer. Note that the 379 # indices of the allocation dict are strings, but the attributes are 380 # fedids, so there is a conversion. 381 for k in self.allocation.keys(): 382 for o in self.allocation[k].get('owners', []): 383 self.auth.set_attribute(o, fedid(hexstr=k)) 384 if self.allocation[k].has_key('userconfig'): 385 sfid = self.allocation[k]['userconfig'] 386 fid = fedid(hexstr=sfid) 387 self.auth.set_attribute(fid, "/%s" % sfid) 388 389 390 def permute_wildcards(self, a, p): 391 """Return a copy of a with various fields wildcarded. 392 393 The bits of p control the wildcards. A set bit is a wildcard 394 replacement with the lowest bit being user then project then testbed. 395 """ 396 if p & 1: user = ["<any>"] 397 else: user = a[2] 398 if p & 2: proj = "<any>" 399 else: proj = a[1] 400 if p & 4: tb = "<any>" 401 else: tb = a[0] 402 403 return (tb, proj, user) 404 405 def find_access(self, search): 406 """ 407 Search the access DB for a match on this tuple. Return the matching 408 access tuple and the user that matched. 409 410 NB, if the initial tuple fails to match we start inserting wildcards in 411 an order determined by self.project_priority. Try the list of users in 412 order (when wildcarded, there's only one user in the list). 413 """ 414 if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7) 415 else: perm = (0, 2, 1, 3, 4, 6, 5, 7) 416 417 for p in perm: 418 s = self.permute_wildcards(search, p) 419 # s[2] is None on an anonymous, unwildcarded request 420 if s[2] != None: 421 for u in s[2]: 422 if self.access.has_key((s[0], s[1], u)): 423 return (self.access[(s[0], s[1], u)], u) 424 else: 425 if self.access.has_key(s): 426 return (self.access[s], None) 427 return None, None 197 198 str = str.strip() 199 if str.startswith('(') and str.endswith(')'): 200 str = str[1:-1] 201 names = [ s.strip() for s in str.split(",")] 202 if len(names) > 3: 203 raise self.parse_error("More than three fields in name") 204 first = names[0].split(":") 205 if first == 'fedid:': 206 del first[0] 207 first[0] = fedid(hexstr=first[0]) 208 names[0] = access_project(first[0], first[1:]) 209 210 for i in range(1,2): 211 names[i] = parse_name(names[i]) 212 213 return tuple(names) 214 else: 215 raise self.parse_error('Bad mapping (unbalanced parens)') 216 428 217 429 218 def lookup_access(self, req, fid): … … 434 223 The fedid is needed to construct the request 435 224 """ 436 user_re = re.compile("user:\s(.*)")437 project_re = re.compile("project:\s(.*)")438 439 # Search keys440 tb = None441 project = None442 user = None443 225 # Return values 444 226 rp = access_project(None, ()) 445 227 ru = None 446 447 user = [ user_re.findall(x)[0] for x in req.get('credential', []) \448 if user_re.match(x)]449 project = [ project_re.findall(x)[0] \450 for x in req.get('credential', []) \451 if project_re.match(x)]452 453 if len(project) == 1: project = project[0]454 elif len(project) == 0: project = None455 else:456 raise service_error(service_error.req,457 "More than one project credential")458 459 460 user_fedids = [ u for u in user if isinstance(u, fedid)]461 # Determine how the caller is representing itself. If its fedid shows462 # up as a project or a singleton user, let that stand. If neither the463 # usernames nor the project name is a fedid, the caller is a testbed.464 if project and isinstance(project, fedid):465 if project == fid:466 # The caller is the project (which is already in the tuple467 # passed in to the authorizer)468 owners = user_fedids469 owners.append(project)470 else:471 raise service_error(service_error.req,472 "Project asserting different fedid")473 else:474 if fid not in user_fedids:475 tb = fid476 owners = user_fedids477 owners.append(fid)478 else:479 if len(fedids) > 1:480 raise service_error(service_error.req,481 "User asserting different fedid")482 else:483 # Which is a singleton484 owners = user_fedids485 # Confirm authorization486 487 for u in user:488 self.log.debug("[lookup_access] Checking access for %s" % \489 ((tb, project, u),))490 if self.auth.check_attribute((tb, project, u), 'access'):491 self.log.debug("[lookup_access] Access granted")492 break493 else:494 self.log.debug("[lookup_access] Access Denied")495 else:496 raise service_error(service_error.access, "Access denied")497 498 228 # This maps a valid user to the Emulab projects and users to use 499 found, user_match = self.find_access((tb, project, user)) 229 found, match = self.lookup_access_base(req, fid) 230 tb, project, user = match 500 231 501 232 if found == None: … … 551 282 552 283 return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\ 553 owners 554 555 def get_handler(self, path, fid): 556 self.log.info("Get handler %s %s" % (path, fid)) 557 if self.auth.check_attribute(fid, path) and self.userconfdir: 558 return ("%s/%s" % (self.userconfdir, path), "application/binary") 284 [ fid ] 285 286 def do_project_allocation(self, dyn, project, user): 287 """ 288 Call the project allocation routines and return the info. 289 """ 290 if dyn: 291 # Compose the dynamic project request 292 # (only dynamic, dynamic currently allowed) 293 preq = { 'AllocateProjectRequestBody': \ 294 { 'project' : {\ 295 'user': [ \ 296 { \ 297 'access': [ { 298 'sshPubkey': self.ssh_pubkey_file } ], 299 'role': "serviceAccess",\ 300 }, \ 301 { \ 302 'access': [ { 303 'sshPubkey': self.ssh_pubkey_file } ], 304 'role': "experimentCreation",\ 305 }, \ 306 ], \ 307 }\ 308 }\ 309 } 310 return self.allocate_project.dynamic_project(preq) 559 311 else: 560 return (None, None) 561 562 def export_userconf(self, project): 563 dev_null = None 564 confid, confcert = generate_fedid("test", dir=self.userconfdir, 565 log=self.log) 566 conffilename = "%s/%s" % (self.userconfdir, str(confid)) 567 cf = None 312 preq = {'StaticProjectRequestBody' : \ 313 { 'project': \ 314 { 'name' : { 'localname' : project },\ 315 'user' : [ \ 316 {\ 317 'userID': { 'localname' : user }, \ 318 'access': [ { 319 'sshPubkey': self.ssh_pubkey_file } ], 320 'role': 'experimentCreation'\ 321 },\ 322 {\ 323 'userID': { 'localname' : user}, \ 324 'access': [ { 325 'sshPubkey': self.ssh_pubkey_file } ], 326 'role': 'serviceAccess'\ 327 },\ 328 ]}\ 329 }\ 330 } 331 return self.allocate_project.static_project(preq) 332 333 def save_project_state(self, aid, ap, dyn, owners): 334 """ 335 Parse out and save the information relevant to the project created for 336 this experiment. That info is largely in ap and owners. dyn indicates 337 that the project was created dynamically. Return the user and project 338 names. 339 """ 340 self.state_lock.acquire() 341 self.allocation[aid] = { } 568 342 try: 569 cf = open(conffilename, "w") 570 os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR) 571 except EnvironmentError, e: 572 raise service_error(service_error.internal, 573 "Cannot create user configuration data") 343 pname = ap['project']['name']['localname'] 344 except KeyError: 345 pname = None 346 347 if dyn: 348 if not pname: 349 self.state_lock.release() 350 raise service_error(service_error.internal, 351 "Misformed allocation response?") 352 if pname in self.projects: self.projects[pname] += 1 353 else: self.projects[pname] = 1 354 self.allocation[aid]['project'] = pname 355 else: 356 # sproject is a static project associated with this allocation. 357 self.allocation[aid]['sproject'] = pname 358 359 self.allocation[aid]['keys'] = [ ] 574 360 575 361 try: 576 dev_null = open("/dev/null", "a") 577 except EnvironmentError, e: 578 self.log.error("export_userconf: can't open /dev/null: %s" % e) 579 580 cmd = "%s %s" % (self.userconfcmd, project) 581 conf = subprocess.call(cmd.split(" "), 582 stdout=cf, stderr=dev_null, close_fds=True) 583 584 self.auth.set_attribute(confid, "/%s" % str(confid)) 585 586 return confid, confcert 587 588 def export_SMB(self, id, state, project, user, attrs): 589 return { 590 'id': id, 591 'name': 'SMB', 592 'visibility': 'export', 593 'server': 'http://fs:139', 594 'fedAttr': [ 595 { 'attribute': 'SMBSHARE', 'value': 'USERS' }, 596 { 'attribute': 'SMBUSER', 'value': user }, 597 { 'attribute': 'SMBPROJ', 'value': project }, 598 ] 599 } 600 601 def export_seer(self, id, state, project, user, attrs): 602 return { 603 'id': id, 604 'name': 'seer', 605 'visibility': 'export', 606 'server': 'http://control:16606', 607 } 608 609 def export_local_seer(self, id, state, project, user, attrs): 610 return { 611 'id': id, 612 'name': 'local_seer_control', 613 'visibility': 'export', 614 'server': 'http://control:16606', 615 } 616 617 def export_seer_master(self, id, state, project, user, attrs): 618 return { 619 'id': id, 620 'name': 'seer_master', 621 'visibility': 'export', 622 'server': 'http://seer-master:17707', 623 } 624 625 def export_tmcd(self, id, state, project, user, attrs): 626 return { 627 'id': id, 628 'name': 'seer', 629 'visibility': 'export', 630 'server': 'http://boss:7777', 631 } 632 633 def export_userconfig(self, id, state, project, user, attrs): 634 if self.userconfdir and self.userconfcmd \ 635 and self.userconfurl: 636 cid, cert = self.export_userconf(project) 637 state['userconfig'] = unicode(cid) 638 return { 639 'id': id, 640 'name': 'userconfig', 641 'visibility': 'export', 642 'server': "%s/%s" % (self.userconfurl, str(cid)), 643 'fedAttr': [ 644 { 'attribute': 'cert', 'value': cert }, 645 ] 646 } 647 else: 648 return None 649 650 def export_hide_hosts(self, id, state, project, user, attrs): 651 return { 652 'id': id, 653 'name': 'hide_hosts', 654 'visibility': 'export', 655 'fedAttr': [ x for x in attrs \ 656 if x.get('attribute', "") == 'hosts'], 657 } 658 659 def export_services(self, sreq, project, user): 660 exp = [ ] 661 state = { } 662 # XXX: Filthy shortcut here using http: so urlparse will give the right 663 # answers. 664 for s in sreq: 665 sname = s.get('name', '') 666 svis = s.get('visibility', '') 667 sattrs = s.get('fedAttr', []) 668 if svis == 'export': 669 if sname in self.exports: 670 id = s.get('id', 'no_id') 671 if sname == 'SMB': 672 exp.append(self.export_SMB(id, state, project, user, 673 sattrs)) 674 elif sname == 'seer': 675 exp.append(self.export_seer(id, state, project, user, 676 sattrs)) 677 elif sname == 'tmcd': 678 exp.append(self.export_tmcd(id, state, project, user, 679 sattrs)) 680 elif sname == 'userconfig': 681 exp.append(self.export_userconfig(id, state, 682 project, user, sattrs)) 683 elif sname == 'project_export': 684 exp.append(self.export_SMB(id, state, project, user, 685 sattrs)) 686 #exp.append(self.export_seer(id, state, project, user, 687 #sattrs)) 688 exp.append(self.export_userconfig(id, state, 689 project, user, sattrs)) 690 elif sname == 'local_seer_control': 691 exp.append(self.export_local_seer(id, state, project, 692 user, sattrs)) 693 elif sname == 'seer_master': 694 exp.append(self.export_seer_master(id, state, project, 695 user, sattrs)) 696 elif sname == 'hide_hosts': 697 exp.append(self.export_hide_hosts(id, state, project, 698 user, sattrs)) 699 return (exp, state) 700 701 def build_response(self, alloc_id, ap, services): 702 """ 703 Create the SOAP response. 704 705 Build the dictionary description of the response and use 706 fedd_utils.pack_soap to create the soap message. ap is the allocate 707 project message returned from a remote project allocation (even if that 708 allocation was done locally). 709 """ 710 # Because alloc_id is already a fedd_services_types.IDType_Holder, 711 # there's no need to repack it 712 msg = { 713 'allocID': alloc_id, 714 'fedAttr': [ 715 { 'attribute': 'domain', 'value': self.domain } , 716 { 'attribute': 'project', 'value': 717 ap['project'].get('name', {}).get('localname', "???") }, 718 ] 719 } 720 721 if self.dragon_endpoint: 722 msg['fedAttr'].append({'attribute': 'dragon', 723 'value': self.dragon_endpoint}) 724 if self.deter_internal: 725 print 'adding internal' 726 msg['fedAttr'].append({'attribute': 'deter_internal', 727 'value': self.deter_internal}) 728 else: print "internal: %s" % self.deter_internal 729 #XXX: ?? 730 if self.dragon_vlans: 731 msg['fedAttr'].append({'attribute': 'vlans', 732 'value': self.dragon_vlans}) 733 734 if services: 735 msg['service'] = services 736 return msg 362 for u in ap['project']['user']: 363 uname = u['userID']['localname'] 364 if u['role'] == 'experimentCreation': 365 self.allocation[aid]['user'] = uname 366 for k in [ k['sshPubkey'] for k in u['access'] \ 367 if k.has_key('sshPubkey') ]: 368 kv = "%s:%s" % (uname, k) 369 if self.keys.has_key(kv): self.keys[kv] += 1 370 else: self.keys[kv] = 1 371 self.allocation[aid]['keys'].append((uname, k)) 372 except KeyError: 373 self.state_lock.release() 374 raise service_error(service_error.internal, 375 "Misformed allocation response?") 376 377 self.allocation[aid]['owners'] = owners 378 self.write_state() 379 self.state_lock.release() 380 return (pname, uname) 737 381 738 382 def RequestAccess(self, req, fid): … … 774 418 raise service_error(service_error.req, "No request!?") 775 419 776 if req.has_key('destinationTestbed'): 777 dt = unpack_id(req['destinationTestbed']) 778 779 if dt == None or dt in self.testbed: 780 # Request for this fedd 781 found, dyn, owners = self.lookup_access(req, fid) 782 restricted = None 783 ap = None 784 785 # if this includes a project export request and the exported 786 # project is not the access project, access denied. 787 if 'service' in req: 788 ep = get_export_project(req['service']) 789 if ep and ep != found[0].name: 790 raise service_error(service_error.access, 791 "Cannot export %s" % ep) 792 793 # XXX 794 # Check for access to restricted nodes 795 if req.has_key('resources') and req['resources'].has_key('node'): 796 resources = req['resources'] 797 restricted = [ gateway_hardware(t) for n in resources['node'] \ 798 if n.has_key('hardware') \ 799 for t in n['hardware'] \ 800 if gateway_hardware(t) \ 801 in self.restricted ] 802 inaccessible = [ t for t in restricted \ 803 if t not in found[0].node_types] 804 if len(inaccessible) > 0: 805 raise service_error(service_error.access, 806 "Access denied (nodetypes %s)" % \ 807 str(', ').join(inaccessible)) 808 # XXX 809 810 # These were passed around before, but now are hidden from users 811 # and configurators alike, beyond a configuration file entry. 812 create_ssh = [ self.ssh_pubkey_file ] 813 service_ssh = [ self.ssh_pubkey_file ] 814 815 if len(create_ssh) > 0 and len(service_ssh) >0: 816 if dyn[1]: 817 # Compose the dynamic project request 818 # (only dynamic, dynamic currently allowed) 819 preq = { 'AllocateProjectRequestBody': \ 820 { 'project' : {\ 821 'user': [ \ 822 { \ 823 'access': [ { 'sshPubkey': s } \ 824 for s in service_ssh ], 825 'role': "serviceAccess",\ 826 }, \ 827 { \ 828 'access': [ { 'sshPubkey': s } \ 829 for s in create_ssh ], 830 'role': "experimentCreation",\ 831 }, \ 832 ], \ 833 }\ 834 }\ 835 } 836 if restricted != None and len(restricted) > 0: 837 preq['AllocateProjectRequestBody']['resources'] = \ 838 {'node': [ { 'hardware' : [ h ] } \ 839 for h in restricted ] } 840 ap = self.allocate_project.dynamic_project(preq) 841 else: 842 preq = {'StaticProjectRequestBody' : \ 843 { 'project': \ 844 { 'name' : { 'localname' : found[0].name },\ 845 'user' : [ \ 846 {\ 847 'userID': { 'localname' : found[1] }, \ 848 'access': [ { 'sshPubkey': s } 849 for s in create_ssh ], 850 'role': 'experimentCreation'\ 851 },\ 852 {\ 853 'userID': { 'localname' : found[2] }, \ 854 'access': [ { 'sshPubkey': s } 855 for s in service_ssh ], 856 'role': 'serviceAccess'\ 857 },\ 858 ]}\ 859 }\ 860 } 861 if restricted != None and len(restricted) > 0: 862 preq['StaticProjectRequestBody']['resources'] = \ 863 {'node': [ { 'hardware' : [ h ] } \ 864 for h in restricted ] } 865 ap = self.allocate_project.static_project(preq) 866 else: 867 raise service_error(service_error.req, 868 "SSH access parameters required") 869 # keep track of what's been added 870 allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log) 871 aid = unicode(allocID) 872 873 self.state_lock.acquire() 874 self.allocation[aid] = { } 875 try: 876 pname = ap['project']['name']['localname'] 877 except KeyError: 878 pname = None 879 880 if dyn[1]: 881 if not pname: 882 self.state_lock.release() 883 raise service_error(service_error.internal, 884 "Misformed allocation response?") 885 if self.projects.has_key(pname): self.projects[pname] += 1 886 else: self.projects[pname] = 1 887 self.allocation[aid]['project'] = pname 888 else: 889 # sproject is a static project associated with this allocation. 890 self.allocation[aid]['sproject'] = pname 891 892 if ap.has_key('resources'): 893 if not pname: 894 self.state_lock.release() 895 raise service_error(service_error.internal, 896 "Misformed allocation response?") 897 self.allocation[aid]['types'] = set() 898 nodes = ap['resources'].get('node', []) 899 for n in nodes: 900 for h in n.get('hardware', []): 901 if self.types.has_key((pname, h)): 902 self.types[(pname, h)] += 1 903 else: 904 self.types[(pname, h)] = 1 905 self.allocation[aid]['types'].add((pname,h)) 906 907 908 self.allocation[aid]['keys'] = [ ] 909 910 try: 911 for u in ap['project']['user']: 912 uname = u['userID']['localname'] 913 if u['role'] == 'experimentCreation': 914 self.allocation[aid]['user'] = uname 915 for k in [ k['sshPubkey'] for k in u['access'] \ 916 if k.has_key('sshPubkey') ]: 917 kv = "%s:%s" % (uname, k) 918 if self.keys.has_key(kv): self.keys[kv] += 1 919 else: self.keys[kv] = 1 920 self.allocation[aid]['keys'].append((uname, k)) 921 except KeyError: 922 self.state_lock.release() 923 raise service_error(service_error.internal, 924 "Misformed allocation response?") 925 926 self.allocation[aid]['owners'] = owners 927 services, svc_state = self.export_services(req.get('service',[]), 928 pname, uname) 929 # Store services state in global state 930 for k, v in svc_state.items(): 931 self.allocation[aid][k] = v 932 self.write_state() 933 self.state_lock.release() 934 for o in owners: 935 self.auth.set_attribute(o, allocID) 936 try: 937 f = open("%s/%s.pem" % (self.certdir, aid), "w") 938 print >>f, alloc_cert 939 f.close() 940 except EnvironmentError, e: 941 raise service_error(service_error.internal, 942 "Can't open %s/%s : %s" % (self.certdir, aid, e)) 943 resp = self.build_response({ 'fedid': allocID } , ap, services) 944 return resp 945 else: 946 if self.allow_proxy: 947 resp = self.proxy_RequestAccess.call_service(dt, req, 948 self.cert_file, self.cert_pwd, 949 self.trusted_certs) 950 if resp.has_key('RequestAccessResponseBody'): 951 return resp['RequestAccessResponseBody'] 952 else: 953 return None 954 else: 955 raise service_error(service_error.access, 956 "Access proxying denied") 420 421 found, dyn, owners = self.lookup_access(req, fid) 422 ap = None 423 424 # if this includes a project export request and the exported 425 # project is not the access project, access denied. 426 if 'service' in req: 427 ep = get_export_project(req['service']) 428 if ep and ep != found[0].name: 429 raise service_error(service_error.access, 430 "Cannot export %s" % ep) 431 432 if self.ssh_pubkey_file: 433 ap = self.do_project_allocation(dyn[1], found[0].name, found[1]) 434 else: 435 raise service_error(service_error.internal, 436 "SSH access parameters required") 437 # keep track of what's been added 438 allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log) 439 aid = unicode(allocID) 440 441 pname, uname = self.save_project_state(aid, ap, dyn[1], owners) 442 443 services, svc_state = self.export_services(req.get('service',[]), 444 pname, uname) 445 self.state_lock.acquire() 446 # Store services state in global state 447 for k, v in svc_state.items(): 448 self.allocation[aid][k] = v 449 self.write_state() 450 self.state_lock.release() 451 # Give the owners the right to change this allocation 452 for o in owners: 453 self.auth.set_attribute(o, allocID) 454 try: 455 f = open("%s/%s.pem" % (self.certdir, aid), "w") 456 print >>f, alloc_cert 457 f.close() 458 except EnvironmentError, e: 459 raise service_error(service_error.internal, 460 "Can't open %s/%s : %s" % (self.certdir, aid, e)) 461 resp = self.build_access_response({ 'fedid': allocID } , 462 ap, services) 463 return resp 957 464 958 465 def ReleaseAccess(self, req, fid): … … 1070 577 "Access proxying denied") 1071 578 1072 def generate_portal_configs(self, topo, pubkey_base, secretkey_base,1073 tmpdir, master, lproj, leid, connInfo, services):1074 1075 def conninfo_to_dict(key, info):1076 """1077 Make a cpoy of the connection information about key, and flatten it1078 into a single dict by parsing out any feddAttrs.1079 """1080 1081 rv = None1082 for i in info:1083 if key == i.get('portal', "") or \1084 key in [e.get('element', "") \1085 for e in i.get('member', [])]:1086 rv = i.copy()1087 break1088 1089 else:1090 return rv1091 1092 if 'fedAttr' in rv:1093 for a in rv['fedAttr']:1094 attr = a.get('attribute', "")1095 val = a.get('value', "")1096 if attr and attr not in rv:1097 rv[attr] = val1098 del rv['fedAttr']1099 return rv1100 1101 # XXX: un hardcode this1102 def client_null(f, s):1103 print >>f, "Service: %s" % s['name']1104 1105 def client_seer_master(f, s):1106 print >>f, 'PortalAlias: seer-master';1107 1108 def client_smb(f, s):1109 print >>f, "Service: %s" % s['name']1110 smbshare = None1111 smbuser = None1112 smbproj = None1113 for a in s.get('fedAttr', []):1114 if a.get('attribute', '') == 'SMBSHARE':1115 smbshare = a.get('value', None)1116 elif a.get('attribute', '') == 'SMBUSER':1117 smbuser = a.get('value', None)1118 elif a.get('attribute', '') == 'SMBPROJ':1119 smbproj = a.get('value', None)1120 1121 if all((smbshare, smbuser, smbproj)):1122 print >>f, "SMBshare: %s" % smbshare1123 print >>f, "ProjectUser: %s" % smbuser1124 print >>f, "ProjectName: %s" % smbproj1125 1126 def client_hide_hosts(f, s):1127 for a in s.get('fedAttr', [ ]):1128 if a.get('attribute', "") == 'hosts':1129 print >>f, "Hide: %s" % a.get('value', "")1130 1131 client_service_out = {1132 'SMB': client_smb,1133 'tmcd': client_null,1134 'seer': client_null,1135 'userconfig': client_null,1136 'project_export': client_null,1137 'seer_master': client_seer_master,1138 'hide_hosts': client_hide_hosts,1139 }1140 1141 def client_seer_master_export(f, s):1142 print >>f, "AddedNode: seer-master"1143 1144 def client_seer_local_export(f, s):1145 print >>f, "AddedNode: control"1146 1147 client_export_service_out = {1148 'seer_master': client_seer_master_export,1149 'local_seer_control': client_seer_local_export,1150 }1151 1152 def server_port(f, s):1153 p = urlparse(s.get('server', 'http://localhost'))1154 print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port)1155 1156 def server_null(f,s): pass1157 1158 def server_seer(f, s):1159 print >>f, 'seer: True'1160 1161 server_service_out = {1162 'SMB': server_port,1163 'tmcd': server_port,1164 'userconfig': server_null,1165 'project_export': server_null,1166 'seer': server_seer,1167 'seer_master': server_port,1168 'hide_hosts': server_null,1169 }1170 # XXX: end un hardcode this1171 1172 1173 seer_out = False1174 client_out = False1175 mproj = None1176 mexp = None1177 control_gw = None1178 testbed = ""1179 # Create configuration files for the portals1180 for e in [ e for e in topo.elements \1181 if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:1182 myname = e.name1183 type = e.get_attribute('portal_type')1184 1185 info = conninfo_to_dict(myname, connInfo)1186 1187 if not info:1188 raise service_error(service_error.req,1189 "No connectivity info for %s" % myname)1190 1191 peer = info.get('peer', "")1192 ldomain = self.domain;1193 ssh_port = info.get('ssh_port', 22)1194 1195 # Collect this for the client.conf file1196 if 'masterexperiment' in info:1197 mproj, meid = info['masterexperiment'].split("/", 1)1198 1199 if type in ('control', 'both'):1200 testbed = e.get_attribute('testbed')1201 control_gw = myname1202 1203 active = info.get('active', 'False')1204 1205 cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())1206 tunnelconfig = self.tunnel_config1207 try:1208 f = open(cfn, "w")1209 if active == 'True':1210 print >>f, "active: True"1211 print >>f, "ssh_port: %s" % ssh_port1212 if type in ('control', 'both'):1213 for s in [s for s in services \1214 if s.get('name', "") in self.imports]:1215 server_service_out[s['name']](f, s)1216 1217 if tunnelconfig:1218 print >>f, "tunnelip: %s" % tunnelconfig1219 print >>f, "peer: %s" % peer.lower()1220 print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \1221 (lproj, leid, pubkey_base)1222 print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \1223 (lproj, leid, secretkey_base)1224 f.close()1225 except EnvironmentError, e:1226 raise service_error(service_error.internal,1227 "Can't write protal config %s: %s" % (cfn, e))1228 1229 # Done with portals, write the client config file.1230 try:1231 f = open("%s/client.conf" % tmpdir, "w")1232 if control_gw:1233 print >>f, "ControlGateway: %s.%s.%s%s" % \1234 (myname.lower(), leid.lower(), lproj.lower(),1235 ldomain.lower())1236 for s in services:1237 if s.get('name',"") in self.imports and \1238 s.get('visibility','') == 'import':1239 client_service_out[s['name']](f, s)1240 if s.get('name', '') in self.exports and \1241 s.get('visibility', '') == 'export' and \1242 s['name'] in client_export_service_out:1243 client_export_service_out[s['name']](f, s)1244 # Seer uses this.1245 if mproj and meid:1246 print >>f, "ExperimentID: %s/%s" % (mproj, meid)1247 # Better way...1248 if testbed == master:1249 print >>f, "SEERBase: True"1250 f.close()1251 except EnvironmentError, e:1252 raise service_error(service_error.internal,1253 "Cannot write client.conf: %s" %s)1254 1255 579 def generate_ns2(self, topo, expfn, softdir, master, connInfo): 1256 580 class dragon_commands: … … 1394 718 "Cannot write experiment file %s: %s" % (expfn,e)) 1395 719 1396 def export_store_info(self, cf, proj, ename, connInfo):1397 """1398 For the export requests in the connection info, install the peer names1399 at the experiment controller via SetValue calls.1400 """1401 1402 for c in connInfo:1403 for p in [ p for p in c.get('parameter', []) \1404 if p.get('type', '') == 'output']:1405 1406 if p.get('name', '') == 'peer':1407 k = p.get('key', None)1408 surl = p.get('store', None)1409 if surl and k and k.index('/') != -1:1410 value = "%s.%s.%s%s" % \1411 (k[k.index('/')+1:], ename, proj, self.domain)1412 req = { 'name': k, 'value': value }1413 self.log.debug("Setting %s to %s on %s" % \1414 (k, value, surl))1415 self.call_SetValue(surl, req, cf)1416 else:1417 self.log.error("Bad export request: %s" % p)1418 elif p.get('name', '') == 'ssh_port':1419 k = p.get('key', None)1420 surl = p.get('store', None)1421 if surl and k:1422 req = { 'name': k, 'value': self.ssh_port }1423 self.log.debug("Setting %s to %s on %s" % \1424 (k, self.ssh_port, surl))1425 self.call_SetValue(surl, req, cf)1426 else:1427 self.log.error("Bad export request: %s" % p)1428 else:1429 self.log.error("Unknown export parameter: %s" % \1430 p.get('name'))1431 continue1432 1433 def import_store_info(self, cf, connInfo):1434 """1435 Pull any import parameters in connInfo in. We translate them either1436 into known member names or fedAddrs.1437 """1438 1439 for c in connInfo:1440 for p in [ p for p in c.get('parameter', []) \1441 if p.get('type', '') == 'input']:1442 name = p.get('name', None)1443 key = p.get('key', None)1444 store = p.get('store', None)1445 1446 if name and key and store :1447 req = { 'name': key, 'wait': True }1448 self.log.debug("Waiting for %s (%s) from %s" % \1449 (name, key, store))1450 r = self.call_GetValue(store, req, cf)1451 r = r.get('GetValueResponseBody', None)1452 if r :1453 if r.get('name', '') == key:1454 v = r.get('value', None)1455 if v is not None:1456 if name == 'peer':1457 self.log.debug("Got peer %s" % v)1458 c['peer'] = v1459 else:1460 self.log.debug("Got %s %s" % (name, v))1461 if c.has_key('fedAttr'):1462 c['fedAttr'].append({1463 'attribute': name, 'value': v})1464 else:1465 c['fedAttr']= [{1466 'attribute': name, 'value': v}]1467 else:1468 raise service_error(service_error.internal,1469 'None value exported for %s' % key)1470 else:1471 raise service_error(service_error.internal,1472 'Different name returned for %s: %s' \1473 % (key, r.get('name','')))1474 else:1475 raise service_error(service_error.internal,1476 'Badly formatted response: no GetValueResponseBody')1477 else:1478 raise service_error(service_error.internal,1479 'Bad Services missing info for import %s' % c)1480 1481 720 def configure_userconf(self, services): 1482 721 """
Note: See TracChangeset
for help on using the changeset viewer.