権限はファイルではなく動作に付く
一言でいうと
rwx の3文字は、ファイルに付いたときとディレクトリに付いたときで意味が異なります。この違いを知らないと、「読み取り専用のファイルがなぜ消えるのか」のような事故に何度も出会うことになります。
なぜ必要なのか
共有ディレクトリをひとつ作り、チームで一緒に使うことにしました。各自がファイルをアップロードし、他人のファイルには触れないと約束しました。ところがある日、誰かのファイルが消えました。ファイルの権限は 644 で、所有者もそのままでした。
ここで「権限が 644 なのにどうして消えたのか」と問うても、答えは出ません。削除はファイルに対する操作ではなく、ディレクトリに対する操作だからです。ファイルを消すとはディレクトリから名前をひとつ外すことで、それには、そのディレクトリへの書き込み権限があれば足ります。ファイル自体が読み取り専用でも他人のものでも関係ありません。
Unix はこの問題のために、ビットをもうひとつ用意しました。それが sticky ビットです。
どう動くのか
まず、同じ文字が対象によってどう変わるかを整理します。
| ビット | ファイルでは | ディレクトリでは |
|---|---|---|
| r | 内容を読める | 一覧を見られる(ls) |
| w | 内容を書き換えられる | エントリを作成・削除できる |
| x | 実行できる | その中に入り、パスを通過できる |
ディレクトリに r だけがあって x がないと、名前は見えても、その中のファイルの情報は読めません。逆に x だけがあって r がないと、一覧は見られませんが、正確な名前がわかればアクセスできます。
ここに、特殊ビットが3つ重なります。8進数4桁のうち、いちばん前の桁です。
- setuid(4): 実行ファイルに付けると、実行した人ではなくファイルの所有者の権限で動きます。
passwdコマンドが代表例です。 - setgid(2): ディレクトリに付けると、その中に新しく作られるファイルが、作成者の基本グループではなくディレクトリのグループを継承します。共有ディレクトリの要となる仕組みです。
- sticky(1): ディレクトリに付けると、その中のエントリはファイルの所有者・ディレクトリの所有者・root だけが削除できます。
/tmpが1777である理由がこれです。
したがって、チーム共有ディレクトリの標準的な形は 2770(グループの継承 + グループ外の遮断)で、誰でもアップロードできるが他人のものは消せないようにするには 1777 です。
現場での姿
1つ目は、グループを追加したのに反映されないことです。 usermod -aG devs alice を実行しても、alice がすでにログインしていたシェルには反映されません。グループの一覧はプロセスが作られるときに決まるからです。新しいログインセッションが必要です。ここで -a(append)を外すと、既存の補助グループがすべて消えます。運用サーバーでよく起きる事故です。
2つ目は、8進数と記号による表記です。 chmod 750 run.sh のように絶対値で指定する方法と、chmod g+x,o-rwx run.sh のように相対的に直す方法があります。デプロイスクリプトでは、現在の状態にかかわらず結果が同じでなければならないので、絶対値が安全です。逆に、大量のファイルを扱うときは、記号による表記のほうがミスを減らせます。
3つ目は、権限の確認は目ではなくコマンドで行うことです。 ls -l の出力にある -rwxr-x--- を人が数えるより、stat -c %a のほうが正確です。採点スクリプトや点検スクリプトを書くときは、常に stat を使います。
新しく作ったファイルの権限は誰が決めるのか
chmod を打ったことはあっても、ファイルが最初に作られるときの権限がどこから来るのかは知らない人が多いものです。これを決めるのが umask です。
プログラムがファイルを作るとき、望む権限をカーネルに伝えます。通常のファイルはたいてい 666、ディレクトリは 777 を要求します。カーネルはそこから umask で立っているビットを引いて作ります。umask が 022 なら、ファイルは 644、ディレクトリは 755 になります。つまり umask は「許可」ではなく「禁止」のリストで、値を読むときにその向きを取り違えると、計算が逆になります。
ここで、よく問題になることが2つあります。1つ目は、実行ビットは umask では付かないことです。要求が 666 なので、そもそも実行ビットがなく、だから新しく作ったスクリプトには必ず chmod +x が必要です。2つ目は、umask はプロセスごとにあって子が引き継ぐため、サービスの umask は、そのサービスを起動した環境が決めることです。人がシェルから起動するときと、システムが起動時に立ち上げるときで値が違うため、手で再起動したサービスが作ったファイルだけ権限が違う、という状況が生じます。こうしたサービスは、設定に umask を明示しておくのが安全です。
権限がおかしいときに確認する順序も決めておくとよいでしょう。誰が作ったか → そのときの umask は何だったか → ディレクトリに setgid が掛かっているか → そのあと誰が chmod したか。 特に3つ目は、先ほど見たようにグループが変わるので、ファイルの所有グループが作成者の基本グループと違うなら、そのディレクトリを先に見ます。
最後に、アクセス制御リスト(ACL)にも触れておきます。所有者・グループ・その他の3つだけでは「この人にだけ追加で読み取りを許可する」を表現できないため、そのときは setfacl で個別の権限を上乗せします。ただし、ls -l では末尾に + が1つ付くだけで目立たないので、権限が説明できないときは getfacl で確認する必要があります。
次のラボですること
ユーザーとグループを自分で作り、setgid と sticky を掛けた共有ディレクトリを構成したうえで、最後に「このファイルが期待どおりの権限か」を検査して終了コードで知らせる点検スクリプトを書きます。