[OpenAFS] Re: Ideas for finer grain set acl controls

Andrew Deason adeason@sinenomine.net
Wed, 4 Nov 2009 16:24:47 -0600

Recent discussions have shown that there are a few possible ways we
could go about restricting ACL-setting access. With this email I hope to
detail all of the ones that have come up so far, and demonstrate the
pros and cons of each.

Since the rest of this is quite long, here's a quick summary of the
conclusions. We have three methods: 'method 1' is the "volume ACL
policies" idea, 'method 2' is the "volume-level ACL overlays" idea, and
'method 3' is the "volume ACL masks" idea. To cover all uses cases, we'd
either do method 1, or a combination of 2 and 3. Or we could just
implement all of them; I don't see anything stopping us from
implementing everything desribed here.

The bottom line is that I find method 1 to be the most flexible and the
least confusing to end-users, but it is the most confusing to
administrators, and it is the slowest (when changing the volume-level
permissions). Using methods 2+3 has the opposite pros/cons.

Here are the details. Each method has an explanation for what it
generally is and how it works, followed by its use in a few simple
use-case scenarios, followed by the pros/cons.

Method 1, "volume ACL policies":

With this method, we maintain policies of who is allowed to set what ACLs
in a volume. For example, to allow nobody but system:powerusers to grant
idwka rights to system:anyuser, we'd have a policy for system:anyuser
that would look like this:

system:powerusers rlidwka
system:anyuser    rl

After that policy is set, any time a user not in system:powerusers tries
to grant system:anyuser more than rl rights, they will get an EACCES
error. This does not change the existing ACLs in the volume; an
administrator will need to run an auditing tool to make sure that
existing ACLs comply with the volume policy.

"How do I prevent system:anyuser/system:authuser write access?"

You would call something like this

 vos setpolicy -add-positive   \
   -user system:anyuser        \
   -set-rights rl              \
   -for-user system:anyuser    \
   -in-volume user.adeason

to prevent people fromt giving system:anyuser write access. To ensure
that existing ACLs don't permit write access, you would need to run
something like

 vos auditpolicy -vol user.adeason

"How do I prevent group.foo write access?"

To prevent an arbitrary normal group from getting write access, things
are slightly different. You would need to prevent users from taking away
negative idwka rights, and then assign negative idwka rights to all
directories in the volume. So, something like

 vos setpolicy -remove-negative \
   -user system:anyuser         \
   -set-rights rl               \
   -for-user group.foo          \
   -in-volume user.adeason

Would allow users to only remove 'rl' rights from group.foo in negative
ACLs. Then you would need to set negative idwka ACLs on all directories
in the volume.

"How do I guarantee group.bar read access?"

Prevent normal users from taking away read access from group.bar, and
from granting negative read access for group.bar:

 vos setpolicy -remove-positive \
   -user system:anyuser         \
   -set-rights idwka            \
   -for-user group.bar          \
   -in-volume user.adeason
 vos setpolicy -add-negative    \
   -user system:anyuser         \
   -set-rights idwka            \
   -for-user group.bar          \
   -in-volume user.adeason

Then, grant read access for group.bar in all directories in the volume.

Method 1 pros:

 - More flexible for a variety of situations. In particular, this allows
   administrators (or an arbitrary administrator-defined set of users)
   to override the volume policies, and set e.g.  system:anyuser write
   on a particular directory if they wish.

 - No performance overhead at access-check time. All of the additional
   access checks are done during SetACL.
 - Minimal end-user confusion. ACLs accurately represent exactly what
   rights different users have. Trying to set an ACL that violates the
   policy will result in EACCES, so they know the SetACL operation
   didn't work. It may be confusing as to why it did not, but at least
   it is clear that the ACL was not changed.
Method 1 cons:

 - Volumes need to be 'audited' (or all of the ACLs just need to be set)
   to make sure they comply with the ACL policy, which can be slow, and
   is just another step that needs to be done.
 - Higher end-administrator confusion (perhaps). Setting the policies
   can get confusing.

Method 2, "volume-level ACL overlays":

With this method, we maintain a single additional ACL in the volume
metadata, which is applied to access checks in the volume, after
performing the per-directory ACL check. It can be thought of as similar
to the fileserver -implicit flag, but more generalized.

For example, if we wanted a volume where system:backups was guaranteed
to have 'rl' rights, and system:evilusers was guaranteed to not have
_any_ rights, the volume-level ACL would look like:

 system:backups rl
 system:evilusers rlidwka

Thus, any time an access check is done on an ACL anywhere in the
volume... after we do the normal directory ACL check, we look at this
volume-level ACL. If the accessing user is in system:backups, they will
get rl rights, and if they are in system:evilusers, all of their rights
will go away.

"How do I prevent system:anyuser/system:authuser write access?"

You can't. If we allow you to specify the 'anonymous' user, you could
assign negative idwka rights to 'anonymous' on the volume-level ACL to
prevent system:anyuser write access. But there is no way to prevent
access for system:authuser.

Note: giving a negative ACL on, say, system:anyuser would prevent _any_
user from getting rights; that's not what we'd want.

"How do I prevent group.foo write access?"

Just grant negative idwka access to group.foo on the volume. Something

 vos setacl -vol user.adeason -acl group.foo idwka -neg

Members of group.foo will now not be able to write anything in the

"How do I guarantee group.bar read access?"

Same as above, just grant positive read rights. Something like:

 vos setacl -vol user.adeason -acl group.bar rl

Method 2 pros:

 - Changing the volume-level rights is quick.

 - Minimal end-administrator confusion; this is relatively simple to
 - Simpler than method 1 to implement.

Method 2 cons:

 - We have no way to restrict access of special groups like
   system:anyuser, system:authuser, or host IP groups. To get this, we'd
   have to combine with method with method 1 or 3.
 - There is no way to override the volume-level ACL, and have an
   administrator force e.g. system:anyuser write access on a particular
 - Higher end-user confusion. With legacy 'fs listacl', there is no way
   to see that there is a volume-level ACL in play, and users may have
   no idea why access is failing for certain cases. Of course, with new
   tools we can fix that.
 - Performance impact is an extra O(n) ACL calculation for the
   volume-level ACL. But those ACLs should be small anyway.

Method 3, "volume ACL masks"

With this method, we maintain a mapping of users to a rights mask. Any
time an ACL access check is performed, if a positive ACL entry matches a
user in that table, the acquires rights are masked to the rights mask in
the table.

For example, if we wanted to prevent users from giving away write
access to system:anyuser, and prevent users from giving admin access to
system:authuser, we could have a table like so:

system:anyuser rl
system:authuser rlidwk

So any time an ACL entry for system:anyuser appears, everything is
treated as if the rights in that ACL entry were logically ANDed with

"How do I prevent system:anyuser/system:authuser write access?"

Set the rights mask for them to just 'rl'. Something like:

 vos setaclmask -vol user.adeason -user system:anyuser -mask rl

So any time an ACL entry for system:anyuser appears in the volume,
everything will act as if the rights were limited to rl.

"How do I prevent group.foo write access?"

You can't. Well, you could, but then someone could do this instead:

 pts createg adeason:foo
 pts addu group.foo adeason:foo
 fs sa dir adeason:foo rlidwka

which would bypass the check for 'group.foo' appearing in the ACL list.

"How do I guarantee group.bar read access?"

You can't.

Method 3 pros:

 - Changing the rights mask is quick.

 - Maybe small end-administrator confusion (I don't know, is this simple
   to understand?)

 - Runtime performance overhead is O(1).

 - Simpler to implement than method 1.
Method 3 cons:

 - Only works for restricting special groups like system:anyuser,
   system:authuser, host IP groups, etc. Cannot restrict 'normal'
   groups, and cannot add rights.
 - No way to override the ACL masks on a particular directory, if the
   administrator wants to.

 - Higher end-user confusion with legacy client tools.

I assume that if we don't do method 1, we could combine both of methods
2 and 3.

To summarize the major areas where each one is better:

                     Better     | Worse
flexibility:       : method 1   | method 2+3
end-user confusion : method 1   | method 2+3
end-admin confusion: method 2+3 | method 1
policy-change speed: method 2+3 | method 1

There are other pros and cons, but I think those areas are the only ones
where it matters much.

Andrew Deason