Linuxのユーザー・グループとアクセス権限
所有者・所属グループ・その他のどの権限が使われるかを計算し、読取りと名前の操作を比較します。
説明
Linuxの通常のアクセス権は、所有者、所有グループ、その他の三組で表します。各組のr・w・xは読取り・書込み・実行を表し、ディレクトリのxはパスをたどる探索権として働きます。許可判定は利用者のUIDとグループの情報を対象の属性へ照合して行います。
ayaはUID1000でグループ2000にも所属し、report.txtはUID2001・GID2000・mode640とします。この表から適用される権限を求め、所有者に一致する場合の優先順、ディレクトリの探索と削除、umaskによる作成時の違いを順に確かめます。ACLやcapabilityなどの追加規則は含めない通常権限のモデルです。
1利用者と対象ファイルの属性
mode640の各桁は所有者6、グループ4、その他0です。r=4、w=2、x=1のビットを加えた数で表します。
user = {'uid':1000,'groups':{1000,2000}}UIDと所属グループ; report = {'uid':2001,'gid':2000,'mode':0o640}ファイル属性; print(user['uid'],sorted(user['groups']))主体の識別情報; print(oct(report['mode']))8進のモードayaは所有者2001ではありませんが、ファイルのグループ2000に所属しています。まずこの一致関係で適用する組を選び、その組のビットで許可を判断します。
2適用する組を選ぶ
所有者に一致すれば上位の3ビット、そうでなくグループが一致すれば中央、どちらでもなければ下位を選びます。
def bits(subject,obj):主体と対象を受け取る
shift = 6 if subject['uid']==obj['uid'] else 3 if obj['gid'] in subject['groups'] else 0所有者→グループ→その他
return (obj['mode']>>shift)&7選んだ3ビット所有者、グループ、その他の許可を全部足し合わせる規則ではありません。どの組が使われるかを先に確定することが、このモデルでの判定の要点です。
3読めるが書けないファイル
選ばれた権限とr・w・xの有無を表示します。
grant = bits(user,report)ayaに適用される組; print(grant, bool(grant&4),bool(grant&2),bool(grant&1))値・読取り・書込み・実行4・True・False・Falseです。グループのrだけが適用されています。ファイル自体に読取り権があっても、その場所までのディレクトリに探索権がなければパスから到達できません。
4所有者に一致する場合はグループの権限を使わない
ファイル所有者だけをayaへ変え、modeを040にします。所有者の権限は0で、グループにはrがあります。
owned = dict(report,uid=1000,mode=0o040)所有者だけ0の条件; print(bits(user,owned))適用された組; print(bool(bits(user,owned)&4))読取りの判定選ばれるのは所有者の0で、読取りはFalseです。ayaがグループにも所属していても、所有者の組で拒否された後にグループの許可へ切り替える規則にはなっていません。
5ディレクトリの読取りと探索
/srvのグループ権限がrだけの場合と、r+xの場合を比べます。通常のパス参照には経路上の各ディレクトリのxが必要です。
directory = {'uid':2001,'gid':2000,'mode':0o740}探索なしのディレクトリ; print(bool(bits(user,directory)&4),bool(bits(user,directory)&1))一覧読取りと探索; directory['mode'] = 0o750グループへ探索を追加; print(bool(bits(user,directory)&1) and bool(bits(user,report)&4))到達とファイル読取り最初は名前一覧の読取りがTrue、探索がFalseです。グループへxを追加した後はreport.txtへの到達と読取りが可能になります。rはディレクトリ内の名前一覧、xはその中の名前をたどる操作に関わります。
6ファイルの書込みと名前の削除
通常のファイル削除は、親ディレクトリの名前を取り除く操作です。親へw+xを与え、ファイルのwと比較します。
directory['mode'] = 0o730親にw+xを許可; can_unlink = (bits(user,directory)&3)==3親の書込みと探索; print(bool(bits(user,report)&2),can_unlink)内容変更と名前削除ファイルの書込みはFalseでも、親の名前削除に必要なw+xはTrueです。ここではsticky bit、読取り専用マウント、immutable属性などの追加制限がないとします。ファイルの中身を変更する権限と名前を消す権限は対象が異なります。
7umaskは要求されたビットを落とす
通常ファイルの要求モード666、ディレクトリの要求モード777へumask027を適用します。ビットの反転とANDで許可を取り除きます。
mask = 0o027作成時のマスク; print(oct(0o666 & ~mask),oct(0o777 & ~mask))ファイルとディレクトリ; print(oct(0o600 & ~mask))元から制限した要求通常ファイル640、ディレクトリ750、元から600なら600です。umaskは要求にないビットを追加しません。8進数の単純な引き算ではなく、該当ビットを落とす操作です。デフォルトACLがある場合は作成権限の扱いに追加規則があります。
8新しいumaskは既存ファイルを変えない
umaskを077へ変え、新規ファイルの計算結果と既存reportのモードを並べます。
mask = 0o077新しいマスク; new_mode = 0o666 & ~mask新規作成のモード; print(oct(new_mode),oct(report['mode']))既存属性との比較新規要求は600、既存reportは640のままです。umaskは作成時に使う規則であり、既存ファイルのモードを変更するchmodの代わりにはなりません。
まとめ
通常権限は所有者・グループ・その他の順に該当する組を選びます。
ファイルの読書き、ディレクトリの一覧・探索・名前削除は、それぞれの対象の権限で判断します。
umaskは作成時の要求からビットを落とし、既存ファイルの権限は変更しません。
操作履歴と解説を振り返れます。
Python 3
user = {'uid':1000,'groups':{1000,2000}}; report = {'uid':2001,'gid':2000,'mode':0o640}; print(user['uid'],sorted(user['groups'])); print(oct(report['mode']))1000 [1000, 2000] 0o640
def bits(subject,obj):
shift = 6 if subject['uid']==obj['uid'] else 3 if obj['gid'] in subject['groups'] else 0
return (obj['mode']>>shift)&7grant = bits(user,report); print(grant, bool(grant&4),bool(grant&2),bool(grant&1))4 True False False
owned = dict(report,uid=1000,mode=0o040); print(bits(user,owned)); print(bool(bits(user,owned)&4))0 False
directory = {'uid':2001,'gid':2000,'mode':0o740}; print(bool(bits(user,directory)&4),bool(bits(user,directory)&1)); directory['mode'] = 0o750; print(bool(bits(user,directory)&1) and bool(bits(user,report)&4))True False True
directory['mode'] = 0o730; can_unlink = (bits(user,directory)&3)==3; print(bool(bits(user,report)&2),can_unlink)False True
mask = 0o027; print(oct(0o666 & ~mask),oct(0o777 & ~mask)); print(oct(0o600 & ~mask))0o640 0o750 0o600
mask = 0o077; new_mode = 0o666 & ~mask; print(oct(new_mode),oct(report['mode']))0o600 0o640
演習完了