Was investigating why “nfs4_getfacl” doesn’t work, while “getfacl” does:
wdennis@cluster-js01 ~> nfs4_getfacl /mnt/shared_hf_cache/WDD_test_3
Operation to request attribute not supported: /mnt/shared_hf_cache/WDD_test_3
wdennis@cluster-js01 ~> getfacl /mnt/shared_hf_cache/WDD_test_3
getfacl: Removing leading '/' from absolute path names
# file: mnt/shared_hf_cache/WDD_test_3
# owner: wdennis
# group: hfcache
user::rwx
group::rwx
other::r-x
This on a Ubuntu 22.04 NFS client, against a TrueNAS SCALE 25.04.2.6 server.
Turned to my trusty assistant Claude, and here’s what he had to say about it:
The knfsd NFSv4 ACL Architecture
Linux’s knfsd supports FATTR4_ACL only through a POSIX→NFSv4 translation layer. When a client requests FATTR4_ACL, knfsd:Calls into the filesystem via vfs_get_acl() expecting POSIX ACL xattrs (system.posix_acl_access)
Translates those POSIX ACEs into NFSv4 ACE format on the wire
It has no path to directly expose ZFS’s natively-stored NFSv4 ACEs. Since your dataset has acltype=nfsv4, there are no system.posix_acl_access xattrs — and knfsd has nothing to translate from, so it returns NFS4ERR_ATTRNOTSUPP.What getfacl Is Actually Showing You
That output isn’t reading real ACL data — it’s synthesized from mode bits by the NFS client:user::rwx ← owner bits (0700)
group::rwx ← group bits (0070)
other::r-x ← other bits (0005)Notice there are no extended entries (no user:someuser:, no mask::). A real POSIX ACL would have a mask:: line the moment any named entry exists. The client is just dressing up stat() permission bits as POSIX ACL format. The actual ZFS NFSv4 ACEs are completely invisible from the NFS client.
Why This Worked on TrueNAS CORE (The Backstory)
This is a TrueNAS SCALE regression from CORE. TrueNAS CORE was FreeBSD-based, and the FreeBSD NFS server (nfsd) natively understands ZFS NFSv4 ACLs — it has a direct kernel path to expose them as FATTR4_ACL. This was a major selling point of CORE for enterprise shops using NFSv4 ACLs.When iXsystems migrated to Linux for SCALE, they got knfsd, which simply doesn’t have that ZFS NFSv4 ACL passthrough capability. The ZFS ACLs are intact on disk — you can see and set them from the TrueNAS shell with nfs4_getfacl/nfs4_setfacl locally — but they don’t propagate over NFS.
This is a known issue in the OpenZFS + knfsd ecosystem. There’s been upstream work on improving ZFS NFSv4 ACL VFS integration, but it’s not there yet as of SCALE 25.x.
Quick Sanity Check
To confirm the NFSv4 ACEs exist on-server but just aren’t reachable over NFS, run this from the TrueNAS shell:sh:
nfs4_getfacl /mnt/tank/shared_hf_cacheYou should see actual ACEs there. That confirms the data is fine — it’s purely a knfsd exposure problem.
(me:) Sadly, doesn’t work either on the TrueNAS OS either:
l-wdennis@tn-storage03-n02 ~ % nfs4_getfacl /mnt/tank/shared_hf_cache
Operation to request attribute not supported: /mnt/tank/shared_hf_cache6:40 PM
Claude responded: The Problem Goes Even Deeper(Investigated OpenZFS ACL interface incompatibility with standard Linux tools)
That failure on the local filesystem — not over NFS — reveals the real issue: OpenZFS on Linux does not expose NFSv4 ACLs via the system.nfs4_acl xattr that nfs4_getfacl expects.
nfs4_getfacl from the nfs4-acl-tools package works by calling:
c:
getxattr(path, “system.nfs4_acl”, buf, size)OpenZFS on Linux with acltype=nfsv4 stores NFSv4 ACEs internally in its own dnode format, but it doesn’t surface them through that xattr name. So nfs4_getfacl fails even locally — not an NFS problem at all.
The TrueNAS SCALE Way
On SCALE, the ACL subsystem lives in the middleware. The correct tool is midclt:sh:
midclt call filesystem.getacl /mnt/tank/shared_hf_cacheOr with pretty-print:
sh:
midclt call filesystem.getacl /mnt/tank/shared_hf_cache | python3 -m json.toolThe Full Picture Now
OpenZFS (acltype=nfsv4)
└── stores NFSv4 ACEs internally in dnode
└── does NOT expose via system.nfs4_acl xattr
├── nfs4_getfacl fails locally ← what you just hit
├── knfsd can’t read them → ATTRNOTSUPP over NFS
└── getfacl synthesizes from mode bits onlyTrueNAS middleware (truenas_pylibzfs / libzfs)
└── has direct access to ZFS ACL internals
└── midclt call filesystem.getacl ← the only working pathThis is a TrueNAS CORE → SCALE regression in a nutshell. On CORE (FreeBSD), getfacl natively spoke both POSIX and NFSv4 ACLs because FreeBSD’s VFS ACL layer is ACL-model-aware. On SCALE (Linux), the standard tools simply don’t have that integration with OpenZFS — iXsystems had to re-implement ACL management entirely in their Python middleware layer, bypassing standard Linux tooling altogether.
l-wdennis@tn-storage03-n02 ~ % midclt call filesystem.getacl /mnt/tank/shared_hf_cache | python3 -m json.tool
{
“path”: “/mnt/tank/shared_hf_cache”,
“user”: null,
“group”: null,
“uid”: 0,
“gid”: 4000,
“acltype”: “NFS4”,
“acl”: [
{
“tag”: “owner@”,
“type”: “ALLOW”,
“perms”: {
“BASIC”: “FULL_CONTROL”
},
“flags”: {
“BASIC”: “INHERIT”
},
“id”: -1,
“who”: null
},
{
“tag”: “group@”,
“type”: “ALLOW”,
“perms”: {
“READ_DATA”: true,
“WRITE_DATA”: true,
“APPEND_DATA”: true,
“READ_NAMED_ATTRS”: true,
“WRITE_NAMED_ATTRS”: true,
“EXECUTE”: true,
“DELETE”: true,
“DELETE_CHILD”: true,
“READ_ATTRIBUTES”: true,
“WRITE_ATTRIBUTES”: true,
“READ_ACL”: true,
“WRITE_ACL”: true,
“WRITE_OWNER”: false,
“SYNCHRONIZE”: true
},
“flags”: {
“BASIC”: “INHERIT”
},
“id”: -1,
“who”: null
},
{
“tag”: “everyone@”,
“type”: “ALLOW”,
“perms”: {
“READ_DATA”: true,
“WRITE_DATA”: false,
“APPEND_DATA”: false,
“READ_NAMED_ATTRS”: true,
“WRITE_NAMED_ATTRS”: false,
“EXECUTE”: true,
“DELETE”: false,
“DELETE_CHILD”: false,
“READ_ATTRIBUTES”: true,
“WRITE_ATTRIBUTES”: false,
“READ_ACL”: true,
“WRITE_ACL”: false,
“WRITE_OWNER”: false,
“SYNCHRONIZE”: true
},
“flags”: {
“BASIC”: “INHERIT”
},
“id”: -1,
“who”: null
}
],
“aclflags”: {
“autoinherit”: false,
“protected”: false,
“defaulted”: false
},
“trivial”: false
}Now We Can See the Full Picture
The ACL is actually well-designed and non-trivial — and this output explains exactly what’s getting lost in translation over NFS.Decoding the ACL
Tag Effective Permissions Notable Omission
owner@ FULL_CONTROL + inherit —
group@ Everything except WRITE_OWNER + inherit Can’t change ownership
everyone@ Read + execute + inherit No write, no delete
The group@ ACE is the interesting one. It’s deliberately one step below full control — the group (gid=4000, your hfcache group) can read, write, delete files, and manage named attributes, but cannot take ownership. That’s an intentional security boundary, not an accident.Why trivial: false Matters
ZFS considers an ACL “trivial” if it’s perfectly equivalent to the three mode bits (owner/group/other rwx). This one is not — it carries semantics that can’t be represented in 9 bits:DELETE and DELETE_CHILD being explicitly granted to group@
WRITE_OWNER: false for group@ while everything else is true
SYNCHRONIZE being explicitly enumerated
Yet here’s what the NFS client sees via synthesized POSIX:user::rwx ← FULL_CONTROL collapsed to rwx ✓
group::rwx ← almost-full-control collapsed to rwx (WRITE_OWNER loss invisible)
other::r-x ← read+execute ✓
The mode bits are a lossy projection. The WRITE_OWNER restriction on group@ silently disappears. Any client relying on NFS to enforce that boundary isn’t getting it.The Inheritance Flag
All three ACEs have “BASIC”: “INHERIT”, which in NFSv4 terms means both FILE_INHERIT and DIR_INHERIT are set. This is why WDD_test_3 (a child directory created by wdennis) shows owner: wdennis with the same effective permissions — the ACEs propagated correctly on the server. But the NFS client still only sees the synthesized mode bits for children too.Summary of What’s Actually Broken
On ZFS (server): Full NFSv4 ACL enforced correctly ✓
Inheritance working correctly ✓
WRITE_OWNER boundary enforced ✓Over NFS (client): All of the above → collapsed to 9 mode bits
nfs4_getfacl: broken (knfsd + OpenZFS gap)
ACL enforcement: whatever the server does, clients can’t inspect or set it
setfacl from client: only sets POSIX xattrs (ignored by ZFS nfsv4 dataset)
If you need NFS clients to be able to read or set ACLs on this share, you’re essentially stuck until either OpenZFS gains a proper system.nfs4_acl xattr interface or knfsd gains ZFS-aware ACL passthrough — neither of which is imminent. For now, midclt call filesystem.setacl from the TrueNAS side (or the WebUI) is the only reliable management path.Why This Worked on TrueNAS CORE (The Backstory)
This is a TrueNAS SCALE regression from CORE. TrueNAS CORE was FreeBSD-based, and the FreeBSD NFS server (nfsd) natively understands ZFS NFSv4 ACLs — it has a direct kernel path to expose them as FATTR4_ACL. This was a major selling point of CORE for enterprise shops using NFSv4 ACLs.
When iXsystems migrated to Linux for SCALE, they got knfsd, which simply doesn’t have that ZFS NFSv4 ACL passthrough capability. The ZFS ACLs are intact on disk — you can see and set them from the TrueNAS shell with nfs4_getfacl/nfs4_setfacl locally — but they don’t propagate over NFS.
Interesting… I guess there is no way to secure things using ACLs on the client side? Or will it work correctly even though it’s displaying the simplified POSIX perm’s?