In invirt.authz.locker, deal with getting tokens and
[invirt/packages/invirt-base.git] / python / invirt / authz / locker.py
1 import errno
2
3 from afs import acl
4 from afs import fs
5 from afs import pts
6
7 from invirt import common
8 from invirt.config import structs as config
9 from invirt import remctl
10
11
12 #
13 # expandOwner and expandAdmin form the API that needs to be exported
14 # for all authz modules.
15 #
16
17
18 def expandOwner(name):
19     """Expand an owner to a list of authorized users.
20
21     For the locker authz module, an owner is an Athena locker. Those
22     users who have been given the administrator ('a') bit on the root
23     of a locker are given access to any VM owned by that locker,
24     unless they also have been given a negative administrator bit.
25
26     If a locker doesn't exist, or we can't access the permissions, we
27     assume the ACL is empty.
28     """
29     try:
30         path = _lockerPath(name)
31         cell = fs.whichcell(path)
32         auth = _authenticate(cell)
33         a = acl.ACL.retrieve(path)
34
35         allowed = set()
36         for ent in a.pos:
37             if a.pos[ent] & acl.ADMINISTER:
38                 allowed.update(_expandGroup(ent, cell=cell, auth=auth))
39         for ent in a.neg:
40             if a.neg[ent] & acl.ADMINISTER:
41                 allowed.difference_update(_expandGroup(ent, cell=cell, auth=auth))
42
43         return allowed
44     except OSError, e:
45         if e.errno in (errno.ENOENT, errno.EACCES):
46             return []
47         else:
48             raise
49
50
51 def expandAdmin(name, owner):
52     """Expand an administrator to a list of authorized users.
53
54     Because the interpretation of an administrator might depend on the
55     owner, the owner is passed in as an argument.
56
57     However, in the case of locker-based authentication, the
58     administrator is always interpreted as an AFS entry (either a user
59     or a group) in the home cell (athena.mit.edu for XVM).
60     """
61     cell = config.authz.cells[0].cell
62     auth = _authenticate(cell)
63     return _expandGroup(name, cell=cell, auth=auth)
64
65
66 #
67 # These are helper functions, and aren't part of the authz API
68 #
69
70
71 def _authenticate(cell):
72     """Acquire credentials if possible for a particular cell.
73
74     This function returns True if an authenticated connection to the
75     cell should be established; False otherwise.
76
77     If a cell isn't explicitly listed in the configuration file,
78     _authenticate will assume that it /should/ authenticate to the
79     cell.
80
81     The assumption is that choosing to authenticate to a cell will
82     fail in two cases: (a) the cell authenticates against the
83     machine's home realm and there is no PTS ID in the cell, or (b)
84     the cell doesn't authenticate against the machine's home realm and
85     doesn't have cross-realm authentication setup.
86
87     In the former case, it should be possible for the sysadmins to
88     list all cells that authenticate against the home realm (including
89     those where attempting authentication would be problematic). In
90     the latter case, such a cell would be at best distantly connected
91     to the home cell, and we probably don't want to give it quota
92     anyway.
93     """
94     for c in config.authz.cells:
95         if c.cell == cell and not c.auth:
96             return False
97
98     remctl.checkKinit()
99     common.captureOutput(['aklog', '-c', cell])
100     return True
101
102
103 def _expandGroup(name, cell=None, auth=False):
104     """Expand an AFS group into a list of its members.
105
106     Because groups are not global, but can vary from cell to cell,
107     this function accepts as an optional argument the cell in which
108     this group should be resolved.
109
110     If no cell is specified, it is assumed that the default cell (or
111     ThisCell) should be used.
112
113     If the name is a user, not a group, then a single-element set with
114     the same name is returned.
115
116     As with expandOwner, if a group doesn't exist or if we're unable
117     to retrieve its membership, we assume it's empty.
118     """
119     try:
120         ent = pts.PTS(cell, 3 if auth else 0).getEntry(name)
121         if ent.id > 0:
122             return set([ent.name])
123         else:
124             return set([x.name for x in ent.members])
125     except OSError, e:
126         if e.errno in (errno.ENOENT, errno.EACCESS):
127             return set()
128         else:
129             raise
130
131
132 def _lockerPath(owner):
133     """Given the name of a locker, return a path to that locker.
134
135     This turns out to be pretty simple, thanks to the /mit
136     automounter.
137     """
138     return '/mit/%s' % owner