Changeset ee950c2 for fedd/federation/emulab_access.py
- Timestamp:
- Jan 10, 2012 5:28:15 PM (12 years ago)
- Branches:
- compt_changes, info-ops, master
- Children:
- f77a256
- Parents:
- d2e86f6
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
fedd/federation/emulab_access.py
rd2e86f6 ree950c2 16 16 17 17 from access import access_base 18 from legacy_access import legacy_access19 18 20 19 from util import * 21 from allocate_project import allocate_project_local, allocate_project_remote22 20 from fedid import fedid, generate_fedid 23 21 from authorizer import authorizer, abac_authorizer … … 42 40 fl.addHandler(nullHandler()) 43 41 44 class access(access_base , legacy_access):42 class access(access_base): 45 43 """ 46 44 The implementation of access control based on mapping users to projects. … … 109 107 # authorization information 110 108 self.auth_type = config.get('access', 'auth_type') \ 111 or ' legacy'109 or 'abac' 112 110 self.auth_dir = config.get('access', 'auth_dir') 113 111 accessdb = config.get("access", "accessdb") 114 112 # initialize the authorization system 115 if self.auth_type == 'legacy': 116 self.access = { } 117 if accessdb: 118 self.legacy_read_access(accessdb, self.legacy_access_tuple) 119 elif self.auth_type == 'abac': 113 if self.auth_type == 'abac': 120 114 self.auth = abac_authorizer(load=self.auth_dir) 121 115 self.access = [ ] … … 128 122 # read_state in the base_class 129 123 self.state_lock.acquire() 130 for a in ('allocation', 'projects', 'keys', 'types'): 131 if a not in self.state: 132 self.state[a] = { } 124 if 'allocation' not in self.state: self.state['allocation']= { } 133 125 self.allocation = self.state['allocation'] 134 self.projects = self.state['projects']135 self.keys = self.state['keys']136 self.types = self.state['types']137 if self.auth_type == "legacy":138 # Add the ownership attributes to the authorizer. Note that the139 # indices of the allocation dict are strings, but the attributes are140 # fedids, so there is a conversion.141 for k in self.allocation.keys():142 for o in self.allocation[k].get('owners', []):143 self.auth.set_attribute(o, fedid(hexstr=k))144 if self.allocation[k].has_key('userconfig'):145 sfid = self.allocation[k]['userconfig']146 fid = fedid(hexstr=sfid)147 self.auth.set_attribute(fid, "/%s" % sfid)148 126 self.state_lock.release() 149 127 self.exports = { … … 195 173 self.call_GetValue = service_caller('GetValue', log=self.log) 196 174 197 if not config.has_option("allocate", "uri"):198 self.allocate_project = \199 allocate_project_local(config, auth)200 else:201 self.allocate_project = \202 allocate_project_remote(config, auth)203 204 205 # If the project allocator exports services, put them in this object's206 # maps so that classes that instantiate this can call the services.207 self.soap_services.update(self.allocate_project.soap_services)208 self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)209 210 @staticmethod211 def legacy_access_tuple(str):212 """213 Convert a string of the form (id[:resources:resouces], id, id) into a214 tuple of the form (project, user, user) where users may be names or215 fedids. The resources strings are obsolete and ignored.216 """217 def parse_name(n):218 if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])219 else: return n220 221 str = str.strip()222 if str.startswith('(') and str.endswith(')'):223 str = str[1:-1]224 names = [ s.strip() for s in str.split(",")]225 if len(names) > 3:226 raise self.parse_error("More than three fields in name")227 first = names[0].split(":")228 if first == 'fedid:':229 del first[0]230 first[0] = fedid(hexstr=first[0])231 names[0] = first[0]232 233 for i in range(1,2):234 names[i] = parse_name(names[i])235 236 return tuple(names)237 else:238 raise self.parse_error('Bad mapping (unbalanced parens)')239 240 175 @staticmethod 241 176 def access_tuple(str): … … 243 178 Convert a string of the form (id, id) into an access_project. This is 244 179 called by read_access to convert to local attributes. It returns 245 a tuple of the form (project, user, user) where the two users are 246 always the same. 180 a tuple of the form (project, user). 247 181 """ 248 182 … … 251 185 # The slice takes the parens off the string. 252 186 proj, user = str[1:-1].split(',') 253 return (proj.strip(), user.strip() , user.strip())187 return (proj.strip(), user.strip()) 254 188 else: 255 189 raise self.parse_error( … … 258 192 # RequestAccess support routines 259 193 260 def legacy_lookup_access(self, req, fid): 261 """ 262 Look up the local access control information mapped to this fedid and 263 credentials. In this case it is a (project, create_user, access_user) 264 triple, and a triple of three booleans indicating which, if any will 265 need to be dynamically created. Finally a list of owners for that 266 allocation is returned. 267 268 lookup_access_base pulls the first triple out, and it is parsed by this 269 routine into the boolean map. Owners is always the controlling fedid. 270 """ 271 # Return values 272 rp = None 273 ru = None 274 # This maps a valid user to the Emulab projects and users to use 275 found, match = self.legacy_lookup_access_base(req, fid) 276 tb, project, user = match 277 278 if found == None: 279 raise service_error(service_error.access, 280 "Access denied - cannot map access") 281 282 # resolve <dynamic> and <same> in found 283 dyn_proj = False 284 dyn_create_user = False 285 dyn_service_user = False 286 287 if found[0] == "<same>": 288 if project != None: 289 rp = project 290 else : 291 raise service_error(\ 292 service_error.server_config, 293 "Project matched <same> when no project given") 294 elif found[0] == "<dynamic>": 295 rp = None 296 dyn_proj = True 297 else: 298 rp = found[0] 299 300 if found[1] == "<same>": 301 if user_match == "<any>": 302 if user != None: rcu = user[0] 303 else: raise service_error(\ 304 service_error.server_config, 305 "Matched <same> on anonymous request") 306 else: 307 rcu = user_match 308 elif found[1] == "<dynamic>": 309 rcu = None 310 dyn_create_user = True 311 else: 312 rcu = found[1] 313 314 if found[2] == "<same>": 315 if user_match == "<any>": 316 if user != None: rsu = user[0] 317 else: raise service_error(\ 318 service_error.server_config, 319 "Matched <same> on anonymous request") 320 else: 321 rsu = user_match 322 elif found[2] == "<dynamic>": 323 rsu = None 324 dyn_service_user = True 325 else: 326 rsu = found[2] 327 328 return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\ 329 [ fid ] 330 331 def do_project_allocation(self, dyn, project, user): 332 """ 333 Call the project allocation routines and return the info. 334 """ 335 if dyn: 336 # Compose the dynamic project request 337 # (only dynamic, dynamic currently allowed) 338 preq = { 'AllocateProjectRequestBody': \ 339 { 'project' : {\ 340 'user': [ \ 341 { \ 342 'access': [ { 343 'sshPubkey': self.ssh_pubkey_file } ], 344 'role': "serviceAccess",\ 345 }, \ 346 { \ 347 'access': [ { 348 'sshPubkey': self.ssh_pubkey_file } ], 349 'role': "experimentCreation",\ 350 }, \ 351 ], \ 352 }\ 353 }\ 354 } 355 return self.allocate_project.dynamic_project(preq) 356 else: 357 preq = {'StaticProjectRequestBody' : \ 358 { 'project': \ 359 { 'name' : { 'localname' : project },\ 360 'user' : [ \ 361 {\ 362 'userID': { 'localname' : user }, \ 363 'access': [ { 364 'sshPubkey': self.ssh_pubkey_file } ], 365 'role': 'experimentCreation'\ 366 },\ 367 {\ 368 'userID': { 'localname' : user}, \ 369 'access': [ { 370 'sshPubkey': self.ssh_pubkey_file } ], 371 'role': 'serviceAccess'\ 372 },\ 373 ]}\ 374 }\ 375 } 376 return self.allocate_project.static_project(preq) 377 378 def save_project_state(self, aid, ap, dyn, owners): 379 """ 380 Parse out and save the information relevant to the project created for 381 this experiment. That info is largely in ap and owners. dyn indicates 382 that the project was created dynamically. Return the user and project 383 names. 194 def save_project_state(self, aid, pname, uname, owners): 195 """ 196 Save the project, user, and owners associated with this allocation. 197 This creates the allocation entry. 384 198 """ 385 199 self.state_lock.acquire() 386 200 self.allocation[aid] = { } 387 self.allocation[aid]['auth'] = set() 388 try: 389 pname = ap['project']['name']['localname'] 390 except KeyError: 391 pname = None 392 393 if dyn: 394 if not pname: 395 self.state_lock.release() 396 raise service_error(service_error.internal, 397 "Misformed allocation response?") 398 if pname in self.projects: self.projects[pname] += 1 399 else: self.projects[pname] = 1 400 self.allocation[aid]['project'] = pname 401 else: 402 # sproject is a static project associated with this allocation. 403 self.allocation[aid]['sproject'] = pname 404 405 self.allocation[aid]['keys'] = [ ] 406 407 try: 408 for u in ap['project']['user']: 409 uname = u['userID']['localname'] 410 if u['role'] == 'experimentCreation': 411 self.allocation[aid]['user'] = uname 412 for k in [ k['sshPubkey'] for k in u['access'] \ 413 if k.has_key('sshPubkey') ]: 414 kv = "%s:%s" % (uname, k) 415 if self.keys.has_key(kv): self.keys[kv] += 1 416 else: self.keys[kv] = 1 417 self.allocation[aid]['keys'].append((uname, k)) 418 except KeyError: 419 self.state_lock.release() 420 raise service_error(service_error.internal, 421 "Misformed allocation response?") 422 201 self.allocation[aid]['project'] = pname 202 self.allocation[aid]['user'] = uname 423 203 self.allocation[aid]['owners'] = owners 424 204 self.write_state() … … 481 261 self.auth.save() 482 262 483 if self.auth_type == "legacy": 484 found, dyn, owners= self.legacy_lookup_access(req, fid) 485 proof = access_proof("me", fid, "create") 486 elif self.auth_type == 'abac': 487 found, dyn, owners, proof = self.lookup_access(req, fid, filter=pf) 263 if self.auth_type == 'abac': 264 found, owners, proof = self.lookup_access(req, fid, filter=pf) 488 265 else: 489 266 raise service_error(service_error.internal, … … 491 268 ap = None 492 269 493 # This only happens in legacy lookups, but if this user has access to494 # the testbed but not the project to be exported, raise the error.495 if ep and ep != found[0]:496 raise service_error(service_error.access,497 "Cannot export %s" % ep)498 499 if self.ssh_pubkey_file:500 ap = self.do_project_allocation(dyn[1], found[0], found[1])501 else:502 raise service_error(service_error.internal,503 "SSH access parameters required")504 270 # keep track of what's been added 505 271 allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log) 506 272 aid = unicode(allocID) 507 273 508 pname, uname = self.save_project_state(aid, ap, dyn[1], owners)274 pname, uname = self.save_project_state(aid, found[0], found[1], owners) 509 275 510 276 services, svc_state = self.export_services(req.get('service',[]), … … 526 292 "Can't open %s/%s : %s" % (self.certdir, aid, e)) 527 293 resp = self.build_access_response({ 'fedid': allocID } , 528 ap, services, proof)294 pname, services, proof) 529 295 return resp 530 531 def do_release_project(self, del_project, del_users, del_types):532 """533 If a project and users has to be deleted, make the call.534 """535 msg = { 'project': { }}536 if del_project:537 msg['project']['name']= {'localname': del_project}538 users = [ ]539 for u in del_users.keys():540 users.append({ 'userID': { 'localname': u },\541 'access' : \542 [ {'sshPubkey' : s } for s in del_users[u]]\543 })544 if users:545 msg['project']['user'] = users546 if len(del_types) > 0:547 msg['resources'] = { 'node': \548 [ {'hardware': [ h ] } for h in del_types ]\549 }550 if self.allocate_project.release_project:551 msg = { 'ReleaseProjectRequestBody' : msg}552 self.allocate_project.release_project(msg)553 296 554 297 def ReleaseAccess(self, req, fid): … … 579 322 raise service_error(service_error.access, "Access Denied") 580 323 581 # If we know this allocation, reduce the reference counts and582 # remove the local allocations. Otherwise report an error. If583 # there is an allocation to delete, del_users will be a dictonary584 # of sets where the key is the user that owns the keys in the set.585 # We use a set to avoid duplicates. del_project is just the name586 # of any dynamic project to delete. We're somewhat lazy about587 # deleting authorization attributes. Having access to something588 # that doesn't exist isn't harmful.589 del_users = { }590 del_project = None591 del_types = set()592 593 324 self.state_lock.acquire() 594 325 if aid in self.allocation: 595 326 self.log.debug("Found allocation for %s" %aid) 596 327 self.clear_allocation_authorization(aid, state_attr='allocation') 597 for k in self.allocation[aid]['keys']:598 kk = "%s:%s" % k599 self.keys[kk] -= 1600 if self.keys[kk] == 0:601 if not del_users.has_key(k[0]):602 del_users[k[0]] = set()603 del_users[k[0]].add(k[1])604 del self.keys[kk]605 606 if 'project' in self.allocation[aid]:607 pname = self.allocation[aid]['project']608 self.projects[pname] -= 1609 if self.projects[pname] == 0:610 del_project = pname611 del self.projects[pname]612 613 if 'types' in self.allocation[aid]:614 for t in self.allocation[aid]['types']:615 self.types[t] -= 1616 if self.types[t] == 0:617 if not del_project: del_project = t[0]618 del_types.add(t[1])619 del self.types[t]620 621 328 del self.allocation[aid] 622 329 self.write_state() 623 330 self.state_lock.release() 624 # If we actually have resources to deallocate, prepare the call. 625 if del_project or del_users: 626 self.do_release_project(del_project, del_users, del_types) 627 # And remove the access cert 331 # Remove the access cert 628 332 cf = "%s/%s.pem" % (self.certdir, aid) 629 333 self.log.debug("Removing %s" % cf) … … 939 643 if aid in self.allocation: 940 644 proj = self.allocation[aid].get('project', None) 941 if not proj:942 proj = self.allocation[aid].get('sproject', None)943 645 self.state_lock.release() 944 646 … … 1215 917 if aid in self.allocation: 1216 918 proj = self.allocation[aid].get('project', None) 1217 if not proj:1218 proj = self.allocation[aid].get('sproject', None)1219 919 user = self.allocation[aid].get('user', None) 1220 920 ename = self.allocation[aid].get('experiment', None) … … 1265 965 if topo: topo = topo.clone() 1266 966 proj = self.allocation[aid].get('project', None) 1267 if not proj:1268 proj = self.allocation[aid].get('sproject', None)1269 967 user = self.allocation[aid].get('user', None) 1270 968 ename = self.allocation[aid].get('experiment', None)
Note: See TracChangeset
for help on using the changeset viewer.