NFSv4 ACL Overview
The ACL model in NFSv4 is similar to the Windows ACL model. The NFSv4 ACL model supports a rich set of access permissions and inheritance controls. An ACL in this model is composed of an array of access control entries (ACE). Each ACE specifies the permissions, access type, inheritance flags and to whom the entry applies. In the NFSv4 model the "who" argument of each ACE, may be either a username or groupname. There are also a set of commonly know names, such as "owner@", "group@", "everyone@". These abstractions are used by UNIX variant operating systems to indicate if the ACE is for the file owner, file group owner or for the world. The everyone@ entry is not equivalent to the POSIX "other" class, it really is everyone. The complete description of the NFSv4 ACL model is availabe in Section 5.11 of the NFSv4 protocol specification.NFSv4 Access Permissions
| Permission |
Description |
| read_data |
Permission to read the data of
the file |
| list_data |
Permission to list the contents
of a directory |
| write_data |
Permission to modify the file's
data anywhere in the file's offset range. This includes the
ability to grow the file or write to an arbitrary offset. |
| add_file |
Permission to add a new file to
a directory |
| append_data |
The ability to modify the data,
but only starting at EOF. |
| add_subdirectory |
Permission to create a
subdirectory to a directory |
| read_xattr |
The ability to read the extended
attributes of a file or to do a lookup in the extended attributes
directory. |
| write_xattr |
The ability to create extended
attributes or write to the extended attributes directory. |
| execute |
Permission to execute a file |
| delete_child |
Permission to delete a file
within a directory |
| read_attributes |
The ability to read basic
attributes (non-ACLs) of a file. Basic attributes are considered
the stat(2) level attributes. |
| write_attributes |
Permission to change the times
associated with a file or directory to an arbitrary value |
| delete |
Permission to delete a file |
| read_acl |
Permission to read the ACL |
| write_acl |
Permission to write a file's ACL |
| write_owner |
Permission to change the owner
or the ability to execute chown(1) or chgrp(1) |
| synchronize |
Permission to access a file
locally at the server with synchronous reads and writes. |
NFSv4 Inheritance flags
| Inheritance Flag |
Description |
| file_inherit |
Can be place on a directory and
indicates that this ACE should be added to each new non-directory file
created. |
| dir_inherit |
Can be placed on a directory and
indicates that this ACE should be added to each new directory created. |
| inherit_only |
Placed on a directory, but does
not apply to the directory itself, only to newly created files and
directories. This flag requires file_inherit and or dir_inherit
to indicate what to inherit. |
| no_propagate |
Placed on directories and
indicates that ACL entries should only be inherited to one level of the
tree. This flag requires file_inherit and or dir_inherit to
indicate what to inherit. |
NFSv4 ACLs vs POSIX
The difficult part of using the NFSv4 ACL model was trying to still
preserve POSIX compliance in the file system. POSIX allows for
what it calls "additonal" and "alternate" access methods. An
additional access method is defined to be layered upon the file
permission bits, but they can only further restrict the standard access
control mechanism. The alternate file access control mechanism is
defined to be independent of the file permission bits and which if
enabled on a file may either restrict or extend the permissions of a
given user. Another major distinction between the additional and
alternate access control mechanisms is that, any alternate file access
control mechanism must be disabled after the file permission bits are
changed with a chmod(2). Additional mechanisms do not need to be
disabled when a chmod is done. Most vendors that have implemented NFSv4 ACLs have taken the approach of "discarding" ACLs during a chmod(2). This is a bit heavy handed, since a user went through the trouble of crafting a bunch of ACLs, only to have chmod(2) come through and destroy all of their hard work. It was this single issue that was the biggest hurdle to POSIX compliance with ZFS in implementing NFSv4 ACLs. In order to achieve this Sam, Lisa and I spent far too long trying to come up with a model that would preserve as much of the original ACL, while still being useful. What we came up with is a model that retains additional access methods, and disabled, but doesn't delete alternate access controls. Sam and Lisa have filed an internet draft which has the details about the chmod(2) algorithm and how to make NFSv4 ACLs POSIX complient.
So whats cool about this
Lets assume we have the following directory /sandbox/test.dir.Its initial ACL looks like:
% ls -dv test.dir
drwxr-xr-x 2 ongk bin 2 Nov 15 14:11 test.dir
0:owner@::deny
1:owner@:list_directory/read_data/add_file/write_data/add_subdirectory
/append_data/write_xattr/execute/write_attributes/write_acl
/write_owner:allow
2:group@:add_file/write_data/add_subdirectory/append_data:deny
3:group@:list_directory/read_data/execute:allow
4:everyone@:add_file/write_data/add_subdirectory/append_data/write_xattr
/write_attributes/write_acl/write_owner:deny
5:everyone@:list_directory/read_data/read_xattr/execute/read_attributes
/read_acl/synchronize:allow
Now if I want to give "marks" the ability to create files, but not subdirectories in this
directory then the following ACL would achieve this.
First lets make sure "marks" can't currently create files/directories
$ mkdir /sandbox/bucket/test.dir/dir.1
mkdir: Failed to make directory "/sandbox/test.dir/dir.1"; Permission denied
$ touch /sandbox/test.dir/file.1
touch: /sandbox/test.dir/file.1 cannot create
Now lets give marks add_file permission
% chmod A+user:marks:add_file:allow /sandbox/test.di
% ls -dv test.dir
drwxr-xr-x+ 2 ongk bin 2 Nov 15 14:11 test.dir
0:user:marks:add_file/write_data:allow
1:owner@::deny
2:owner@:list_directory/read_data/add_file/write_data/add_subdirectory
/append_data/write_xattr/execute/write_attributes/write_acl
/write_owner:allow
3:group@:add_file/write_data/add_subdirectory/append_data:deny
4:group@:list_directory/read_data/execute:allow
5:everyone@:add_file/write_data/add_subdirectory/append_data/write_xattr
/write_attributes/write_acl/write_owner:deny
6:everyone@:list_directory/read_data/read_xattr/execute/read_attributes
/read_acl/synchronize:allow
Now lets see if it works for user "marks"
$ id
uid=76928(marks) gid=10(staff)
$ touch file.1
$ ls -v file.1
-rw-r--r-- 1 marks staff 0 Nov 15 10:12 file.1
0:owner@:execute:deny
1:owner@:read_data/write_data/append_data/write_xattr/write_attributes
/write_acl/write_owner:allow
2:group@:write_data/append_data/execute:deny
3:group@:read_data:allow
4:everyone@:write_data/append_data/write_xattr/execute/write_attributes
/write_acl/write_owner:deny
5:everyone@:read_data/read_xattr/read_attributes/read_acl/synchronize
:allow
Now lets make sure "marks" can't create directories.
$ mkdir dir.1
mkdir: Failed to make directory "dir.1"; Permission denied
The write_owner permission is handled in a special way. It allows for a user to "take" ownership of a file. The following example will help illustrate this. With the write_owner a user can only do a chown(2) to himself or to a group that he is a member of.
We will start out with the following file.
% ls -v file.test
-rw-r--r-- 1 ongk staff 0 Nov 15 14:22 file.test
0:owner@:execute:deny
1:owner@:read_data/write_data/append_data/write_xattr/write_attributes
/write_acl/write_owner:allow
2:group@:write_data/append_data/execute:deny
3:group@:read_data:allow
4:everyone@:write_data/append_data/write_xattr/execute/write_attributes
/write_acl/write_owner:deny
5:everyone@:read_data/read_xattr/read_attributes/read_acl/synchronize
:allow
Now if user "marks" tries to chown(2) the file to himself he will get an error.
$ chown marks file.test
chown: file.test: Not owner
$ chgrp staff file.test
chgrp: file.test: Not owner
Now lets give "marks" explicit write_owner permission.
% chmod A+user:marks:write_owner:allow file.test
% ls -v file.test
-rw-r--r--+ 1 ongk staff 0 Nov 15 14:22 file.test
0:user:marks:write_owner:allow
1:owner@:execute:deny
2:owner@:read_data/write_data/append_data/write_xattr/write_attributes
/write_acl/write_owner:allow
3:group@:write_data/append_data/execute:deny
4:group@:read_data:allow
5:everyone@:write_data/append_data/write_xattr/execute/write_attributes
/write_acl/write_owner:deny
6:everyone@:read_data/read_xattr/read_attributes/read_acl/synchronize
:allow
Now lets see who "marks" can chown the file to.
$ id
uid=76928(marks) gid=10(staff)
$ groups
staff storage
$ chown bin file.test
chown: file.test: Not owner
So "marks" can't give the file away.
$ chown marks:staff file.test
Now lets look at an example to show how a user can be granted special delete permissions. ZFS doesn't create any delete permissions when a file is created, instead it uses write_data/execute for permission to write to a directory and execute to search the directory.
Lets first create a read-only directory and then give "marks" the ability to delete files.
% ls -dv test.dir
dr-xr-xr-x 2 ongk bin 2 Nov 15 14:11 test.dir
0:owner@:add_file/write_data/add_subdirectory/append_data:deny
1:owner@:list_directory/read_data/write_xattr/execute/write_attributes
/write_acl/write_owner:allow
2:group@:add_file/write_data/add_subdirectory/append_data:deny
3:group@:list_directory/read_data/execute:allow
4:everyone@:add_file/write_data/add_subdirectory/append_data/write_xattr
/write_attributes/write_acl/write_owner:deny
5:everyone@:list_directory/read_data/read_xattr/execute/read_attributes
/read_acl/synchronize:allow
Now the directory has the following files:
% ls -l
total 3
-r--r--r-- 1 ongk bin 0 Nov 15 14:28 file.1
-r--r--r-- 1 ongk bin 0 Nov 15 14:28 file.2
-r--r--r-- 1 ongk bin 0 Nov 15 14:28 file.3
Now lets see if "marks" can delete any of the files?
$ rm file.1
rm: file.1: override protection 444 (yes/no)? y
rm: file.1 not removed: Permission denied
Now lets give "marks" delete permission on just file.1
% chmod A+user:marks:delete:allow file.1
% ls -v file.1
-r--r--r--+ 1 ongk bin 0 Nov 15 14:28 file.1
0:user:marks:delete:allow
1:owner@:write_data/append_data/execute:deny
2:owner@:read_data/write_xattr/write_attributes/write_acl/write_owner
:allow
3:group@:write_data/append_data/execute:deny
4:group@:read_data:allow
5:everyone@:write_data/append_data/write_xattr/execute/write_attributes
/write_acl/write_owner:deny
6:everyone@:read_data/read_xattr/read_attributes/read_acl/synchronize
:allow
$ rm file.1
rm: file.1: override protection 444 (yes/no)? y
Lets see what a chmod(1) that changes the mode would do to a file with a ZFS ACL.
We will start out with the following ACL which gives user bin read_data and write_data permission.
$ ls -v file.1
-rw-r--r--+ 1 marks staff 0 Nov 15 10:12 file.1
0:user:bin:read_data/write_data:allow
1:owner@:execute:deny
2:owner@:read_data/write_data/append_data/write_xattr/write_attributes
/write_acl/write_owner:allow
3:group@:write_data/append_data/execute:deny
4:group@:read_data:allow
5:everyone@:write_data/append_data/write_xattr/execute/write_attributes
/write_acl/write_owner:deny
6:everyone@:read_data/read_xattr/read_attributes/read_acl/synchronize
:allow
$ chmod 640 file.1
$ ls -v file.1
-rw-r-----+ 1 marks staff 0 Nov 15 10:12 file.1
0:user:bin:write_data:deny
1:user:bin:read_data/write_data:allow
2:owner@:execute:deny
3:owner@:read_data/write_data/append_data/write_xattr/write_attributes
/write_acl/write_owner:allow
4:group@:write_data/append_data/execute:deny
5:group@:read_data:allow
6:everyone@:read_data/write_data/append_data/write_xattr/execute
/write_attributes/write_acl/write_owner:deny
7:everyone@:read_xattr/read_attributes/read_acl/synchronize:allow
In this example ZFS has prepended a deny ACE to take away write_data permission. This
is an example of disabling "alternate" access methods. More details about
how ACEs are disabled are described in internet draft.
The ZFS admin guide and the chmod(1) manpages have many more examples of setting ACLs and how the inheritance model works.
With the ZFS ACL model access control is no longer limited to the simple "rwx" model that UNIX has used since its inception.