sudoers ポリシー・プラグインは、ユーザにどんな sudo 権限があるかを決定する。 このプラグインが sudo のデフォルトのポリシー・プラグインである。 ポリシーの運用は /etc/sudoers ファイルによって行われるが、 LDAP を使用することも可能である。ポリシーを設定するときの書式は、 「SUDOERS ファイルの書式」セクションで詳しく説明している。 sudoers ポリシーの情報を LDAP に格納することについては、 sudoers.ldap(5) をご覧いただきたい。
sudo は sudo.conf(5) ファイルを参照して、 どのポリシー・プラグインと入出力ロギング・プラグインをロードするかを決める。 sudo.conf(5) ファイルが存在しない場合や、存在しても Plugin 行を含まない場合は、 sudoers プラグインがポリシーの決定や 入出力ロギングに使用されることになる。 sudoers プラグインを使用するように明示的に設定するには、 sudo.conf(5) に次のよう書き込めばよい。
Plugin sudoers_policy sudoers.so
Plugin sudoers_io sudoers.so
sudo 1.8.5 以来、sudoers プラグインに対する任意の引き数を sudo.conf(5) で指定することが可能になっている。 そうした引き数が存在する場合は、プラグインのパスの後ろに続けることになる (すなわち、sudoers.so の後ろだ)。 引き数がいくつもあるときは、ホワイトスペースで区切って指定すればよい。 一例を挙げる。
Plugin sudoers_policy sudoers.so sudoers_mode=0400
以下のプラグインに対する引き数が使用できる。
- ldap_conf=pathname
- ldap_conf 引き数を使用すると、 ldap_conf ファイルのパスをデフォルトのものから変更することができる。
- ldap_secret=pathname
- ldap_secret 引き数を使用すると、 ldap_secret ファイルのパスをデフォルトのものから変更することができる。
- sudoers_file=pathname
- sudoers_file 引き数を使用すると、 sudoers ファイルのパスをデフォルトのものから変更することができる。
- sudoers_uid=uid
- sudoers_uid 引き数を使用すると、 sudoers ファイルの所有者をデフォルトのものから変更することができる。 指定には、ユーザ ID 番号を使うべきである。
- sudoers_gid=gid
- sudoers_gid 引き数を使用すると、 sudoers ファイルのグループをデフォルトのものから変更することができる。 指定には、グループ ID 番号を使用しなければならない (グループ名ではない)。
- sudoers_mode=mode
- sudoers_mode 引き数を使用すると、 sudoers ファイルのファイル・モードをデフォルトのものから変更することができる。 指定には、8 進数の値を使うべきである。
sudo.conf(5) の設定についてさらに詳しいことをお知りになりたかったら、 sudo.conf(5) のマニュアルをご覧になっていただきたい。
sudoers セキュリティポリシーでは、ユーザはたいていの場合、 本人であることを証明してからでなければ。sudo を使用できない。ただし、 sudo の実行者が root だったり、変身対象ユーザが sudo の実行者と同一であったり、 ポリシーがその実行者やコマンドに対して認証を免除している場合は、パスワードが要求されることはない。 su(1) とは違って、sudoers ポリシーが認証に当たってチェックするのは、 sudo を実行するユーザの認証情報 (訳注: 通常はパスワード) であって、 変身対象ユーザの (あるいは、root の) 認証情報ではない。この動作は、後述する rootpw, targetpw, runaspw フラグによって変更することができる。
ポリシーに登録されていないユーザが sudo を使って、コマンドを実行しようとすると、 しかるべき権威者にメールが送付される。そうしたメールの宛先は、 後述する「デフォルト設定」の mailto 行によって設定できるが、 デフォルトでは root になっている。
sudo を使用する権限のないユーザが、-l や -v オプションを付けて sudo の実行を試みても、認証に失敗し、しかも mail_always または mail_badpass フラグが有効になっている場合を除いて、 メールは送付されないことに注意していただきたい。そうした動作にすることで、自分に sudo の使用が許可されているかどうか、ユーザが自分で判断できるようにしているのである。 sudo 実行の試みは (成功、失敗にかかわらず)、すべてログに記録される。 メールが送られるかどうかには関係がない。
sudo が root によって実行されたとき、環境変数 SUDO_USER が設定されていると、 sudoers ポリシーは、実際のユーザが誰かを判断するのに、その値を使用することになる。 ユーザとしては、この動作を利用することで、すでにルートシェルを起動している場合でも、 自分が sudo を介して実行したコマンドのログを取ることができる。 また、この動作のおかげで、sudo で実行するスクリプトやプログラムから呼び出される場合でさえ、 -e オプションが役に立つものになっている。ただし、そうした場合でも、 sudoers ファイルの参照はやはり root に対してなされるのであって、 SUDO_USER が指定しているユーザに対してではないことに注意していただきたい。
sudoers は、認証情報の一時保存 (credential caching) にユーザごとのタイムスタンプ・ファイルを使用する。 ユーザの認証が済むと、記録が書き込まれるが、それには、認証に使用された uid、 端末セッション ID、タイムスタンプ (利用できるならば、単調増加時計 (monotonic clock) を使用する) が含まれている。ユーザは、その後しばらくの間 (timestamp_timeout オプションによって変更されていなければ、5 分間)、パスワードなしで sudo を使うことができる。 sudoers はデフォルトでは、各 tty ごとに別の記録を使用する。 そこで、認証は、ユーザのログイン・セッションごとに独立して行われることになる。 tty_tickets オプションを無効にすれば、あるユーザのすべてのセッションに対して、 単一のタイムスタンプの使用を強制することができる。
sudoers は sudo の実行が成功したときも失敗したときも、 その旨を (エラーの内容とともに) syslog(3) や独自のファイル、 あるいはその両方に記録することができる。sudoers はデフォルトでは、 syslog(3) 経由でログを記録することになっているが、この動作はデフォルト設定の syslog と logfile を使って変更することができる。 ログファイルの書式については、「ログの書式」セクションの説明をご覧いただきたい。
また sudoers は、擬似 tty でコマンドを実行して、 すべての入力や出力をログに記録することもできる。 標準入力、標準出力、標準エラーが端末と結びついていない場合でさえ、 それをログに記録することができるのだ。入出力ロギングは、デフォルトでは ON になっていないが、 log_input や log_output オプションを使って有効にすることができる。 コマンド・タグの LOG_INPUT や LOG_OUTPUT を使用して有効にすることも可能だ。 入出力ログファイルがどんなふうに格納されるかについては、 「入出力ログファイル」セクションに詳細な説明がある。
環境変数はプログラムの動作に影響を与えることがあるので、sudoers は、 実行されるコマンドがユーザの環境からどんな変数を引き継ぐかについて、 制御する手段を用意している。すなわち、sudoers は二つの異なった方法で、 環境変数を処理することができる。
デフォルトでは env_reset オプションが有効になっている。 この場合、コマンドは新しい、最小の環境で実行されることになる。 AIX (及び PAM を使用していない Linux システム) では、/etc/environment ファイルの内容で環境が初期化される。新しい環境には、TERM, PATH, HOME, MAIL, SHELL, LOGNAME, USER, USERNAME, 及び SUDO_* という変数、 それに、呼び出し側のプロセスから来た変数で、env_check や env_keep オプションによって許可されたものが含まれている。これは、言わば、 環境変数のホワイトリストである。値が () で始まる環境変数は、 変数名と値の両方が env_keep や env_check の指定にマッチしないかぎり、 除去されるが、それは、bash シェルの古いバージョンでは関数と解釈されることになるからである。 1.8.11 より前のバージョンでは、そうした変数は無条件で除去されていた。
これに対して、env_reset オプションが無効になっている場合は、 env_check や env_delete オプションによって明示的に拒否されていないかぎり、 いかなる環境変数も呼び出し側のプロセスから継承される。 この場合、env_check や env_delete は、ブラックリストのように振る舞うわけだ。 値が () で始まる環境変数は、ブラックリストの一つにマッチしない場合でも、 必ず除去される。危険性のある環境変数のすべてをブラックリストに載せることは不可能なので、 env_reset を有効にしておくデフォルトの動作を採用することをお勧めする。
デフォルトでは、環境変数のマッチは変数名によって行われる。 しかしながら、マッチに使われるパターンに等号 ('=') が含まれる場合は、 変数名と値の両方がマッチしなければならない。たとえば、旧式の (shellshock 問題以前の export 方法による) bash のシェル関数にマッチさせるならば、次のように指定すればよいだろう。
env_keep += "my_func=()*"
旧式の bash のシェル関数は、デフォルトでは保存されないので、 "=()*" という後続部分がなかったら、こうした環境変数はマッチしないことになる。
- [訳注]:
- shellshock 以前の bash では、たとえば "my_func='() { ls -F; }'" といったシェル変数を export することで、 my_func という関数をサブシェルに渡すことができた。 shellshock 以後の bash では、この方法による関数の継承はできなくなり、 値が "() { ls -F; }" である my_func という変数が、 渡されるだけになっている。だから、 sudoers ファイルの Defaults 行に上のように書いても、 shellshock 以後の bash を使っているかぎり、 my_func という環境変数が保存されるだけで、関数が渡されるわけではない。
sudo が許可、または拒否する環境変数すべてのリストは、"sudo -V" を root の資格で実行したときの出力中に含まれている。このリストは、 sudo が実行されるオペレーティングシステムによって異なることに気をつけていただきたい。
PAM をサポートしているシステムで、pam_env モジュールが sudo に対して有効になっていると、 PAM の管理する環境にある変数が、環境にマージされることになるだろう。 ただし、PAM 環境にある変数がユーザの環境にすでに存在している場合に、 その値が上書きされることになるのは、 その変数が sudoers によって保存されていないときだけである。 すなわち、env_reset が有効になっているときは、 env_keep のリストによって sudo を実行するユーザの環境から保存された変数が、 PAM 環境にある変数より優先される。また、 env_reset が無効になっているときは、 sudo を実行するユーザの環境にある変数が、 env_delete のリストのパターンにマッチしていないかぎり、 PAM 環境にある変数よりも優先されるのである。
たいていのオペレーティングシステムのダイナミック・リンカは、 ダイナミック・リンキングを制御する働きがある環境変数を、 sudo もその一つである setuid 実行ファイルの環境から除去するようになっていることに注意していただきたい。 オペレーティングシステムによって名前は様々だが、_RLD*, DYLD_*, LD_*, LDR_*, LIBPATH, SHLIB_PATH などが、この範疇に含まれるだろう。 そうした変数は、sudo の実行が始まるよりも前に、環境から除去されるので、 sudo がそうした変数を保存することは不可能である。
特例として、sudo に -i (initial login) オプションが指定されている場合は、 sudoers は env_reset の有効・無効にかかわらず、環境を初期化する。 環境変数 DISPLAY, PATH, TERM は変更されないが、HOME, MAIL, SHELL, USER, LOGNAME は、変身対象ユーザのそれにセットされる。ATX (及び PAM を使用していない Linux システム) では、/etc/environment の内容も取り込まれる。 それ以外の環境変数はすべて捨てられる。
最後に、env_file オプションが設定されている場合は、 そのファイルに記載されたいかなる変数も、 すでに存在している環境変数と衝突しないかぎり、 そこで指定されている値にセットされることになる。
sudoers ファイルは二種類のエントリから構成されている。 (要するに変数である) エイリアスと (誰が何を実行できるかを指定している) ユーザ設定だ。 (訳注: 訳者としては、「エイリアス、デフォルト設定、ユーザ設定の三種類のエントリから構成されている」と言った方が、 現在の実態に合っているのではないかと思う。)
一人のユーザに複数のエントリがマッチするときは、順番に適用される。 複数の指定がマッチしている箇所については、最後にマッチしたものが使用される (それが一番明示的なマッチだとはかぎらないが)。
以下では sudoers ファイルの文法を拡張 Backus-Naur 記法 (EBNF) を用いて記述する。 EBNF を御存じないからといって、あきらめないでいただきたい。 わりと簡単なものだし、以下に出てくる定義には詳しい説明をつけておきますから。
EBNF は言語の文法を記述する簡潔で厳密な方法である。 EBNF の個々の定義は生成規則からできている。たとえば、
シンボル ::= 定義 | 別の定義 1 | 別の定義 2 ...
個々の生成規則は、ほかの生成規則を参照し、そのようにして言語の文法を作り上げている。 また EBNF には以下の演算子が含まれるが、正規表現で御存じの読者も多いだろう。 だが、いわゆる「ワイルドカード」文字と混同しないでいただきたい。 あれは別の意味を持っている。
- ?
- 直前のシンボル (または、シンボルのグループ) が、あってもなくてもよいことを意味する。 すなわちそのシンボルは、1 回現れてもよいし、1 回も現れないでもよい。
- *
- 直前のシンボル (または、シンボルのグループ) が 0 回以上現れる。
- +
- 直前のシンボル (または、シンボルのグループ) が 1 回以上現れる。
丸カッコを使うと、複数のシンボルをグループにまとめることができる。 なお、混乱を避けるため、以下の定義では、それが (シンボル名ではなく) 文字どおりの文字列や記号であることを示す場合には、シングルクォート (' ') で囲むことにする。
エイリアスには四種類ある。User_Alias, Runas_Alias, Host_Alias, Cmnd_Alias である。
Alias ::= 'User_Alias' User_Alias (':' User_Alias)* |
'Runas_Alias' Runas_Alias (':' Runas_Alias)* |
'Host_Alias' Host_Alias (':' Host_Alias)* |
'Cmnd_Alias' Cmnd_Alias (':' Cmnd_Alias)*
User_Alias ::= NAME '=' User_List
Runas_Alias ::= NAME '=' Runas_List
Host_Alias ::= NAME '=' Host_List
Cmnd_Alias ::= NAME '=' Cmnd_List
NAME ::= [A-Z]([A-Z][0-9]_)* 個々のエイリアスの定義は、次の形をとる。
Alias_Type NAME = item1, item2, ...
上記において Alias_Type は、User_Alias, Runas_Alias, Host_Alias, Cmnd_Alias のうちの一つである。NAME は、アルファベットの大文字、数字、 アンダースコア ('_') からなる文字列であるが、 先頭の文字はアルファベットの大文字でなければならない。 同じタイプのエイリアス定義を、コロンで (':') つないで、一行に複数書くこともできる。 たとえば、
Alias_Type NAME = item1, item2, item3 : NAME = item4, item5
既存のエイリアスを再定義するのは、文法エラーである。 異なるタイプのエイリアスに対して同じ名前を使用することはできるが、 お薦めできることではない。
エイリアスの有効な要素となるものの定義は、以下のようになる。
User_List ::= User |
User ',' User_List
User ::= '!'* user name |
'!'* #uid |
'!'* %group |
'!'* %#gid |
'!'* +netgroup |
'!'* %:nonunix_group |
'!'* %:#nonunix_gid |
'!'* User_Alias User_List を構成するのは、一個以上の次のものである。 ユーザ名、ユーザID (接頭辞 '#' が付く)、 システムグループ名やその ID (それぞれ、接頭辞 '%' と '%#' が付く)、 ネットグループ名 (接頭辞 '+' が付く)、 non-Unix グループ名やその ID (それぞれ、接頭辞 '%:' と '%:#' が付く)、 それに User_Alias。 リストの各項目の前には一個以上の '!' 演算子を付けてもよい。 奇数個の '!' はその項目の値を否定する。偶数個の場合は互い相殺されるだけだ。 なお、ユーザのネットグループについては、 ネットグループの成員中のユーザとドメインの要素のみを使って、マッチングが行われる。 ホストの要素はマッチングに使用されない。
ユーザ名、uid、グループ名、gid、ネットグループ名、non-Unix グループ名、 non-Unix グループ の gid は、ダブルクォートで囲めば、特殊文字をエスープしないですむ。 ダブルクォートで囲まずに特殊文字を使いたいなら、エスケープした 16 進数を指定してやればよい。 たとえば、スペースなら \x20 という具合だ。 ダブルクォートを使用する場合は、接頭辞があれば、それをダブルクォートの内側に入れなければならない。
non-Unix グループやその gid を指定するときのの書式が、 実際にどんなものになるかは、 利用するグループ・プロバイダー・プラグイン (group provider plugin) 次第である。 たとえば、QAS (Quest Authentication Services) の AD プラグインは、 以下の書式をサポートしている。
- •
- 同じドメインのグループ: "%:Group Name"
- •
- 任意のドメインのグループ: "%:Group Name@FULLY.QUALIFIED.DOMAIN"
- •
- グループ SID: "%:S-1-2-34-5678901234-5678901234-5678901234-567"
詳細については、 「グループ・プロバイダー・プラグイン」セクションをご覧いただきたい。
グループ名を囲む引用符は任意であることに注意していただきたい。 文字列を引用符で囲まない場合は、バックスラッシュ ('\') を使って、 スペースや特殊文字をエスケープしなければならない。 エスケープする必要がある文字のリストについては、 「ほかの特殊文字と予約語」のセクションを参照していただきたい。
Runas_List ::= Runas_Member |
Runas_Member ',' Runas_List
Runas_Member ::= '!'* user name |
'!'* #uid |
'!'* %group |
'!'* %#gid |
'!'* %:nonunix_group |
'!'* %:#nonunix_gid |
'!'* +netgroup |
'!'* Runas_Alias Runas_List は User_List に似ている。違うのは、User_Alias ではなく、 Runas_Alias が使えることだ。ユーザ名やグループ名のマッチは、 文字列として行われることに気を付けていただきたい。 言い換えると、二つのユーザ名 (あるいは、グループ名) は、 仮に同じ uid (gid) を持っていても、別個のものと見なされるのである。 だから、もし同じ uid を持ったすべてのユーザ名にマッチさせたかったら (たとえば、root と toor がそうだとしよう)、ユーザ名の代わりに uid を使えばよい (この例なら、#0 である)。
Host_List ::= Host |
Host ',' Host_List
Host ::= '!'* host name |
'!'* ip_addr |
'!'* network(/netmask)? |
'!'* +netgroup |
'!'* Host_Alias Host_List を構成するのは、一個以上の次のものである。 ホスト名、IP アドレス、ネットワークアドレス、 ネットグループ名 (接頭辞 '+' が付く)、および他のエイリアス。 ここでもまた、項目の値は、'!' 演算子によって否定することができる。なお、 ホストのネットグループについては、ネットグループの成員中のホスト (完全修飾名とそうでないもののどちらでも) とドメインの要素のみを使って、 マッチングが行われる。ユーザの要素はマッチングに使用されない。 ネットワークアドレスをネットマスクなしで指定した場合は、 sudo はローカルホストのネットワークインターフェースを一つ一つ参照し、 指定されたネットワークアドレスと同じアドレスを持つインターフェースがあれば、 そのネットマスクを使用することになる。ネットマスクの指定は、 標準の IP アドレス表記 (たとえば 255.255.255.0 や ffff:ffff:ffff:ffff::) でもよく、 CIDR 表記 (ビット数、たとえば 24 や 64) でもよい。 ホスト名の一部にシェル風のワイルドカードを使用することができるが (下記の「ワイルドカード」セクションを参照)、 ご使用のマシンの hostname コマンドが完全修飾ドメイン名 (FQDN) を返さない場合、 ワイルドカードを利用するには fqdn オプションを使う必要がある。 なお、sudo がチェックするのは、 実在のネットワークインターフェースだけだということに留意してほしい。 すなわち、IP アドレス 127.0.0.1 (localhost) がマッチすることは、絶対にないのである。 また、"localhost" というホスト名がマッチするのは、 それが実際のホスト名であるときだけであり、 それは通常、ネットワークにつながっていないシステムの場合にしか当てはまらない。
digest ::= [A-Fa-f0-9]+ |
[[A-Za-z0-9+/=]+
Digest_Spec ::= "sha224" ':' digest |
"sha256" ':' digest |
"sha384" ':' digest |
"sha512" ':' digest
Cmnd_List ::= Cmnd |
Cmnd ',' Cmnd_List
command name ::= file name |
file name args |
file name '""'
Cmnd ::= Digest_Spec? '!'* command name |
'!'* directory |
'!'* "sudoedit" |
'!'* Cmnd_Alias Cmnd_List は一個以上の、コマンド名、ディレクトリ、 他のエイリアスからなるリストである。コマンド名は絶対パスのファイル名であり、 シェル風のワイルドカードを含んでいても構わない(下記の「ワイルドカード」セクションを参照)。 単にファイル名だけ指定した場合、 ユーザはお望みのどんな引き数でも付けてそのコマンドを実行することができる。 とは言え、コマンドライン引き数を (ワイルドカードを含めて) 指定しても構わないし、また、引き数に "" を指定して、そのコマンドは、 コマンドライン引き数を付けずに実行することしかできないと指示することもできる。 ディレクトリは '/' で終わる絶対パス名である。 Cmnd_List にディレクトリを指定すると、 ユーザーはそのディレクトリ内の任意のファイルを実行できるようになる (だが、そのサブディレクトリにあるファイルは実行できない)。
Cmnd がコマンドライン引き数を伴っている場合は、 Cmnd 中の引き数は、 ユーザがコマンドラインで打ち込む引き数と正確に一致しなければならない (Cmnd 中の引き数にワイルドカードがあるならば、 それがコマンドラインの引き数とマッチしなければならない)。 以下に挙げる文字をコマンド引き数の中で用いるときは、 '\' によってエスケープしなければならないことに注意していただきたい。 ',', ':', '=', '\' がそれである。 Cmnd に "sudoedit" という sudo の組み込みコマンドを指定すると、 ユーザに sudo を -e オプション付きで (あるいは、sudoedit というコマンド名で) 実行することを許可することになる。 この場合、コマンドライン引き数も指定することができるのは、 普通のコマンドとまったく同様だ。 "sudoedit" は、sudo そのものに組み込まれたコマンドなので、sudoers ファイルではパスを前に付けずに指定しなければならないことに注意していただきたい。
コマンド名の前に Digest_Spec が付いている場合、コマンドのマッチに成功するのは、 指定された SHA-2 ダイジェストを使って照合できたときだけである。 ダイジェストのフォーマットとしては、 sha224, sha256, sha384, sha512 をサポートしている。 文字列は、16 進数形式でも base64 形式でも指定できる (base64 の方が短くて済む)。 SHA-2 ダイジェストを 16 進数形式で生成できるユーティリティはいくつかある。 openssl, shasum, sha224sum, sha256sum, sha384sum, sha512sum といったものがそうだ。
たとえば、openssl を使うなら、
$ openssl dgst -sha224 /bin/ls
SHA224(/bin/ls)= 118187da8364d490b4a7debbf483004e8f3e053ec954309de2c41a25
openssl を使って、base64 の出力を生成することもできる。
$ openssl dgst -binary -sha224 /bin/ls | openssl base64
EYGH2oNk1JC0p9679IMATo8+BT7JVDCd4sQaJQ==
注意: もしユーザがコマンドそのものに対して (直接であれ、sudo コマンドを通してであれ) 書き込み権限を持っているならば、そのユーザは、ダイジェストチェックが済み、 コマンドが実行されるまでの間に、コマンドを別のものに置き換えることができるかもしれない。 同様の競合状態が、fexecve(2) システムコールを持っていないシステムでは、 コマンドが存在するディレクトリがユーザによって書き込み可能であるときに起こりえる。
コマンド・ダイジェストをサポートしているのは、バージョン 1.8.7 以上だけである。
かなりの設定オプションが、 一行以上の Default_Entry 行を指定することで実行時にデフォルトの値から変更可能だ。 その効果の及ぶ範囲は、あらゆるホストのすべてのユーザにすることもできるし、 ある特定のホストのすべてのユーザ、ある特定のユーザ、ある特定のコマンド、 ある特定のユーザとして実行するコマンドなどに限定することもできる。 気を付けてほしいのは、コマンドに限定した Defaults 行にコマンドライン引き数まで書くことができないことだ。 引き数を指定する必要がある場合は、Cmnd_Alias を定義して、代わりにそれを参照すればよい。
Default_Type ::= 'Defaults' |
'Defaults' '@' Host_List |
'Defaults' ':' User_List |
'Defaults' '!' Cmnd_List |
'Defaults' '>' Runas_List
Default_Entry ::= Default_Type Parameter_List
Parameter_List ::= Parameter |
Parameter ',' Parameter_List
Parameter ::= Parameter '=' Value |
Parameter '+=' Value |
Parameter '-=' Value |
'!'* Parameter パラメータはフラグ、整数値、文字列、リストのどれであってもよい。 フラグは要するにブーリアン (真偽値) であり、'!' 演算子で off にできる。 整数値、文字列、リストのパラメータにも、真偽値として使用して、 それを無効にできるものがいくつか存在する。 パラメータの値が複数の単語を含むときは、 値をダブルクオート ("") で囲むとよい。 特殊文字はバックスラッシュ ('\') でエスケープすることができる。
リストには代入演算子が = のほかにもう二つある。+= と -= である。 こうした演算子は、それぞれ、リストに付け加えたり、リストから削除したりするのに使用する。 -= 演算子を使って、リストに存在しない要素を消去しようとしても、エラーにはならない。
Defaults 行の解析は、次の順序で行われる。まず、汎用、Host、User の Defaults が解析され、それから Runas の Defaults、最後にコマンドの Defaults の順番になる。
Defaults 行で使用できるパラメータのリストについては、 「SUDOERS のオプション」を御覧いただきたい。
User_Spec ::= User_List Host_List '=' Cmnd_Spec_List \
(':' Host_List '=' Cmnd_Spec_List)*
Cmnd_Spec_List ::= Cmnd_Spec |
Cmnd_Spec ',' Cmnd_Spec_List
Cmnd_Spec ::= Runas_Spec? SELinux_Spec? Tag_Spec* Cmnd
Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')'
SELinux_Spec ::= ('ROLE=role' | 'TYPE=type')
Tag_Spec ::= ('EXEC:' | 'NOEXEC:' | 'FOLLOW:' | 'NOFOLLOW' |
'LOG_INPUT:' | 'NOLOG_INPUT:' | 'LOG_OUTPUT:' |
'NOLOG_OUTPUT:' | 'MAIL:' | 'NOMAIL:' | 'PASSWD:' |
'NOPASSWD:' | 'SETENV:' | 'NOSETENV:') ユーザ設定は、あるユーザが、指定されたホストで (どのユーザに変身して) どのコマンドを実行できるかを決定する。デフォルトでは、コマンドは root に変身して実行されるが、これはコマンドごとに変更することができる。
ユーザ設定の基本構造は、"who where = (as_whom) what" である ("誰が どのホストで = (誰に変身して) 何を")。構成部分に分けて説明しよう。
Runas_Spec は変身の対象となるユーザやグループを規定している。 完全な形の Runas_Spec は、(上で定義しているように) コロン (':') で区切られ、 カッコで囲まれた、二つの Runas_List からなっている。一つ目の Runas_List は、 sudo で -u オプションを使ったときに変身できるユーザを指している。 二番目の方が規定しているのは、sudo の -g オプションによって指定できるグループのリストだ。 両方の Runas_List が指定されている場合は、それぞれの Runas_List にリストされているユーザとグループの任意の組み合わせで、 コマンドを実行することができる。一つ目の Runas_List だけが指定されている場合は、 リスト中のいかなるユーザにでも変身してコマンドを実行できるが、 -g オプションを指定することはできない。一つ目の Runas_List が空で、 二番目だけ指定されている場合は、sudo を実行するユーザの資格で、 グループを Runas_List にリストされている任意のグループに設定して、 コマンドを実行することができる。Runas_Lists が両方とも空の場合は、 sudo を実行するユーザの資格でしかコマンドを実行できない。 Runas_Spec がまったく指定されていない場合は、root としてコマンドを実行できるが、 グループを指定することはできない。
Runas_Spec は、それに続くコマンドに対してデフォルトを定める。 それはどういうことかと言うと、次のようなエントリがあったとしよう。
dgb boulder = (operator) /bin/ls, /bin/kill, /usr/bin/lprm
ユーザ dgb は /bin/ls, /bin/kill, /usr/bin/lprm を実行することができる。 ただし、operator として実行できるだけだ。たとえば、次のようにである。
$ sudo -u operator /bin/ls
エントリの後ろの方の Runas_Spec を変更することも可能だ。 上のエントリをこんなふうに書き変えたとしよう。
dgb boulder = (operator) /bin/ls, (root) /bin/kill, /usr/bin/lprm
すると、ユーザ dgb は、/bin/ls こそ operator としてだが、 /bin/kill や /usr/bin/lprm は root の資格で実行できるようになる。
dgb が /bin/ls を実行するとき、変身対象ユーザとグループのどちらでも operator になれるように、この記述を拡張することもできる。
dgb boulder = (operator : operator) /bin/ls, (root) /bin/kill,\
/usr/bin/lprm
注意してほしいが、Runas_Spec のグループの部分は、 コマンドをそのグループとして実行することをユーザに許可しているのであって、 そうすることをユーザに強制しているのではない。 コマンドラインでグループを指定しない場合は、コマンドは、 パスワード・データベースにある変身対象ユーザのエントリに登録されているグループとして実行されることになるのだ。 以下のコマンドはすべて、上記の sudoers エントリによって許可されることになるだろう。
$ sudo -u operator /bin/ls
$ sudo -u operator -g operator /bin/ls
$ sudo -g operator /bin/ls
次の例では、ユーザ tcm がモデムのデバイスファイルにアクセスするコマンドを dialer グループとして実行できるようにしている。
tcm boulder = (:dialer) /usr/bin/tip, /usr/bin/cu,\
/usr/local/bin/minicom
この例では、グループしか指定できないことに注意してほしい。コマンドは、 ユーザ tcm の資格のまま実行されるのである。たとえば、次のように。
$ sudo -g dialer /usr/bin/cu
Runas_Spec には複数のユーザやグループが存在してもよい。 その場合、ユーザは -u や -g オプションを使って、 ユーザとグループのどんな組み合わせでも選択することができる。
alan ALL = (root, bin : operator, system) ALL
この例では、ユーザ alan は root と bin のどちらのユーザにでも変身して、 任意のコマンドを実行することができる。また、グループを operator や system に設定することも自由である。
SELinux をサポートするシステムでは、sudoers ファイルのエントリで SELinux の role や type をコマンドに関連付けることも可能である。 role や type を特定のコマンドについて指定すると、 sudoers 中でデフォルトとして設定されている role や type があっても、 それよりも優先される。もっとも、role や type をコマンドラインで指定すれば、 そちらが sudoers 中の値よりさらに優先されることになる。
コマンドは 0 個以上のタグを伴うことができる。使用できるタグの値は 14 個あり、 EXEC, NOEXEC, FOLLOW, NOFOLLOW, LOG_INPUT, NOLOG_INPUT, LOG_OUTPUT, NOLOG_OUTPUT, MAIL, NOMAIL, PASSWD, NOPASSWD, SETENV, NOSETENV が、それである。 ある Cmnd にタグをセットすると、 Cmnd_Spec_List 中のそれ以後の Cmnd は、 反対の意味を持つタグによって変更されないかぎり、そのタグを継承することになる (すなわち、PASSWD は NOPASSWD を無効にし、NOEXEC は EXEC を無効にする)。
- EXEC と NOEXEC
-
sudo が noexec サポートつきでコンパイルされ、 使用しているオペレーティングシステムがそれに対応している場合、NOEXEC タグを利用すれば、 動的にリンクされた実行ファイルが、そこからさらにコマンドを実行するのを防ぐことができる。
次の例では、ユーザ aaron は /usr/bin/more と /usr/bin/vi を実行できるが、シェル・エスケープは利用できない。
aaron shanty = NOEXEC: /usr/bin/more, /usr/bin/vi
NOEXEC がどんなふうに働くのか、お使いのシステムで利用できるかどうか、 などについてさらに詳しく知りたかったら、 後述の「シェル・エスケープの防止」セクションを御覧になるとよい。
- FOLLOW と NOFOLLOW
- バージョン 1.8.15 以来、sudoedit は、 sudoedit_follow オプションが有効になっていないかぎり、 ファイルがシンボリックリンクならば、それを開かないようになっている。 FOLLOW と NOFOLLOW タグは、sudoedit_follow の値を上書きするので、 これを使用すれば、シンボリックリンクの編集をコマンドごとに許可したり、 禁止したりすることができる。 この二つのタグが効果があるのは、sudoedit コマンドに対してのみであり、 他のどんなコマンドに対しても無視される。
- LOG_INPUT と NOLOG_INPUT
-
この二つのタグは log_input オプションの値をコマンドごとに変更する。 詳しい情報については、後述する「SUDOERS のオプション」セクションの log_input の説明をご覧になっていただきたい。
- LOG_OUTPUT と NOLOG_OUTPUT
-
この二つのタグは log_output オプションの値をコマンドごとに変更する。 詳しい情報については、後述する「SUDOERS のオプション」セクションの log_output の説明をご覧になっていただきたい。
- MAIL と NOMAIL
-
この二つのタグを使えば、 mail_all_cmnds オプションの値をコマンドごとに上書きすることによって、 ユーザがコマンドを実行したときにメールを送付するかどうかについて、 きめ細かな制御を行うことができる。 sudo が -l や -v オプションを付けて実行されたときには、効果がない。 NOMAIL は、mail_always や mail_no_perms オプションも上書きする。 詳細については、後述の「SUDOERS のオプション」セクションにある mail_all_cmnds, mail_always, mail_no_perms の説明をご覧いただきたい。
- PASSWD と NOPASSWD
-
デフォルトでは、sudo はコマンドを実行する前に、ユーザが本人であることを証明するように求める。 この振舞いは NOPASSWD タグによって変更することができる。Runas_Spec と同様、 NOPASSWD タグも Cmnd_Spec_List 中のそれに続くコマンドに対してデフォルトを定める。 PASSWD の働きは反対であり、振舞いを元に戻したいときに使える。たとえば、
ray rushmore = NOPASSWD: /bin/kill, /bin/ls, /usr/bin/lprm
とすれば、ユーザ ray はマシン rushmore 上で認証をしないでも root として /bin/kill, /bin/ls, /usr/bin/lprm を実行できるようになる。もし ray がパスワードなしで実行できるコマンドを /bin/kill だけに絞りたいのなら、エントリはこうなるだろう。
ray rushmore = NOPASSWD: /bin/kill, PASSWD: /bin/ls, /usr/bin/lprm
ただし、ユーザが exempt_group オプションで指定されているグループに属する場合は、 PASSWD タグが効果を持たないことに注意してほしい。
デフォルトでは、現在使用中のホストに関するユーザのエントリのうちに NOPASSWD タグが指定されているものが一つでもあれば、 そのユーザはパスワードなしで "sudo -l" を実行できる。 なお、ユーザがパスワードなしで "sudo -v" を実行できるのは、 現在使用中のホストに関するそのユーザのエントリのすべてで NOPASSWD タグが生きているときのみである。この動作は、verifypw や listpw オプションによって変更できる。
- SETENV と NOSETENV
-
上記のタグは setenv オプションの値をコマンドごとに変更する。 あるコマンドに対して SETENV を設定すると、 ユーザがコマンドラインから -E オプションを使用して、 env_reset オプションを無効にできるようになることに注意してほしい。 それだけではない。コマンドラインから設定する環境変数が env_check, env_delete, env_keep による規制を受けないようにもなる。 それ故、こうした形で環境変数を設定することを許可するのは、 信用できるユーザだけに限るべきである。なお、マッチするコマンドが ALL だった場合は、暗黙のうちに SETENVタグがそのコマンドに付けられるが、 このデフォルトの動作は UNSETENV タグを使えば打ち消すことができる。
sudoers ファイルでは、ホスト名、コマンドのパス名、 コマンドライン引き数にシェル形式のワイルドカード (メタ文字とか glob キャラクタとも言う) が使用できる。ワイルドカードのマッチングは、 IEEE Std 1003.1 ("POSIX.1") で規定されている glob(3) や fnmatch(3) 関数を用いて行われる。
- *
- 0 個以上の任意の文字 (ホワイトスペースも含む) にマッチする。
- ?
- 任意の 1 文字 (ホワイトスペースも含む) にマッチする。
- [...]
- 指定された範囲の任意の 1 文字にマッチする。
- [!...]
- 指定された範囲以外の任意の 1 文字にマッチする。
- \x
- 'x' がどんな文字であっても、'x' そのものとして評価する。 この表記法は '*', '?', '[', ']' といった特殊文字をエスケープするために使用される。
上記のものは正規表現ではないことに注意していただきたい。 正規表現とは違って、範囲内の文字一つ以上にマッチさせる方法は存在しない。
使用しているシステムの glob(3) や fnmatch(3) 関数が文字クラスに対応しているなら、 文字クラスが使用できる。ただし、':' 文字は、 sudoers で特別な意味を持っているので、エスケープしなければならない。 一例を上げる。
sudoers 中で上のように書けば、アルファベットの文字で始まるどんなファイル名にもマッチするだろう。
コマンドのファイル名の部分で使われたワイルドカードはフォワードスラッシュ ('/') にマッチしないことに注意していただきたい。そこで、次のようなパスは、
/usr/bin/who にマッチするが、/usr/bin/X11/xterm にはマッチしない。
だが、コマンドライン引き数の部分のマッチングでは、 ワイルドカードはスラッシュにしっかりマッチする。 コマンドライン引き数には、任意の文字列を含むことが認められており、 パス名しか許されていないわけではないからだ。
sudoers 中でコマンドライン引き数にワイルドカードを使用するときは、 注意しなければならない。
コマンドライン引き数は、結合して一つの文字列にした上で、マッチングを行う。 そのため、'?' や '*' といったワイルドカード文字が、 ユーザが指定したコマンドライン引き数と、 単語の境界をまたいでマッチしてしまうことになるのだ。 これは想定外のことかもしれない。たとえば、sudoers に次のような行があると、
%operator ALL = /bin/cat /var/log/messages*
以下のコマンドが実行できることになるが、
$ sudo cat /var/log/messages.1
また、以下のコマンドの実行も可能になってしまう。
$ sudo cat /var/log/messages /etc/shadow
後者は、おそらく意図に反しているだろう。たいていの場合、 コマンドラインの処理は、sudoers ファイルの中ではなく、 スクリプト言語中で行った方が間違いがない。
上記ルールには以下の例外がある。
- ""
- sudoers ファイルのエントリにおいて、空文字列 "" が唯一のコマンドライン引き数だった場合は、 そのコマンドは引き数を付けずに実行しなければならないことを意味している。
- sudoedit
- 組み込みコマンド sudoedit にコマンドラインで渡す引き数は、 常にパス名であるはずだ。そこで、ワイルドカードはフォワードスラッシュ ('/') にマッチしないようになっている (訳注: sudoers 中で sudoedit に対して引き数を指定する場合、 それは一般のコマンドライン引き数と同じ扱いではなく、パス名扱いになるということ)。
#include 命令や #includedir 命令を使えば、現在解析中の sudoers ファイルに、 外部にあるほかの sudoers ファイルをインクルードすることができる。
この方法を使えば、たとえば、サイト全体で使用する sudoers ファイルのほかに、 マシンごとのローカルな sudoers ファイルを持つことができる。 ここでは、サイト全体の sudoers ファイルを /etc/sudoers とし、 マシンごとの方は /etc/sudoers.local とすることにしよう。 /etc/sudoers に /etc/sudoers.local をインクルードするには、 /etc/sudoers 中に次の行を書き込めばよい。
#include /etc/sudoers.local
sudo は解析中この行に出会うと、カレントファイル (/etc/sudoers だ) の処理を一時中止して、処理の対象を /etc/sudoers.local に切り替える。 そして、/etc/sudoers.local の末尾まで達したら、/etc/sudoers の残りを処理するのである。 インクルードされるファイルが、さらに他のファイルをインクルードしていてもよい。 インクルートのネストには、128 ファイルまでというハード・リミットがあって、 インクルードファイルのループが起きないようになっている。
インクルードファイルのパスが絶対パスでない場合は (すなわち、パスが '/' で始まっていない場合は)、インクルードする側の sudoers ファイルと同じディレクトリに、インクルードされるファイルも存在しなければならない。 たとえば、/etc/sudoers に次のような行があったら、
インクルードされるファイルは、/etc/sudoers.local である。
なお、ファイル名には %h エスケープが使える。これはホスト名の短縮形を示している。 たとえば、マシンのホスト名が "xerxes" のとき、
と書けば、sudo はファイル /etc/sudoers.xerxes をインクルードすることになる。
#includedir 命令を使えば、sudoers.d ディレクトリを作っておいて、 システムのパッケージ管理者がパッケージをインストールする際に sudoers のルールを記したファイルをそこに入れてやる、といったことが可能になる。 たとえば、次のように書くと、
#includedir /etc/sudoers.d
sudo は /etc/sudoers.d にあるファイルを一つづつ読み込む。 ただし、末尾が '~' だったり、'.' 文字を含んでいたりするファイル名はスキップするが、 これは、パッケージマネージャやエディタが作った、 テンポラリファイルやバックアップファイルを読み込むような問題を起こさないためである。 ファイルは辞書順にソートされて、解析される。すなわち、/etc/sudoers.d/01_first は /etc/sudoers.d/10_second より前に解析されるということだ。 ソートは辞書順であって、数値の順ではないので、 /etc/sudoers.d/1_whoops というファイルがあっても、 /etc/sudoers.d/10_second より後でロードされることに注意していただきたい。 ファイル名の先頭を 0 で埋めて数字の桁を揃えれば、 こうした問題を回避することができる。
気をつけていただきたいが、#include でインクルードされたファイルとは違って (訳注: visudo は /etc/sudoers を編集するとき、#include で指定したファイルがあれば、続けてそれも編集する)、 visudo が #includedir で指定したディレクトリのファイルまで編集するのは、 シンタクスエラーを含むものがあるときだけである。 とは言え、visudo を -f オプション付きで実行して、 ディレクトリ中のファイルを直接編集することは可能だが、その場合は、 他のファイルで定義されているエイリアスが再定義されていても、 それを見つけて指摘してくれることはない。
パウンド記号 ('#') はコメントを示すのに使用される (例外は、#include 命令の一部であるときや、ユーザ名に関連して現れ、 その後に一個以上の数字が続くときであり、後者の場合は uid と見なされる)。 コメント記号とそれに続くテキストは、行末にいたるまで無視される。
予約語 ALL は組込みのエイリアスであり、何に対してでもマッチする。 ALL は、Cmnd_Alias, User_Alias, Runas_Alias, Host_Alias を代わりに使えるところなら、どこでも使用できる。 ALL という名前のエイリアスを自分で定義しようとしてはいけない。 組込みのエイリアスの方が、自分で作ったエイリアスより優先して使われるからだ。 ALL の使用には危険が伴うことがあるのを忘れないでいただきたい。 なぜなら、ALL をコマンドに関して使うと、 ユーザにシステム上のどんなコマンドでも実行することを許してしまうからである。
エクスクラメーションマーク ('!') は、リストやエイリアス中はもちろん、 Cmnd の前でも論理 not 演算子として使用することができる。 これによってある値を除外することが可能になるわけだ。 ただし、'!' 演算子が効果を持つためには、 そこから除外する対象が存在しなければならない。 たとえば、root 以外のすべてのユーザにマッチさせたい場合は、 次の表現を使用する。
次のように ALL の記述を省くと、
確かに root を明示的に否定することにはなるが、 他のどんなユーザともマッチすることがない。 この点が、正真の「否定」演算子とは違っている。
とは言え、組込みエイリアス ALL と '!' を組み合わせて、 「二三のコマンド以外のすべての」コマンドの実行をあるユーザに許可しようとしても、 意図どおりの動きになることはめったにないことに気をつけていただきたい (下記の「セキュリティに関する注意点」を参照)。
長い行は、行末にバックスラッシュ '\' を置けば、継続することができる。
リストにおける要素間やユーザ設定における構文用特殊文字 ('=', ':', '(', ')') の前後に空白 (white space) を入れることは、任意である。
次の文字を単語 (ユーザ名とかホスト名とか) の一部として使うときは、 バックスラッシュ ('\') でエスケープしなければならない。 '!', '=', ':', ',', '(', ')', '\' がそれである。
以下は sudoers ファイルの記載例である。 正直なところ、いささか凝りすぎの部分もある。 まず最初に継承を許可する環境変数をいくつか指定し、 続いて aliases の定義をする。
# sudo 経由で X アプリケーションを実行するとき、HOME は
# .Xauthority ファイルを探すために使用される。ほかのプログラムも
# 設定ファイルを探すのに HOME を使用するので、この指定が
# 権限の昇格を引き起こしかねないことに注意してほしい。
Defaults env_keep += "DISPLAY HOME"
# User alias の指定
User_Alias FULLTIMERS = millert, mikef, dowdy
User_Alias PARTTIMERS = bostley, jwfox, crawl
User_Alias WEBMASTERS = will, wendy, wim
# Runas alias の指定
Runas_Alias OP = root, operator
Runas_Alias DB = oracle, sybase
Runas_Alias ADMINGRP = adm, oper
# Host alias の指定
Host_Alias SPARC = bigtime, eclipse, moet, anchor :\
SGI = grolsch, dandelion, black :\
ALPHA = widget, thalamus, foobar :\
HPPA = boa, nag, python
Host_Alias CUNETS = 128.138.0.0/255.255.0.0
Host_Alias CSNETS = 128.138.243.0, 128.138.204.0/24, 128.138.242.0
Host_Alias SERVERS = master, mail, www, ns
Host_Alias CDROM = orion, perseus, hercules
# Cmnd alias の指定
Cmnd_Alias DUMPS = /usr/bin/mt, /usr/sbin/dump, /usr/sbin/rdump,\
/usr/sbin/restore, /usr/sbin/rrestore,\
sha224:0GomF8mNN3wlDt1HD9XldjJ3SNgpFdbjO1+NsQ== \
/home/operator/bin/start_backups
Cmnd_Alias KILL = /usr/bin/kill
Cmnd_Alias PRINTING = /usr/sbin/lpc, /usr/bin/lprm
Cmnd_Alias SHUTDOWN = /usr/sbin/shutdown
Cmnd_Alias HALT = /usr/sbin/halt
Cmnd_Alias REBOOT = /usr/sbin/reboot
Cmnd_Alias SHELLS = /usr/bin/sh, /usr/bin/csh, /usr/bin/ksh,\
/usr/local/bin/tcsh, /usr/bin/rsh,\
/usr/local/bin/zsh
Cmnd_Alias SU = /usr/bin/su
Cmnd_Alias PAGERS = /usr/bin/more, /usr/bin/pg, /usr/bin/less
以下では、コンパイル時に埋め込まれたデフォルト値のいくつかを変更している。 sudo には syslog(3) 経由でログを記録し、 ファシリティにはすべての場合に auth を使用させたい。 フルタイムのスタッフには sudo の訓戒を出さないようにしたい。 ユーザ millert はパスワードを入力しないでよい。 コマンドを root として実行するときは、 環境変数 LOGNAME, USER, USERNAME を変更したくない。 さらに、SERVERS という Host_Alias に属するマシンでは、 ローカルなログファイルを副本として作り、 ログの記入事項は数年に渡って保存されるので、 ログの各行に間違いなく年度が入るようにする。 最後に PAGERS という Cmnd_Alias に属するコマンド (/usr/bin/more, /usr/bin/pg, /usr/bin/less) については、 シェル・エスケープを無効にする。なお、最後の設定は、 許可するコマンドが ALL になっているユーザに対しては、 効果的な抑制にはならないことに注意していただきたい。
# built-in defaults の変更
Defaults syslog=auth
Defaults>root !set_logname
Defaults:FULLTIMERS !lecture
Defaults:millert !authenticate
Defaults@SERVERS log_year, logfile=/var/log/sudo.log
Defaults!PAGERS noexec
ユーザ設定が、誰が何を実行できるかを実際に決めている部分だ。
root ALL = (ALL) ALL
%wheel ALL = (ALL) ALL
root と wheel グループに属するすべてのユーザには、 どのホストでも任意のユーザとしていかなるコマンドでも実行することを認める。
FULLTIMERS ALL = NOPASSWD: ALL
フルタイムのシステム管理者 (millert, mikef, dowdy) は、 どのホストでも任意のコマンドを認証なしで実行できる。
パートタイムのシステム管理者 (bostley, jwfox, crawl) は、 どのホストでも任意のコマンドを実行できるが、その際に認証をしなければならない (このエントリには NOPASSWD タグが指定されていないので)。
ユーザ jack は、CSNETS というエイリアスに属するマシンで、 任意のコマンドを実行できる (すなわち、ネットワークが 128.138.243.0, 128.138.204.0, 128.138.242.0 のマシンだ)。この内、128.138.204.0 にのみ class C のネットワークであることを示す明示的な (CIDR 表記の) netmask がある。CSNETS のほかのネットワークについては、 ローカルマシンの netmask がマッチングの際に使われることになる。
ユーザ lisa は、エイリアスが CUNETS のいかなるホストでも、 任意のコマンドを実行することができる (すなわち、128.138.0.0 という class B ネットワークのマシンだ)。
operator ALL = DUMPS, KILL, SHUTDOWN, HALT, REBOOT, PRINTING,\
sudoedit /etc/printcap, /usr/oper/bin/
ユーザ operator は、 簡単な保守管理に用途が限定されたコマンドを実行できる。この場合それは、 バックアップしたり、プロセスを kill したり、印刷システムを操作したり、 システムをシャットダウンしたりするのに関係するコマンドと、 /usr/oper/bin/ ディレクトリにある任意のコマンドである。 注意: DUMPS という Cmnd_Alias に属するコマンドのひとつ、すなわち /home/operator/bin/start_backups は、sha224 ダイジェスト付きである。 そうしているのは、そのスクリプトが置かれているディレクトリが operator ユーザによって書き込み可能だからだ。スクリプトが変更された場合 (その結果、ダイジェストが一致しないことになる)、 sudo 経由でそのスクリプトを実行することはもはや不可能になる。
joe ALL = /usr/bin/su operator
ユーザ joe は、su(1) を使って operator になることしかできない。
pete HPPA = /usr/bin/passwd [A-Za-z]*, !/usr/bin/passwd root
%opers ALL = (: ADMINGRP) /usr/sbin/
opers グループのユーザは、/usr/sbin/ にあるコマンドを、 自分自身の資格で、Runas_Alias ADMINGRP に属する任意のグループ (すなわち、adm か oper グループ) として実行できる。
ユーザ pete は、HPPA に属するマシンで root 以外なら誰のパスワードでも変更することを許されている。 コマンドライン引き数は、結合して一つの文字列にした上で、マッチングを行うので、 ワイルドカード '*' は、複数の単語とマッチすることになる。だから、 この例は、passwd(1) がコマンドラインで複数のユーザ名を受け付けないことを前提としているのである。 GNU のシステムでは、 passwd(1) に対するオプションをユーザ引き数の後ろに置くことができるのに注意していただきたい。 そのため、このルールは次のようなコマンドも許可してしまうことになる。
これは、望ましくないことかもしれない。
bob SPARC = (OP) ALL : SGI = (OP) ALL
ユーザ bob は、SPARC や SGI に属するマシンで Runas_Alias OPに登録されている任意のユーザとして (root と operator である) どんなコマンドでも実行できる。
ユーザ jim は、biglab ネットグループに属するマシンで、 どんなコマンドでも実行できる。 sudo は、"biglab" に '+' の接頭辞が付いているので、 それをネットグループだと認識する。
+secretaries ALL = PRINTING, /usr/bin/adduser, /usr/bin/rmuser
secretaries ネットグループのユーザは、ユーザの追加や削除はもちろん、 プリンタの管理にも協力する必要がある。 そこで、すべてのマシンでその種のコマンドの実行を認められている。
fred ALL = (DB) NOPASSWD: ALL
ユーザ fred は、Runas_Alias DB の任意のユーザとして (oracle か sybase だ) パスワードを入力しないでもコマンドを実行することができる。
john ALPHA = /usr/bin/su [!-]*, !/usr/bin/su *root*
ユーザ john は、ALPHA に属するマシンで su(1) を使って root 以外の誰にでもなることができるが、 su にオプションを指定することは許されていない。
ユーザ jen は、Host_Alias SERVERS に属するマシン (master, mail, www, ns) を除くいかなるマシンでも、任意のコマンドを実行できる。
jill SERVERS = /usr/bin/, !SU, !SHELLS
jill は、Host_Alias SERVERS のいかなるマシンでも /usr/bin/ ディレクトリにある任意のコマンドを実行できるが、 SU や SHELLS という Cmnd_Alias に属するコマンドは実行できない。 ルールのこのくだりでは特に言っていないが、 Cmnd_Alias PAGER のコマンドはすべて /usr/bin にあり、 noexec オプションが設定されている。
steve CSNETS = (operator) /usr/local/op_commands/
ユーザ steve は、ディレクトリ /usr/local/op_commands/ にある任意のコマンドを実行できるが、 operator というユーザとして実行できるだけだ。
matt も、自分用のワークステーション valkyrie で、 ハングしたプロセスの kill ぐらいはできる必要がある。
WEBMASTERS www = (www) ALL, (root) /usr/bin/su www
ホスト www で User_Alias WEBMASTERS に属するいかなるユーザも (will, wendy, wim だ)、ユーザ www (web ページの所有者) として任意のコマンドを実行することができる。 単に su(1) でユーザ www になってもよい。
ALL CDROM = NOPASSWD: /sbin/umount /CDROM,\
/sbin/mount -o nosuid\,nodev /dev/cd0a /CDROM
いかなるユーザも、Host_Alias が CDROM のマシンで (orion, perseus, hercules)、パスワードを入力することなく CD-ROM をマウント、アンマウントできる。 上記のコマンドを打ち込むのはユーザにとっていささか面倒なので、 シェルスクリプトとしてカプセル化してしまうのがよいだろう。
- [訳注]:
- 注意していただきたいが、 /etc/sudoers ファイルに「user1 ALL = (ALL) ALL」といった設定を書いても、 user1 が sudo 経由であらゆるコマンドを実行できるようになるのは、 自ホスト、すなわち、その /etc/sudoers が存在するホストだけであって、 telnet や ssh で接続したホストで sudo 経由でコマンドを実行できるとはかぎらない。 他ホストの sudo は、自分のところにある /etc/sudoers ファイルしか参照しないからである。
それでは、何故、上記の「用例」で自ホスト以外の設定が行われているのだろうか? そもそも、sudoers の書式で自ホスト以外のホストを指定できるのは、 何故なのか? ホストに ALL を指定できるのは、何故なのか? それは、管理しているサイトのすべてのホストの設定を記した sudoers ファイルを一つ作って、それをすべてのホストにコピーして使う、 そういった使い方を想定しているからだろう。昔の習慣の名残りかもしれない。 もし、サイト中のすべてのホストの設定を一ヶ所にまとめて置き、 それをすべてのホストに共有させたいのなら (すなわち、sudoers セキュリティポリシー設定の集中管理がしたいのなら)、 LDAP の採用を考えるべきである。
一般的に言って、演算子 '!' を使用して ALL からコマンドの「引き算」をするのは、あまり効果的な方法ではない。 ユーザは実行したいコマンドを名前を変えてコピーし、 それからそれを実行するといった簡単な方法で、裏をかくことができるからだ。 たとえば、
bill ALL = ALL, !SU, !SHELLS
という行は、SU や SHELLS に列記されているコマンドの bill による実行を、 本当に阻止することにはならない。なぜなら、bill としては、 そうしたコマンドを単に名前を変えてコピーすればよいし、 エディタなどのプログラムからシェル・エスケープを利用することもできるからだ。 だから、この種の制限はやった方がまし程度に考えておくべきである (そして、しっかりした運用方針によって制限の実効力を上げるべきだ)。
一般に、もし ユーザに許可するコマンドに ALL が入っているならば、 ユーザが自分でプログラムを作って (あるいは、シェルを自分専用に別の名前でコピーして)、 ルート・シェルを獲得するのを防ぐことはできない。 ユーザ設定でどんな項目に '!' を付けようとも防止不可能である。
fast_glob オプションが使われている場合、 パス名に glob 文字 (ワイルドカードとも言う) が含まれるコマンドを確実に無効にすることは不可能である。その理由は、 C ライブラリの fnmatch(3) 関数は、相対パスのパス名展開ができないからだ。 このことは、権限を許可するルールにとっては、 たいていの場合不便なだけにすぎないが、権限を減らしたり、 取り消したりするルールにとっては、セキュリティ上の問題をもたらしかねない。
たとえば、sudoers ファイルに次のエントリがあったとしよう。
john ALL = /usr/bin/passwd [a-zA-Z0-9]*, /usr/bin/chsh [a-zA-Z0-9]*,\
/usr/bin/chfn [a-zA-Z0-9]*, !/usr/bin/* root それでも、fast_glob が有効になっていれば、john は "/usr/bin/passwd root" を実行できてしまう。 /usr/bin に移動して、"./passwd root" を実行すればよいのだ。
sudo があるプログラムを実行した場合、そのプログラムは、 ほかのプログラムの実行も含めて、何でも自由に好きなことができる。 このことがセキュリティ上の問題になりかねないのは、 プログラムがシェル・エスケープを許しているのは珍しいことではなく、 そのために、ユーザが sudo のアクセス制御やロギングをすり抜けることが可能になるからだ。 よく使うプログラムでシェル・エスケープを許しているものには、 (当然ながら) シェル、エディタ、 ページャ、メーラ、ターミナルなどがある。
この問題に対処するには、基本的に二つの方法がある。
- 制限
- ユーザに任意のコマンドの実行を許すようなコマンドに対して、 ユーザがアクセスできないようにする。 エディタの場合は、制限モード (restricted mode) と称して、 シェル・エスケープが使えないモードを持っているものも多い。 とは言え、sudo 経由でエディタを使うのなら、 sudoedit を使用する方がより優れた対策である。 シェル・エスケープを提供するプログラムはたくさんあるので、 それを提供しないプログラムのみを使用するようにユーザを制限するのは、 たいてい実現不可能である。
- noexec
- 共有ライブラリをサポートしているシステムには、 環境変数 (たいていは LD_PRELOAD) で別の共有ライブラリを指定することによって、 デフォルトのライブラリ関数を置き換えることができるものが多い。 そういったシステムでは、sudo の noexec 機能を使えば、 sudo から実行されるプログラムが、 何かほかのプログラムを実行するのを防ぐことができる。 とは言え、これが当てはまるのは、 動的にリンクされたネイティブなプログラムだけだということに留意してほしい。 静的にリンクされたプログラムや、 バイナリ・エミュレーションのもとで動くほかの OS のプログラムには効果がない。
noexec 機能は SunOS, Solaris, *BSD, Linux, IRIX, Tru64 UNIX, MacOS X, HP-UX 11.x、それに 5.3 以上の AIX で使えることがわかっている。 環境変数 LD_PRELOAD をサポートしているたいていのオペレーティングシステムが、 この機能に対応しているはずだ。 使用しているオペレーティングシステムのマニュアルページを調べて、 ダイナミック・リンカについて (通例 ld.so, ld.so.1, dyld, dld.sl, rld, loader といった名前になっている) LD_PRELOAD がサポートされているかどうか確認していただきたい。
あるコマンドに対して noexec を有効にするには、 上述の「ユーザ設定」セクションで解説したように、NOEXEC タグを使用する。 そのときの例を再掲しよう。
aaron shanty = NOEXEC: /usr/bin/more, /usr/bin/vi
この例では、ユーザ aaron 対して、noexec を有効にした上で、 /usr/bin/more と /usr/bin/vi の実行を許可している。 このようにすれば、この二つのコマンドから (シェルのような) ほかのコマンドを実行することができなくなるわけだ。使用しているシステムが noexec に対応する能力があるかどうかよくわからない場合でも、 取りあえず試してみることなら、いつだってできる。noexec を有効にして、 シェル・エスケープが可能かどうか確かめてみればよいのだ。
注意してほしいが、シェル・エスケープの禁止は万能薬ではない。 root の権限で動いているプログラムには、ほかにも、危険性のあるさまざまな作業 (ファイルの属性を変更するとか、上書きしてしまうとか) が可能であり、 思いがけずに権限を拡大してしまうこともありえるのだ。特にエディタについて言うと、 ユーザには sudoedit を実行する許可を与えるのが、より安全な方法である (下記参照)。
sudoers プラグインでは sudoedit が使用できるようになっており、 ユーザは好みのエディタを使って、安全にファイルを編集することができる。 sudoedit は sudo の組み込みコマンドなので、 sudoers ファイル中で指定するときは、 頭にパスを付けてはいけない。ただし、コマンドライン引き数については、 通常のコマンドと全く同じように指定することができる。 sudoedit のコマンドライン引き数にワイルドカードを使用した場合は、 sudoedit の引き数にはパス名が来るはずなので、 ワイルドカードはフォワードスラッシュ ('/') にマッチしないようになっている。
sudo 経由で実行される他のコマンドとは違って、エディターは sudo を起動するユーザの資格で、環境を変更せずに実行される。 詳しくは、sudo(8) のマニュアルの -e オプションの説明をご覧になるとよい。
たとえば、ユーザ operator が "message of the day" ファイルを編集できるようにするには、sudoers で次のように指定する。
operator ALL = sudoedit /etc/motd
そして、ユーザ operator は、sudoedit を次のように実行する。
エディタは、root ではなく、ユーザ operator の資格で、/etc/motd の作業用コピーに対して実行される。ファイルの編集が済むと、 /etc/motd は作業用コピーの内容で更新されることになる。
ユーザが書き込み権限を持っているディレクトリに存在するファイルに対して、 ファイル名を直接指定してであれ、ワイルドカードによって指定してであれ、 sudoedit を使って編集する許可をそのユーザに与えてはいけない。 もし、ユーザがディレクトリに対して書き込み権限を持っているならば、編集を許可されたファイルを、 別のファイルに対するリンクで置き換えることができるわけで、そうすることによって、 任意のどんなファイルでも編集できるようになってしまうからだ。 そうした事態を防ぐために、バージョン 1.8.16 以降の sudoedit では、 sudoedit_checkdir オプションが無効になっている場合や、 sudoedit を実行するユーザが root である場合を除いて、 ユーザが書き込み権限を持っているディレクトリに存在するいかなるシンボリックリンクも、 たどらないようになっている。 また、書き込み権限があるディレクトリに存在するファイルの編集を行うことも拒否する。 さらに、バージョン 1.8.15 以降の sudoedit では、 sudoers ファイルで sudoedit_follow オプションが有効になっているか、 sudoedit コマンドの前に FOLLOW タグが指定してあるかのどちらかでないかぎり、 シンボリックリンクをオープンしないようになっている。
sudoers は、タイムスタンプ・ディレクトリ (デフォルトでは /var/run/sudo/ts) の所有者を調べて、所有者が root でなかったり、 root 以外のユーザにも書き込み可能であったりすると、 そのディレクトリの中身を無視する。古いバージョンの sudo は、 タイムスタンプ・ファイルを /tmp に置いていたが、 そうしたことは今では推奨できない。 特権を持たないユーザが自分の作ったファイルの所有者を変更できるシステムでは、 ユーザがタイムスタンプを自分で作成することが可能になるかもしれないからだ。
タイムスタンプ・ディレクトリは、 リブートしたときにその中身を消去されるべきだが、 すべてのシステムに /var/run ディレクトリが存在するとはかぎらない。 問題が起きるのを避けるために、sudoers は、 ブートタイムを参照できるシステムでは、 マシンがブートした時刻よりも古い日時を持つタイムスタンプ・ファイルを無視する。
グラフィカルなデスクトップ環境を持っているシステムの中には、 特権を持たないユーザにシステム・クロックの変更を許しているものがある。 sudoers は、タイムスタンプが有効か否を確認するのに、 システム・クロックを拠り所にしている。そこで、そうしたシステムでは、 ユーザがクロックを後戻りさせることで、timestamp_timeout よりも長い時間 sudo を実行することが可能になるかもしれない。そうした事態に対抗するため、 sudoers は、システムがサポートしているならば、 タイムスタンプに単調増加時計 (monotonic clock) を使用する (単調増加時計は後戻りすることがないからだ)。
sudoers はあまりにも未来に設定されたタイムスタンプを認めない。 タイムスタンプが「現在時 + 2 * TIMEOUT」より新しい日時だった場合、 そのタイムスタンプは無視され、sudoers はログに記録して、警告を発する。
タイムスタンプ・ファイルはファイルシステム中に作られるものだから、 ユーザのログイン・セッションが終わっても残っている。 そのため、ユーザがログインし、認証を行ってから sudo を使ってコマンドを実行し、一旦ログアウトして、再度ログインしたとき、 認証なしで sudo を実行することが可能になってしまうかもしれない。 タイムスタンプ・ファイルに記録されているタイムスタンプが 5 分以内のものであれば (あるいは、sudoers ファイルで設定されているタイムアウト時間以内のものであれば)、 そういうことが可能かもしれないのだ。 tty_tickets オプションが有効な場合は、タイムスタンプの記録に、 ユーザが認証するときに使った端末のデバイス番号が含まれる。それよって、 tty ごとのきめ細かな管理が可能になるが、それでもタイムスタンプの記録が、 ユーザのセッションが終わった後まで有効である可能性もある。また、 タイムスタンプの記録には、最後に認証を行ったプロセスのセッション ID も含まれている。 別の端末セッションのプロセスが、同じタイムスタンプの記録を使えないようにしているのだ。 それはまた、ユーザがログアウトし、再度同じ端末にログインしたときに、 パスワードを入力することなしに sudo を実行できる可能性を減少させる役にも立っている。