家族共有フォルダのACLをバックアップスクリプトで消した話
`homenas`のSamba共有で家族ごとにNFSv4 ACLを丁寧に設定していたのに、深夜のバックアップスクリプトが`chmod -R`を叩いて全部リセットしていたことがあります。ZFSのACLがなぜPOSIXのモードビットと二重管理になるのか、aclmode/aclinheritでどこまで制御できるのか、そしてこの問題を統一しようとして結局カーネルに入らなかったrichaclの構想を整理します。
homenasのSamba共有で、家族それぞれのフォルダにNFSv4 ACLを丁寧に設定していたことがあります。ある朝、子どもから「自分のフォルダに書き込めない」と言われて調べたところ、深夜に走らせていたバックアップスクリプトがchmod -Rを実行しており、苦労して設定したACLエントリが根こそぎ消えていました。原因はaclmode=discardのままだったことです。
「ls -lで見えるパーミッションと、実際にアクセスできるかどうかが一致しない」というこの手の違和感は、ZFS on Linuxでファイルサーバーを運用していると一度は踏みます。原因の多くは、ZFSが内部的に持っているNFSv4スタイルのACLと、Linuxの標準的な権限表示であるPOSIXモードビットが、実は別々のモデルであることに起因します。この記事では、ZFSのACL実装がなぜNFSv4方式なのか、POSIXモードビットとどう「翻訳」されているのか、そして両者を統一しようとして最終的にはメインラインカーネルに入らなかったrichaclという提案から、現場で押さえておくべき設計上の注意点を整理します。
ZFSのACLはPOSIX ACLではなくNFSv4 ACL
Linuxのファイルシステムで「拡張ACL」といえば、多くの管理者はgetfacl/setfaclで操作するPOSIX ACL(POSIX.1e草案ベース)を思い浮かべます。ext4やXFSはこの方式です。ところがZFSは、Solaris由来の設計を引き継いでおり、POSIX ACLではなくNFSv4 ACLを実装しています。これはOpenZFS on Linuxでも変わりません。
NFSv4 ACLはPOSIX ACLよりモデルが豊富で、次のような違いがあります。
| 項目 | POSIX ACL | NFSv4 ACL(ZFS) |
|---|---|---|
| エントリの種類 | allow相当のみ | allowとdenyを明示的に持てる |
| 継承の粒度 | デフォルトACL(ディレクトリのみ)で一括継承 | file_inherit/dir_inherit/inherit_only/no_propagateをエントリ単位で指定 |
| 権限の細かさ | read/write/executeの3種 | read_data/write_data/append_data/delete_childなど十数種の個別権限 |
| 操作コマンド | getfacl/setfacl | ls -v/chmod A+...(Solaris系)、Linuxではnfs4_getfacl/nfs4_setfacl(nfs4-acl-toolsパッケージ) |
ZFS上のACLを確認・編集するには、ext4で使い慣れたgetfacl/setfaclではなく、nfs4-acl-toolsパッケージが提供する専用コマンドが必要です。
nfs4_getfacl /tank/shared/report.docx
nfs4_setfacl -a A::user@domain:rwatTnNcCy /tank/shared/report.docx
setfaclをそのままZFS上のファイルに使おうとすると、多くの環境で「Operation not supported」相当のエラーになります。この時点で「ZFSはACLに対応していないのか」と誤解して調査が止まってしまうケースが実務では少なくありません。
モードビットとACLの二重管理という現実
ここが最大の落とし穴です。NFSv4 ACLを持っていても、Linux上のZFSファイルには依然としてPOSIXのモードビット(rwxr-xr-xのような9ビット)も同時に存在します。ls -lが表示するのはこのモードビットであり、ACLの内容そのものではありません。ACLが設定されているかどうかは、多くのls実装で末尾に付く+記号(例: -rw-r--r--+)でしか気づけません。
この二重構造は、次の2つのプロパティによって「どちらを正とするか」「相互にどう反映するか」が決まります。
aclmode:chmodでモードビットを変更したときに、既存のACLをどう扱うかaclinherit: 新規作成されたファイル/ディレクトリが、親ディレクトリのACLをどう継承するか
aclmodeの主な値は次の通りです。
| aclmode | 挙動 |
|---|---|
discard(過去のデフォルト) | chmod実行時、モードビットで表現できない拡張ACLエントリをすべて破棄する |
groupmask | モードビットのgroup権限でACLエントリのマスクをかける(POSIX ACLのmaskに近い挙動) |
passthrough(現在の多くの環境でのデフォルト) | chmodはモードビット相当の基本エントリのみ書き換え、他の明示的なACLエントリは維持する |
restricted | ACLが設定されたファイルに対するchmod自体を拒否する |
aclmode=discardの環境で、Sambaなどが細かく設定したNFSv4 ACLに対して誰かが何気なくchmod 644を実行すると、苦労して設定したACLエントリが黙って消えます。homenasのバックアップスクリプトがやっていたのはまさにこれで、権限を揃えるつもりで書いたchmod -Rが、Samba側で丁寧に設定したACLを一括で吹き飛ばしていました。運用でACLを積極的に使うデータセットでは、aclmode=restrictedにしてchmodそのものを止めるか、少なくともaclmode=passthroughにしておくことを検討してください。
zfs set aclmode=passthrough tank/shared
zfs set aclinherit=passthrough tank/shared
aclinheritの継承モデルとpassthroughの罠
aclinheritは新規ファイル作成時のACL継承挙動を決めます。
| aclinherit | 挙動 |
|---|---|
discard | 継承フラグの付いたACLエントリを一切継承しない |
noallow | denyエントリのみ継承し、allowエントリは継承しない |
restricted(デフォルト) | write_acl/write_ownerなどACL自体を書き換えられる権限を継承時に取り除く |
passthrough | 継承フラグに従ってすべてのエントリをそのまま継承する |
NFSv4 ACLエントリは、file_inherit/dir_inherit/inherit_only/no_propagateという継承フラグを個別に持てます。これはPOSIX ACLの「デフォルトACLをディレクトリ単位で一括継承する」という単純なモデルより表現力が高い一方、aclinherit=passthroughにした状態で深い階層のディレクトリを作り続けると、各エントリが子孫すべてに伝播し、数階層下のファイルに、作成者も把握していない大量の継承ACLエントリが積み重なるという状態になりがちです。nfs4_getfaclで確認したときに読み切れないほどのエントリが並んでいたら、まずこの伝播の設計を疑ってください。
restricted(デフォルト)は、継承時にwrite_acl/write_ownerを落とすことで「子ファイルの作成者が、親の意図しない形でACLを書き換えてしまう」事故を防ぐための保守的な設定です。Sambaで意図的にACL継承チェーンを構築する場合はpassthroughが必要になりますが、その場合は継承フラグの設計(どのエントリをfile_inheritだけにし、どれをdir_inheritまで含めるか)をディレクトリ設計時点で決めておかないと、後から棚卸しがほぼ不可能になります。
richaclという統一の試みと、それが失敗した理由
Linux上でのNFSv4 ACLの扱いに一貫性がない状況を整理しようとした提案がrichaclです。Andreas GruenbacherがLinuxカーネル向けに提案した、NFSv4 ACLセマンティクスをベースにしたリッチなACLモデルで、Btrfsやext4への実装が試みられ、一時期はメインラインへのマージも検討されていました。
richaclが目指したのは、ZFSがSolaris由来で独自に持っているようなACLの表現力を、Linuxの汎用VFS層で一般化することでした。しかし最終的にrichaclはLinuxメインラインカーネルにマージされませんでした。背景にあったのは、まさにここまで説明してきたモードビットとの相互変換の複雑さです。
- POSIXのモードビット(
chmodで変更可能な9ビット)とNFSv4スタイルのallow/denyエントリを、常に矛盾なく相互変換するアルゴリズムを標準化する必要があった umaskの扱い(NFSv4 ACLには本来umaskという概念がない)をLinuxの慣習とどう整合させるかで議論が紛糾した- 既存のPOSIX ACL対応アプリケーション・ツール群(
getfacl/setfacl、多くのバックアップ/同期ツール)との互換性維持コストが大きかった - レビューの過程でセマンティクスの複雑さそのものが「Linuxのファイル権限モデルとして受け入れるべきか」という設計思想レベルの反対に直面した
結果として、Linuxの汎用ファイルシステム(ext4、Btrfs、XFSなど)は今もPOSIX ACLのままで、NFSv4スタイルのリッチなACLは、ZFS(独自のACL実装を持ち込んでいる)とNFSv4プロトコル自体(サーバー/クライアントの実装依存)という、限られた領域にとどまっています。ZFSがaclmode/aclinheritという独自プロパティで「モードビットとの折り合いをどうつけるか」を管理者に選ばせているのは、richaclが解決しようとして解決しきれなかった問題を、カーネル全体ではなくファイルシステム単体の裁量で個別に処理している結果だと理解すると筋が通ります。
Sambaでの実務: 二重モデルが表面化する典型的な現場
ZFS上のデータセットをSambaで共有するケースは、この二重モデルの問題が最も表面化しやすい現場です。
- WindowsクライアントのエクスプローラーでACLタブから権限を変更する操作は、Samba経由で
nfs4_setfacl相当の処理としてZFSのNFSv4 ACLに書き込まれます - 一方、Linux側で管理者が
chmodやchownを実行すると、aclmodeの設定次第でこのACLが上書き・破棄される可能性があります smb.conf側のinherit acls、inherit permissions、map acl inheritといった設定は、SambaレイヤーでのACL継承挙動を制御しますが、これはZFS側のaclinheritとは別レイヤーの設定であり、両方が一致していないと「Windows側では継承されるはずなのに、Linux側で見ると継承されていない」という食い違いが起きます
これは自分だけの特殊な事故ではなく、Sambaで丁寧にACLを設定した共有フォルダに対して、バックアップスクリプトや別の管理者がchown -Rやchmod -Rを実行するたびに起こり得る、かなり典型的なパターンです。aclmode=discardのままだと、この一括操作でACLエントリが静かに失われ、後日「特定ユーザーだけアクセスできなくなった」という問い合わせが来て初めて発覚します。ACLを運用の柱に据えるデータセットでは、モードビットを変更する可能性のある操作(バックアップツール、rsyncの--perms、CI/CDのデプロイスクリプトなど)を洗い出し、aclmodeの設定と矛盾しないか事前に確認することが欠かせません。
あのバックアップスクリプトはどうしたか
homenasのバックアップスクリプトは、chmod -Rの行をまるごと削除し、aclmodeをpassthroughに変更しました。それ以来、次を確認するようにしています。
- ZFS上のACLは
getfacl/setfaclではなくnfs4_getfacl/nfs4_setfacl(nfs4-acl-tools)で操作しているか aclmodeがdiscardのまま放置されていないか。ACLを積極的に使うデータセットではpassthroughまたはrestrictedを明示的に設定するchmod/chownを実行しうる運用(バックアップ、rsync、デプロイスクリプト)が、設定したaclmodeの挙動と矛盾しないかaclinherit=passthroughを使う場合、継承フラグの設計をディレクトリ階層ごとに決めているか- Sambaで共有している場合、
smb.confのinherit acls/map acl inheritとZFSのaclinheritが矛盾していないか
richaclは、この二重モデルをLinuxカーネル全体で統一しようとした提案でしたが、モードビットとの相互変換の複雑さと既存ツールとの互換性コストからメインラインには入りませんでした。ZFSのaclmode/aclinheritは、その問題をファイルシステム単体の裁量で個別に処理している実例です。権限まわりの設計判断も、special vdevやRAIDZの本数と同様、後から気づいて直そうとすると手戻りが大きくなる領域だと、家族の不満げな顔を見て学びました。