Overview¶
ACL Structure¶
An NFSv4 ACL has the following structure:
An ACL (
ACL) has zero or more entries.An ACL entry, in short ACE, (
Entry) has these attributes:The type of the entry (
entry_type).This field determines whether the ACE allows or denies access.
The tag type of the entry (
tag_type).Together with the qualifier field this field controls for whom this ACE describes access permissions.
The principal (aka qualifier) of the entry (
qualifier).The ID of a user or group for whom this ACE describes access permissions. This field makes sense only for entries of tag type
ACL_USERorACL_GROUP.Typically checked against the proper effective IDs of a process.
The set of permissions (
permset).This field defines what kind of access the process matching this ACE has for accessing the associated file.
The set of flags (
flagset).They are used to control and/or view the inheritance of ACLs.
A permission set (
Permset) in an ACE has just methods to set and reset permission bits.A flag set (
Flagset) in an ACE has just methods to set and reset flag bits.
Implementation Note¶
The implementation is fairly low-level. The provided “abstractions” are leaky. Most operations are implemented by an immediate call into the native C implementation – the given classes are just thin wrappers around the OS level handles/pointers.
Usage Examples¶
Get an ACL from a file with and print it:
import nfs4acl with nfs4acl.ACL(file="_tmp/mnt") as acl: # print in the standard form print(acl) # print with numeric IDs instead print(acl.to_any_text( options=nfs4acl.const.ACL_TEXT_NUMERIC_IDS))Be sure to use the
withconstruct or callclose()to cleanup any associated OS resources.
Iterate over all entries:
from nfs4acl import * with ACL(file="_tmp/mnt") as acl: for entry in acl: # do something ...
Append a new entry with:
from nfs4acl import * entry = Entry(acl)or:
from nfs4acl import * entry = acl.append()
Prepend a new entry at the begin:
from nfs4acl import * entry = Entry(acl, pos=0)
Delete an existing entry from an ACL:
# find the entry ... # delete it entry.delete()
Warning
After adding or deleting entries all existing entries (and dependent permission and flag sets) will be invalid.
Re-using such items afterwards is outright dangerous because these items are typically implemented as descriptors (i.e. pointers) into an existing ACL structure. After manipulating the number of entries an existing object can point to another entry or to nowhere (e.g. because the ACL got reallocated completely).
While the parent ACL instance is able to account for this, the package applies proper protections against re-use of child items after such operations.
from nfs4acl import * with acl: # get the entries as list entries = list(acl) # all entries are valid for e in entries: assert e # insert a new one at the beginning new_entry = Entry(acl, pos=0) # # ATTENTION : Re-using entries is invalid because # the underlying ACL structure got # rearranged. # The entries therefore have been # invalidated by the package. # for e in entries: # now all the existing old entries are invalid assert not e # # and some other operation like this will raise # a ValueError exception # e.delete()
Make a file executable for the owner:
from nfs4acl import * with ACL(fd=myfile.fileno()) as acl: entries = filter( lambda x: x.tag_type == const.ACL_USER_OBJ) if entries: for entry in entries: entry.permset.add(const.ACL_EXECUTE) else: # # new entry for the owner: # add one with just the execute permission # entry = acl.append() entry.tag_type = const.ACL_USER_OBJ entry.entry_type = const.ACL_ENTRY_TYPE_ALLOW ps = entry.permset ps.add(const.ACL_EXECUTE) # # NOTE: This changed the entry inline because it # is a descriptor into the entry. # No need to to # entry.permset = ps # # apply this to the file acl.applyto(myfile)
Copy a complete ACL entry from another ACL:
import nfs4acl with nfs4acl.ACL(file="source") as source: with nfs4acl.ACL(file="destination") as destination: # find the correct entry pair ... destination_entry.copy(source_entry) assert destination_entry == source_entry
Create a new empty ACL:
import nfs4acl acl = nfs4acl.ACL() # of course... assert acl is not None # ... but an ACL is logically False if there are no # entries assert not acl # you have to close an empty ACL also acl.close()
Copy a flag set from another ACL:
import nfs4acl with nfs4acl.ACL(file="source") as source: with nfs4acl.ACL(file="destination") as destination: # find the correct entry pair ... # copy: now the flagset setter comes really handy destination_entry.flagset = source_entry.flagset assert destination_entry.flagset == source_entry.flagset
Copy a complete ACL:
from nfs4acl import * with ACL(file="source") as given_acl: with ACL(acl=given_acl) as the_copy: # do something with the copy ... # maybe you want it to save into a different file the_copy.applyto(file="destination")If you just want to copy an ACL from one file to another file unchanged:
from nfs4acl import * with ACL(file="source") as acl: acl.applyto(file="destination")
Create an ACL from a text representation:
from nfs4acl import * with ACL(file="source") as given_acl: with ACL(text=str(given_acl)) as the_copy: # it is a real copy ... assert not (given_acl is the_copy) # .. but logically identical assert given_acl == the_copy
Unit Tests¶
Preparation¶
The unit tests need a clean scratch area in the filesystem with support for NFSv4 ACLs. On FreeBSD the ZFS filesystem is a perfect match for this.
To setup a clean filesystem test area with NFSv4 ACL support there is
a helper script tests/zfstest.sh. It has to be run as
user root with the current working directory in tests.
The script helps to create a file-backed ZFS pool named “nfs4aclpool”
and helps to mount a temporary filesystem at mountpoint _tmp/mnt
as scratch area for the unit tests.
Usage:
Setup the pool:
cd tests ./zfstest.sh pool
It creates an empty imagefile in
_tmpand uses this to create a file-backed ZFS pool named “nfs4aclpool”. A completely new image file will be created automatically. An existing file will be deleted first.Setup the filesystem at the mountpoint
_tmp/mntand make the real user who runs the tests the owner:cd tests ./zfstest.sh mount <userid>
Develop and run unit tests …
Unmount the working space:
cd tests ./zfstest.sh umount
Destroy the ZFS pool:
cd tests ./zfstest.sh unpool
Remove the image file if needed:
rm _tmp/zfspool.img
Running¶
Unit tests are executed by:
cd tests python test.py
Unit tests use Python’s built-in unittest package and have no
other external dependencies.