Catch AFS exceptions
[invirt/packages/xvm-authz-locker.git] / python / xvm / authz / locker.py
1 import errno
2
3 from afs import acl
4 from afs import fs
5 from afs import pts
6 from afs import _util
7
8 from invirt import common
9 from invirt.config import structs as config
10 from invirt import remctl
11
12
13 #
14 # expandOwner and expandAdmin form the API that needs to be exported
15 # for all authz modules.
16 #
17
18
19 def expandOwner(name):
20     """Expand an owner to a list of authorized users.
21
22     For the locker authz module, an owner is an Athena locker. Those
23     users who have been given the administrator ('a') bit on the root
24     of a locker are given access to any VM owned by that locker,
25     unless they also have been given a negative administrator bit.
26
27     If a locker doesn't exist, or we can't access the permissions, we
28     assume the ACL is empty.
29     """
30     try:
31         path = _lockerPath(name)
32         cell = fs.whichcell(path)
33         auth = _authenticate(cell)
34         a = acl.ACL.retrieve(path)
35
36         allowed = set()
37         for ent in a.pos:
38             if a.pos[ent] & acl.ADMINISTER:
39                 allowed.update(_expandGroup(ent, cell=cell, auth=auth))
40         for ent in a.neg:
41             if a.neg[ent] & acl.ADMINISTER:
42                 allowed.difference_update(_expandGroup(ent, cell=cell, auth=auth))
43
44         return allowed
45     except (_util.AFSException, OSError), e:
46         if e.errno in (errno.ENOENT, errno.EACCES):
47             return []
48         else:
49             raise
50
51
52 def expandAdmin(name):
53     """Expand an administrator to a list of authorized users.
54
55     For locker-based authorization, the administrator is always
56     interpreted as an AFS entry (either a user or a group) in the
57     machine's home cell (athena.mit.edu for XVM).
58     """
59     cell = config.authz.afs.cells[0].cell
60     auth = _authenticate(cell)
61     return _expandGroup(name, cell=cell, auth=auth)
62
63
64 #
65 # These are helper functions, and aren't part of the authz API
66 #
67
68
69 def _authenticate(cell):
70     """Acquire AFS tokens for a cell if encryption is required by config.
71
72     If the Invirt configuration requires connections to this cell to
73     be encrypted, acquires tokens and returns True. Otherwise, returns
74     False. Consumers of this function must still be sure to encrypt
75     their own connections if necessary.
76
77     Cells not listed in the Invirt configuration default to requiring
78     encryption in order to maintain security by default.
79
80     Due to AFS's cross-realm auto-PTS-creation mechanism, using
81     authenticated connections by default should only fail for cells
82     which authenticate directly against the machine's home realm and
83     cells distantly related to the machine's home realm.
84     """
85     for c in config.authz.afs.cells:
86         if c.cell == cell and not c.auth:
87             return False
88
89     remctl.checkKinit()
90     common.captureOutput(['aklog', '-c', cell])
91     return True
92
93
94 def _expandGroup(name, cell=None, auth=False):
95     """Expand an AFS group into a list of its members.
96
97     Because groups are not global, but can vary from cell to cell,
98     this function accepts as an optional argument the cell in which
99     this group should be resolved.
100
101     If no cell is specified, it is assumed that the default cell (or
102     ThisCell) should be used.
103
104     If the name is a user, not a group, then a single-element set with
105     the same name is returned.
106
107     As with expandOwner, if a group doesn't exist or if we're unable
108     to retrieve its membership, we assume it's empty.
109     """
110     try:
111         ent = pts.PTS(cell, pts.PTS_ENCRYPT if auth else pts.PTS_UNAUTH).\
112             getEntry(name)
113         if ent.id > 0:
114             return set([ent.name])
115         else:
116             return set([x.name for x in ent.members])
117     except _util.AFSException, e:
118         if e.errno in (267268, # User or group doesn't exist
119                        267269  # Permission denied
120                        ):
121             return set()
122         else:
123             raise
124     except OSError, e:
125         if e.errno in (errno.ENOENT, errno.EACCESS):
126             return set()
127         else:
128             raise
129
130
131 def _lockerPath(owner):
132     """Given the name of a locker, return a path to that locker.
133
134     This turns out to be pretty simple, thanks to the /mit
135     automounter.
136     """
137     return '/mit/%s' % owner