--- /dev/null
+Source: xvm-authz-locker
+Section: base
+Priority: extra
+Maintainer: Invirt project <invirt@mit.edu>
+Build-Depends: cdbs (>= 0.4.23-1.1), debhelper (>= 4.1.0), python-all-dev, python-support, python-setuptools, python-debian, python-apt
+Standards-Version: 3.8.0
+
+Package: xvm-authz-locker
+Architecture: all
+Depends: ${python:Depends}, ${misc:Depends}, invirt-base, python-afs
+Provides: ${python:Provides}, invirt-authz
+XB-Python-Version: ${python:Versions}
+Description: Authorization module for XVM
+ This package contains an authorization module for XVM. It supports
+ the locker authorization scheme.
--- /dev/null
+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.afs.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 AFS tokens for a cell if encryption is required by config.
+
+ If the Invirt configuration requires connections to this cell to
+ be encrypted, acquires tokens and returns True. Otherwise, returns
+ False. Consumers of this function must still be sure to encrypt
+ their own connections if necessary.
+
+ Cells not listed in the Invirt configuration default to requiring
+ encryption in order to maintain security by default.
+
+ Due to AFS's cross-realm auto-PTS-creation mechanism, using
+ authenticated connections by default should only fail for cells
+ which authenticate directly against the machine's home realm and
+ cells distantly related to the machine's home realm.
+ """
+ for c in config.authz.afs.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, pts.PTS_ENCRYPT if auth else pts.PTS_UNAUTH).\
+ 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
--- /dev/null
+#!/usr/bin/python
+
+from os import path
+from email.utils import parseaddr
+from glob import glob
+from setuptools import setup, find_packages
+
+try:
+ from debian_bundle.changelog import Changelog
+ from debian_bundle.deb822 import Deb822
+ version = Changelog(open(path.join(path.dirname(__file__), 'debian/changelog')).read()).\
+ get_version().full_version
+
+ maintainer_full = Deb822(open(path.join(path.dirname(__file__), 'debian/control')))['Maintainer']
+ maintainer, maintainer_email = parseaddr(maintainer_full)
+except:
+ version = '0.0.0'
+ maintainer, maintainer_email = parseaddr('Invirt project <invirt@mit.edu>')
+
+setup(
+ name='xvm.authz.locker',
+ version=version,
+ maintainer=maintainer,
+ maintainer_email=maintainer_email,
+
+ packages=find_packages('python'),
+ package_dir = {'': 'python'},
+)