import errno from afs import acl from afs import fs from afs import pts from invirt import common from invirt.config import structs as config from invirt import remctl # # expandOwner and expandAdmin form the API that needs to be exported # for all authz modules. # def expandOwner(name): """Expand an owner to a list of authorized users. For the locker authz module, an owner is an Athena locker. Those users who have been given the administrator ('a') bit on the root of a locker are given access to any VM owned by that locker, unless they also have been given a negative administrator bit. If a locker doesn't exist, or we can't access the permissions, we assume the ACL is empty. """ try: path = _lockerPath(name) cell = fs.whichcell(path) auth = _authenticate(cell) a = acl.ACL.retrieve(path) allowed = set() for ent in a.pos: if a.pos[ent] & acl.ADMINISTER: allowed.update(_expandGroup(ent, cell=cell, auth=auth)) for ent in a.neg: if a.neg[ent] & acl.ADMINISTER: allowed.difference_update(_expandGroup(ent, cell=cell, auth=auth)) return allowed except OSError, e: if e.errno in (errno.ENOENT, errno.EACCES): return [] else: raise def expandAdmin(name, owner): """Expand an administrator to a list of authorized users. Because the interpretation of an administrator might depend on the owner, the owner is passed in as an argument. However, in the case of locker-based authentication, the administrator is always interpreted as an AFS entry (either a user or a group) in the home cell (athena.mit.edu for XVM). """ cell = config.authz.cells[0].cell auth = _authenticate(cell) return _expandGroup(name, cell=cell, auth=auth) # # These are helper functions, and aren't part of the authz API # def _authenticate(cell): """Acquire credentials if possible for a particular cell. This function returns True if an authenticated connection to the cell should be established; False otherwise. If a cell isn't explicitly listed in the configuration file, _authenticate will assume that it /should/ authenticate to the cell. The assumption is that choosing to authenticate to a cell will fail in two cases: (a) the cell authenticates against the machine's home realm and there is no PTS ID in the cell, or (b) the cell doesn't authenticate against the machine's home realm and doesn't have cross-realm authentication setup. In the former case, it should be possible for the sysadmins to list all cells that authenticate against the home realm (including those where attempting authentication would be problematic). In the latter case, such a cell would be at best distantly connected to the home cell, and we probably don't want to give it quota anyway. """ for c in config.authz.cells: if c.cell == cell and not c.auth: return False remctl.checkKinit() common.captureOutput(['aklog', '-c', cell]) return True def _expandGroup(name, cell=None, auth=False): """Expand an AFS group into a list of its members. Because groups are not global, but can vary from cell to cell, this function accepts as an optional argument the cell in which this group should be resolved. If no cell is specified, it is assumed that the default cell (or ThisCell) should be used. If the name is a user, not a group, then a single-element set with the same name is returned. As with expandOwner, if a group doesn't exist or if we're unable to retrieve its membership, we assume it's empty. """ try: ent = pts.PTS(cell, 3 if auth else 0).getEntry(name) if ent.id > 0: return set([ent.name]) else: return set([x.name for x in ent.members]) except OSError, e: if e.errno in (errno.ENOENT, errno.EACCESS): return set() else: raise def _lockerPath(owner): """Given the name of a locker, return a path to that locker. This turns out to be pretty simple, thanks to the /mit automounter. """ return '/mit/%s' % owner