l

l

してみるテストロゴ
Apache 2.4系でHTTP/2サーバを構築してみるテスト。

FreeBSDのZFSのACLを設定してみる。

従来、UNIX系のOSでは、UNIXファイルシステム(UFS)の「モード」と呼ばれる形式によって、ファイルのアクセス権限を管理していました。

ところが、ZFSでは、「モード」に加えて、アクセスコントロールリスト(ACL)と呼ばれる新しい形式で、ファイルのアクセス権限を管理します。

最初に、ACLを確認するためのgefaclコマンドの使い方を確認し、次にUFSの「モード」を確認します。

その後、ACLの読み方と、setfaclコマンドによる設定の仕方を見ていきます。

最後に、UFSの「モード」では表現できない、ZFSのACLがある場合、chmodコマンドが、「Operation not permitted」と出力して失敗するケースを説明します。

まだの方は、まず、下のリンクからZFSの初期設定を行ってください。

なお、ZFSのオリジナルは、今では知らない人の方が多い気もするSolarisとよばれるUNIX系OSですが、Solarisでは、「ls」コマンド及び、「chmod」コマンドを、ACLに対応させる拡張が行われているので、このページに記載のコマンド例は動きません。

FreeBSD(とたぶんLinux)でZFSをお使いの方に限定の説明です。

getfaclコマンドの使い方

まず、ZFSをマウントしているディレクトリの配下にcdコマンドで移動します。

$ cd /tank/home/username 

次に、今回の例で使うディレクトリを作ります。名前は何でもいいのですが、ここでは、「sample」としてみます。

$ mkdir sample

さっそく、作ったディレクトリのACL(アクセス権限リスト)を確認してみます。

$ getfacl sample
# file: sample ←ファイル名/ディレクトリ名(ディレクトリエントリ名)
# owner: root ←ディレクトリエントリの所有者
# group: wheel ←ディレクトリエントリのグループ
            owner@:rwxp--aARWcCos:-------:allow ←所有者のアクセス権限
            group@:r-x---a-R-c--s:-------:allow ←グループに所属している人のアクセス権限
         everyone@:r-x---a-R-c--s:-------:allow ←上記以外のユーザ、グループのアクセス権限

最初の行に、ファイル名かディレクトリ名が出力されます。

ファイルやディレクトリをまとめて、「ディレクトリエントリ」と呼びます。

次の行に、ディレクトリエントリの所有者、続いてグループが表示されます。

最後に、ACLエントリ(アクセス権限)が1行ずつ並びます。

ACLエントリの1行は、「:」(コロン)で区切られています。左から、ACLタグ、ACL修飾子、ACL継承フラグ、ACLタイプの順番です。

出力の4行目の「owner@」がACLタグ、「rwxp--aARWcCos」がACL修飾子、「------」がACL継承フラグ、「allow」がACLタイプです。

これを理解するためには、まずUNIXファイルシステム(UFS)を理解しておくことが重要です。

UNIXファイルシステム(UFS)の「モード(mode)」の復習

そこで、ACLを説明する前に、まず、UNIXファイルシステム(UFS)の「モード」の仕組みを軽く振り返ってみます。

UNIXファイルシステムのファイルやディレクトリ(ディレクトリエントリ)には、ユーザIDとグループIDを一つずつ設定できます。

ユーザIDとグループIDは必須で、一つのディレクトリエントリに、必ず一つずつユーザIDとグループIDが設定されます。

 

この2つのIDを使って利用者を3つに分類します。

ユーザIDのユーザを「所有者」、グループIDのグループに所属している「グループ所属者」、所有者でもグループ所属者でもない、「その他」の3つです。

この3つに分類された「所有者」「グループ所属者」「その他」のそれぞれの対象者に、権限を許可したり禁止したりします。

who(誰への) permission(許可) ACL表現
who
シンボル
対象者 perm
シンボル
ファイルにおける権限 ディレクトリにおける権限
u 所有者 r 読み込みできる ディレクトリの内容を読み出せる 可能
g グループ所属者
o その他
u 所有者 w 書き込みできる ファイルを追加、削除できる
g グループ所属者
o その他
u 所有者 x 実行できる 検索できる
g グループ所属者
o その他
- s 実行時 setuid - 不可能
実行時 setgid -
t sticky ビット

具体的には、権限の対象者をwhoシンボルと呼ばれる文字で示し、権限の内容をpermシンボルと呼ばれる文字で示します。

whoシンボルとpermシンボルの間には「+」や「-」のopシンボル(オペレータシンボル)を入れて、許可するのか、禁止するかを指定します。

例えば「u+x」は所有者にファイルの実行を許可します。「g-w」はグループ所属者の書き込みを禁止します。

これらをchmodコマンドに指定する事で、既存のファイルやディレクトリに対する権限を指定します。

既存のディレクトリエントリに対する権限(モード)の設定

chmodコマンドは、既存のファイルやディレクトリの「モード」と呼ばれるアクセス権を設定するためのコマンドです。

chmodコマンドの書式
chmod [whoシンボル][opシンボル][permシンボル] [filename]

下記の例では、「u+x」は所有者にファイルの実行を許可します。

$ chmod u+x sample

「g-w」はグループ所属者の書き込みを禁止します。

$ chmod g-w sample

この方法では、所有者だけに権限を追加したり、グループだけに書き込みを禁止したりと言った、一部の対象者に対する差分の権限を指定していますが、全員の権限を一気に設定する方法もあります。

chmodコマンドの書式
chmod [モード] [filename]

これは、permシンボルの権限を数値化して、r:「読み込み」を4、w:「書き込み」を2、x:「実行」を1として、許可する権限の合計を、所有者、グループ、その他の順番に並べたものです。

これをファイルの「モード」と呼びます。

例えば、

$ chmod 755 sample

の755の内、先頭の7は「所有者」の「読み込み(4)」+「書き込み(2)」+「実行(1)」の合計で、フルアクセスを許可します。

続く5が、「グループ」への「読み込み(4)」+「実行(1)」の許可、

最後の5が、「その他」への「読み込み(4)」+「実行(1)」の許可になります。

この「755」が「モード」と呼ばれるアクセス権限を示すデータになります。

もう一つ例を挙げると、

$ chmod 700 sample

とすれば、「所有者」だけがフルアクセス、「グループ」と「その他」は何もできなくなります。

この「700」の部分が、ファイルの「モード」と呼ばれるデータになります。

 

上の表にあるとおり、「モード」には、「ACL」で表現可能な部分と、不可能な部分があります。

逆に「ACL」は、「モード」にはない柔軟性で、アクセス権限を指定できます。

この違いがセキュリティー上の問題となることがありますが、後述の通り、ZFSでは、ZFSのaclinheritプロパティーと、aclmodeプロパティーの値で、初期状態では問題が起きないようになっています。

新規のディレクトリエントリに対する権限(モード)の設定

chmodコマンドは、既存のファイルやディレクトリに設定された、アクセス権限(モード)を設定できました。

それでは、新規にファイルやディレクトリを作った場合の、「モード」はどうなるのでしょうか?

新規にファイルやディレクトリを作った場合の「モード」は、「umask」と呼ばれる数値を元に決定します。

それを説明する前に、新規にファイルやディレクトリを作ったときに、設定されていると便利な「モード」を考えてみます。

 

ディレクトリを作った場合、実行権限が付かなければ、検索ができなかったり、cdコマンドでディレクトリの中に入ることができなくなり不便です。

そのため、新規のディレクトリには、「755」といったモードを設定すると、不便無く使えそうです。

一方、ディレクトリのように、ファイルにいきなり実行権限が付くと、何でもかんでも実行できることになってしまい困ったことになりそうです。

つまり、新規のファイルには、「644」といったモードを設定したいところです。

 

このように、ファイルとディレクトリでは、異なるモードが設定されると、便利です。

これを一つの値で実現するのが、umaskとなります。

umaskには、許可したくない(禁止したい)権限を、モードのように列挙します。

7だと全てを禁止、2だと書き込みを禁止、0だと何も禁止しません。

たとえば、所有者には何も禁止せず、グループとその他には書き込みを禁止する場合、「022」をumaskに設定します。

 

このumaskの数値と、ファイルの初期モード「666」、もしくはディレクトリの初期モード「777」の論理積(AND演算)を行います。

umaskのmaskとは、「AND演算」のことですね。

ファイルを作ると、モード「666」と、umask「022」のAND演算の結果、モード「644」が設定されます。

一方、ディレクトリを作ると、モード「777」と、umask「022」のAND演算の結果、モード「755」が設定されます。

ちなみに、umaskに027を設定しておけば、新しく作るディレクトリ(750)、ファイル(640)ともに、「その他」の人が見られなくなります。

この仕組みによって、禁止したい権限をumaskに設定すれば、新しいファイル、新しいディレクトリのそれぞれに、ふさわしい「モード」を設定できます。

 

さきほどの表にあるように、「モード」と「ACL」には、双方に真似ができない違いがあるため、厳密な表現では無いのですが、ZFSにおいて、chmodコマンドに相当するのが「setfaclコマンド」で、umaskに相当するのが「ACL継承フラグ」になります。

ACLエントリの構成

さて本題に戻ります。先ほども説明したとおり、ACLエントリは、

[ACLタグ]:[ACL修飾子]:[ACL継承フラグ]:[ACLタイプ]

のように4つのパートに分かれています。

            owner@:rwxp--aARWcCos:-------:allow 
            group@:r-x---a-R-c--s:-------:allow 
         everyone@:r-x---a-R-c--s:-------:allow 

ACLタグ(ACL tag)

ACLエントリの先頭にはACLタグと呼ばれる情報が表示されます。

ACLタグとは、ユーザ名、グループ名のいずれかです。

上記例では、UNIXファイルシステムとの互換性のために用意された、「owner@」(所有者に相当)、「group@」(グループに相当)、「everyone@」(その他に相当)の3つのACLタグが表示されています。

ACL修飾子(ACL qualifier)

ACL修飾子は、実際の権限を表現したデータです。

ACL修飾子によって表現されたアクセス権限が許可(allow)されるか、拒否(deny)されるかは、後述の「ACLタイプ」で決まります。

以下の14個の権限があります。OSやZFSのバージョンによっては実装されていない権限もあります。

Short Long ファイルにおける権限 ディレクトリにおける権限
r read_data ファイルを読み出せる ディレクトリを読み出せる
w write_data ファイルを書き込める ファイルを追加できる
x execute ファイルを実行できる ディレクトリの内容を検索できる
p append_data ファイルに追記できる サブディレクトリの追加できる。
D delete_child - 配下のファイル、もしくはサブディレクトリエントリの削除
d delete ファイルを削除できる ディレクトリを削除できる。
a read_attributes 基本属性を読み出せる。
A write_attributes 基本属性を変更できる。
R read_xattr 拡張属性を読み出せる。
W write_xattr 拡張属性を作成できる。
c read_acl ACLの読み込み。
C write_acl ACLを変更できる。
o write_owner 所有者の変更
s synchronize (未実装)

ここで注意してほしいのは、ディレクトリ専用の「D」です。

配下のファイルおよびサブディレクトリを削除するアクセス権が、親ディレクトリにつくことがあります。

親ディレクトリのACL 配下の削除対象のディレクトリエントリのACL 対象を
D d 削除できる
D - 削除できる
- d 削除できる
- - 削除できない

つまり、削除対象のファイルのやディレクトリの削除権限「d」を持っていなくても、対象を削除できる組み合わせがあります。

同じく、親ディレクトリに「wx」のアクセス権があると、「D」と同じ効果となります。

親ディレクトリのACL 配下の削除対象のディレクトリエントリのACL 対象を
wx d 削除できる
wx - 削除できる
- d 削除できる
- - 削除できない

 

なお、-vオプションを付けると、以下のように上の表のLongの応答が表示されるので、少しわかりやすくなります。

$ getfacl -v sample
# file: sample
# owner: root
# group: wheel
            owner@:read_data/write_data/execute/append_data/read_attributes/write_attributes/read_xattr/write_xattr/read_acl/write_acl/write_owner/synchronize::allow
            group@:read_data/execute/read_attributes/read_xattr/read_acl/synchronize::allow
         everyone@:read_data/execute/read_attributes/read_xattr/read_acl/synchronize::allow

ちょっと長いですが、読めばわかるので安心ですね。

ACL継承フラグ(ACL inheritance flags)

ACL継承フラグは、あるディレクトリで、新しく作ったファイルやサブディレクトリに、適用される当該ディレクトリのACLエントリを指定します。

Short Long  
f file_inherit ディレクトリで作られたファイルは、当該ディレクトリのACLを継承する
d dir_inherit ディレクトリで作られたサブディレクトリは、当該ディレクトリのACLを継承する
i inherit_only 継承にのみ使用されるACLエントリ
n no_propagate 直下のサブディレクトリには継承されますが、それよりも下層のサブディレクトリには伝搬しないACLエントリであることを示す。
I inherited 継承されたACLエントリであることを示す。

ACL継承フラグの実際の動作は、セキュリティー上のリスクを避けるため、後述のZFSのaclinheritプロパティーと、aclmodeプロパティーの影響を受けます。

 

ACLタイプ(ACL type)

ACLタイプは、ACL修飾子で指定された権限について、許可するか、拒否するかを指定します。

ACLは上から順番に評価されます。

まず、ファイルを操作しているユーザのIDが、ACLタグに含まれるか判断します。

ユーザのIDが、ACLタグに含まれていた場合、ACL修飾子にユーザのファイル操作についてのアクセス権限が含まれているか判断します。

アクセス権限も含まれていた場合、ACLタイプに従って、「許可」ないし「禁止」を判断します。

ACLタグにユーザIDが含まれていなかったり、ACL修飾子にアクセス権限が含まれていなかった場合、次のACLエントリを評価します。

こうして、全てを含むACLエントリが見つかるまで繰り返します。

 

ちなみに、所有者には、たとえACLタイプで、拒否(deny)されていたとしても read_acl, write_acl, read_attributes, write_attributes の各権限が与えられます。

そのため、たとえ、aclinheritプロパティーが「restricted」になっていて、新しく作ったファイルのACLに、所有者のwrite_aclが削られたとしても、所有者はACLの変更ができます。

ZFSのaclinheritプロパティーと、aclmodeプロパティー

ZFSでは、これまで説明してきた「ACL」と「モード」の両方を共存できます。

しかし共存できるということは、chmodで「モード」を設定したけつもりだけど、chmodから設定できない「ACL」が残っていて、意図しない人にアクセスを許してしまうといった、セキュリティー上の問題が発生します。

これを防ぐため、ZFSのデフォルトでは、chmodコマンドを実行するとchmodコマンドから設定できる「モード」と関係の無い「ACL」が、削除される設定になっています。

ACLを知らない人がいても安全になるような配慮ですが、これはこれで、設定した「ACL」が消えて、やはり意図したとおりのアクセス権限となりません。

そこで、新規のファイルやディレクトリを作るときのumaskの動作と、ZFSのACLとの整合性を取るための「aclinheritプロパティー」と、chmodコマンドにおけるZFSのACLの扱いを決める「aclmodeプロパティー」が用意されています。

aclinheritプロパティー

aclinheritプロパティーは、ファイルやディレクトリーを作った場合の、ACL継承の動作を決めます。

$ zfs get aclinherit /tank/home/username
NAME                PROPERTY    VALUE          SOURCE
tank/home/username  aclinherit  restricted     default

上記の例にあるようにデフォルトでは、aclinheritがrestrictedに設定されています。

aclinheritプロパティーには、以下の値を入れることができます。

説明
discard ACLエントリは継承されません。
noallow ACLタイプがdenyのエントリだけ継承されます。
restricted ACLエントリが継承される場合、write_ownerとwrite_aclを除いたアクセス権限が継承されます。
passthrough ACLエントリを変更せずに、継承するように設定されたすべてのACLエントリを継承します。
passthrough-x 上の「passthrough」と同じ意味を持ちますが、owner@、group@、およびeveryone@のACLエントリは、umaskで設定されたファイル作成モードに、実行ビットが設定されていた場合に限り、「実行(x:execute)」のアクセス権限を継承します。

aclmodeプロパティー

aclmodeプロパティーは、chmodを使った場合に、ACLをどうするか指定します。

$ zfs get aclmode  /tank/home/username
NAME                PROPERTY  VALUE        SOURCE
tank/home/username  aclmode   discard      default

上の例にあるように、デフォルトでは、aclmodeがdiscardに、設定されています。

aclmodeプロパティーには、以下の値を入れることができます。

説明
discard chmodで設定したモードを表すACLエントリ以外のエントリが削除されます。
groupmask グループのACLアクセス権限について、ACLタイプがallowのものを、chmodが設定したモードのグループ権限を超えないようにマスクします
passthrough chmodが設定したモードを表すACLエントリを設定する以外に、既存のACLを変更しません。
restricted ファイルや、ディレクトリが、chmodのモードで表現できないACLを持っていたり、実行時setuidビットや実行時setgidビット、スティッキービットを設定する場合、chmodはエラーを返します。この場合、chmodのmodeで表現できないACLを削除してからchmodを使用する必要があります。

デフォルトでは、discardが設定されていますが、これではchmodコマンドを使う度に、せっかく設定したACLが削除されることになってしまい、使いにくい状態です。

「discard」は、ACLを知らない人が居る場合には有用かもしれませんが、ACLが突然消える可能性もあります。

そこで、「restricted」にしておくと、特定のchmodを実行するためには、「モード」の表現可能範囲を超えたACLを、あらかじめ手動で消しておく必要があるため、ACLが突然消えることが無くなります。

ひととおり理解したのなら、aclmodeプロパティーを、restrictedに変更しておきましょう。

なおZFSのプロパティーはデータセット毎(つまりマウントポイント毎)に設定します。

データセットとは、zfs listコマンドで表示される1行のことです。

今回は、以下のようなデータセットがある場合を例に考えます。

# zfs list
NAME                                   USED  AVAIL  REFER  MOUNTPOINT
tank                                  2.87G   120G  8.84M  /tank
tank/home                              468M   120G    19K  /tank/home
tank/home/username                     468M   120G   468M  /tank/home/username

データセット名が、「tank/home/username 」(マウントポイントで言うと、 /tank/home/username)のプロパティーを変える場合、下のようにコマンドを打って、プロパティの中身を確認し、

# zfs get aclmode /tank/home/username
NAME              PROPERTY  VALUE        SOURCE
tank/home/username  aclmode   discard      default

aclmodeが、デフォルトの「discard」になっていたら、さらに以下のようにコマンドを打って、プロパティーを書き換えます。

このとき、zfs setコマンドの最後に指定するのは、先頭に/(スラッシュ)が無い「データセット名」です。マウントポイントではエラーになるので注意してください。

# zfs set aclmode=restricted tank/home/username

最後に、確認します。

# zfs get aclmode /tank/home/username
NAME              PROPERTY  VALUE        SOURCE
tank/home/username  aclmode   restricted   local

「restricted」となっていることを確認します。

setfaclコマンドの使い方

簡単に、setfaclコマンドの使い方を説明します。

今回は、以下のような「sample」ディレクトリを例にします。

$ getfacl sample/
# file: sample/
# owner: username
# group: wheel
            owner@:rwxp--aARWcCos:-------:allow
            group@:r-x---a-R-c--s:-------:allow
         everyone@:r-x---a-R-c--s:-------:allow	

この例では、ACLエントリID=0にowner(ファイル「mode」の「所有者」)、ACLエントリID=1にgroup(ファイル「mode」の「グループ」)、ACLエントリID=2にeveryone(ファイル「mode」の「その他」)のACLエントリが存在しています。

ACLエントリの追加

このディレクトリに、もう一人(便宜上「another」さんとします。)だけ、フルコントロールできる人を追加したい場合を考えます。

この人は、groupやeveryoneよりも先に評価されたいACLエントリです。

つまり、ACLエントリIDの1の前に、追加したいので

「-aオプション」の後ろに、1を入れます。

続いて、ユーザを意味する「u:」とユーザ名(「another」さん)を入れます。

その後、ACL修飾子を入れますが、ACL修飾子は14文字のアルファベットなので、毎回打ち込むのはちょっと苦行です。

そこで、ACL修飾子の代わりに、ACLセットと呼ばれる、ACL修飾子の別名を入れることができます。

今回は、「フルコントロール」を意味するACLセット、「:full_set」と、ACL継承フラグに何も設定しないことを意味する「::」を続けます。

そして、「許可」を意味する「allow」を書きます。最後に、ディレクトリ名「sample」をいれます。

まとめると以下のようになります。

$ setfacl -a 1 u:another:full_set::allow sample
$ getfacl sample
# file: sample
# owner: username
# group: wheel
            owner@:rwxp--aARWcCos:-------:allow
      user:another:rwxpDdaARWcCos:-------:allow
            group@:r-x---a-R-c--s:-------:allow
         everyone@:r-x---a-R-c--s:-------:allow

ちなみに、full_setの他には、以下のACLセットが使えます。

ACLセット名 ACL修飾子
full_set rwxpDdaARWcCos
modify_set rwxpDdaARWc--s
read_set r-----a-R-c---
write_set -w-p---A-W----

 

ACLエントリの変更

上記の例では、ACLエントリID=1の人(「another」さん)に、「D」や「d」の権限が付いて、 所有者よりも多くのアクセス権限が付いて気に入らない、と思う方もいるかもしれません。

ACLエントリの権限だけを変更する場合は、一旦、ACLエントリを消して、再度追加する方法が推奨されています。

ACLエントリの削除は、「-Xオプション」に、ACLエントリIDの数字を添えて使います。

$ setfacl -x 1 sample
$ getfacl sample/
# file: sample/
# owner: username
# group: wheel
            owner@:rwxp--aARWcCos:-------:allow
            group@:r-x---a-R-c--s:-------:allow
         everyone@:r-x---a-R-c--s:-------:allow

そして、再びACLエントリを追加します。今度は、full_setのようなACLセットの代わりに、owner@のACL修飾子である「rwxp--aARWcCos」をコピペします。

$ setfacl -a 1 u:another:rwxp--aARWcCos::allow sample
$ getfacl sample
# file: sample
# owner: username
# group: wheel
            owner@:rwxp--aARWcCos:-------:allow
      user:another:rwxp--aARWcCos:-------:allow
            group@:r-x---a-R-c--s:-------:allow
         everyone@:r-x---a-R-c--s:-------:allow

ACLエントリの削除

さらに、「everyone」や「group」といったUFSの「モード」との互換性のために用意されたACLエントリを削除することもできます。

たとえば、上記の状態から、everyoneのACLエントリを消すためには、

$ setfacl -x 3 sample
$ getfacl sample
# file: sample
# owner: username
# group: wheel
            owner@:rwxp--aARWcCos:-------:allow
      user:another:rwxp--aARWcCos:-------:allow
            group@:r-x---a-R-c--s:-------:allow

とします。

ちなみに、ここで、「ls」コマンドを打つと、

$ ls -la
total 2
drwxr-xr-x  3 username  wheel  3  2月 14 16:15 .
drwxr-xr-x  5 username  wheel  6  2月 14 13:44 ..
drwxr-x---+ 2 username  wheel  2  2月 14 16:15 sample

となります。ディレクトリエントリの「モード」から、「その他」のrとxが無くなっています。

あと、見慣れない「+」が付いていますが、これは、ファイルの「モード」では表現できないACLが付いていることを意味します。

chmodコマンドが失敗するケース

このとき、ディレクトリが存在するZFSのデータセットの「aclmodeプロパティー」が、「restricted」となっている場合、再度「その他(others)」がアクセスできるようにするため、chmodを使うと、エラーとなります。

つまり、以下のように、

$ chmod 755 sample
chmod: sample: Operation not permitted

となって、chmodが失敗します。

つまり、ファイルの「モード」では表現できないACLが付いているために失敗します。

ちなみに、「aclmodeプロパティー」がデフォルトの「discard」となっていれば、chmodコマンドは成功しますが、さきほど追加した、「another」さん向けのACLエントリが削除されます。

NEXT >> FreeBSDでZFSを設定してみる。

©Copyrights 2015-2019, non-standard programmer

このサイトは、あくまでも私の個人的体験を、綴ったものです。 軽く参考程度にご利用ください。