Sudo | ||
---|---|---|
HOME |
あまりにも基本的なツールだからなのか、世の中では「いまさら聞けない」状態になっていてあまり良いドキュメントがなかったので、これをまとめることにした。ちなみに sudo の読み方だが、オフィシャルサイトの FAQ によると、「我々は 『スードゥー (soo-doo)』 と呼んでいるが、"pseudo" と同じ読み方 (つまり 『スード』) も一般的だ」とある。
sudo の設定ファイルが /etc/sudoers。RedHat 系では PAM でも一部制御されており、/etc/pam.d/sudo ファイルも動作に影響を与えるのだが、ここでは sudoers ファイルについてのみ述べる。
sudoers ファイルを編集するには、ファイルを直接エディタで開くのではなく、root 権限で、
root# visudo
とコマンドする。すると sudoers がテンポラリファイルにコピーされて、コピーが vi で開かれる (エディタプログラムの変更は後述)。そして、編集して保存すると、それが本来のファイルに書き戻される。同時編集防止のロック機能も備え、保存時には構文エラーチェックも行われるので、必ず visudo 経由で編集するよう心掛けるべきだ。
sudoers ファイルは、大別すると
の 4つから成る。以下に、それぞれの書き方や設定のポイントを述べる。
通常は sudoers ファイルの最後に書くものだが、これを知っておかないと次項からの説明が何のことやら分からないので、最初に説明する。ユーザ定義こそが、「誰が誰として何をできるか」を制御する sudoers のメインであり、後述の Alias定義などは (スクリプトに喩えれば) メイン部分で使用するための変数設定のようなものだ。
User定義文は、下記のフォーマットを採る (Tag については後述する)。
User Host = (RunasUser) [Tag:] Command
噛み砕いて日本語で書いてみると;
誰が どのホスト上で = (誰として) [Tag:] 何を実行できるか
ただし、上の表記は単純化したものだ。実際には各要素を一度に複数指定することができる (この柔軟性が逆に sudo の敷居を高くしている!)。複数指定を加味した上で、man などで使われるコマンド表記法に近いカタチで書くとこうなる;
User[,User[,..]] Host[,Host[,..]] = \ (RunasUser[,RunasUser[,..]]) [Tag:[Tag:[..]]] Command[,Command[,..]] \ [,(RunasUser..)..] \ [,Host[,Host[,..]] = ..] \ [,Host....]
苦しくなって色分けまでしてみたけれど、わっかるかなぁ。sudo の man の EBNF 表記よりは分かりやすいと思うのだが...
書き方についての幾つかポイント;
sudo 1.7.2p1 でのタグには以下の 3種類がある。(NO)PASSWD:以外の 2つは RHEL/CentOS 4 の sudo にはまだない。最新の sudo ではこの他に (NO)LOG_INPUT:, (NO)LOG_OUTPUT: というタグもあるようだ。
タグ | 説明 |
(NO)PASSWD: | NOPASSWD: を指定したコマンドの実行時には、「誰として」に成る際にパスワードを訊かれない。PASSWD: はその反対だが、デフォルトではパスワードを訊くようにしてあるのが通例なのであまり使わない。Defaults定義にはこれと同等のパラメータ authenticate があり、ユーザなりコマンドなりに対してまとめて適用するにはそちらが便利。 |
(NO)SETENV: | SETENV: を指定したコマンドの実行時には、「誰が」のユーザの時に持っていた環境変数 (sudoに「危険なもの」としてハードコードされた一部の環境変数を除く) を、「誰として」になってからも引き継ぐことが可能になる。ただし、実際に引き継がれるのは、sudoers でこの設定 (またはDefaults定義でsetenvを設定) した上で、sudo の実行時に -E オプションかVAR=Value を指定した時だけ。[sudo 1.6.8p12以降] |
(NO)EXEC: | コマンドの、共有ライブラリにダイナミックリンクして実現している機能を抑止する。例えば、vi や vim では編集中に `:sh' とコマンドすれば「誰として」の権限でのシェルに入れるし、less や more では `!command ' 、例えば `!rm /etc/passwd' などという恐ろしいこともできてしまう。そこで、そうしたプログラムを NOEXEC: で修飾しておけば、シェルエスケープ機能を殺すことができる。ただし、NOEXEC: の過信は禁物。エディタやページャを sudo で許可するのはお勧めできない。どうしてもエディタを使わせたいのであれば、sudoedit を指定すべき。sudoedit も vi クローンの一種だが、シェルエスケープ機能を使っても「誰として」のではなく元のユーザのシェルにしか入れないようにできている (後述 Cmnd_Alias の項を参照)。なお、Defaults定義にも同等のパラメータ noexec がある。[sudo 1.6.8p12以降] |
タグは、User定義文の Command 部分で使用し、タグが出現してから次のタグが出現するまでのコマンド群が効果を受ける。例えば、
ADMINS MYBOX = (IMPORTANT) STORAGE, NOPASSWD: SOFTWARE,\ SETENV:NOPASSWD: PERFORMANCE
と定義した場合、「コマンドエイリアス STORAGE に属するコマンドはタグ修飾なし、SOFTWARE はパスワードなしで実行でき、PERFORMANCE は環境変数保持が可能で且つパスワードなしで実行できる」となる。基本書式のところでも少し触れたが、タグを「且つ」のつもりで使用するところは、`SETENV: NOPASSWD:' という風にスペースを挟むと意味が違ってしまう。
エイリアスは、幾つかのコマンドやユーザをまとめて指す変数のようなものだ。エイリアスには、Host_Alias, User_Alias, Runas_Alias, Cmnd_Alias の 4つがある。
共通の記述ルールは、
Alias_Type NAME = item, item, item, ..
ここで、いわば変数名である NAME は、大文字アルファベットで始まり、大文字アルファベット, 数字, アンダーバー(`_') だけから成っていなければならない。その他、特別なエイリアスとして、全てにマッチする `ALL' がある。ALL は予約語であり、ALL というエイリアスを再定義しようとしても無理。
ふと疑問が起こり、別々のエイリアスタイプに同じ名前のエイリアスを定義してみたところ、それらはちゃんと区別されることが分かった。例えば、User_Alias として定義した ADMINS と Runas_Alias で定義した ADMINS は、別のものとして扱われる。
「どのホスト上で」のホストを格納するエイリアス。経験として、ユーザ定義文で自機ホスト名を直接書いてもうまくいかなかった場合があったため、以後は自機ホスト名を必ず Host_Alias に入れて使う習慣になっている。ただし、Host_Alias が何の役に立つのか飲み込める機会に出会えていない。sudo が某かのネットワークポートをリッスンしているわけでもないし、例えば sudo 経由で ssh を使って他機に乗り込んでも、ローカル上に書いた他機のホスト名や IP が効果を発揮するわけではない。この機能はおそらく、sudoers ファイルを共有ディスクに置き NFS などで複数のホストから参照している場合にのみ意味があるのだろう (※)。よって、これに関して書式云々について書くのはやめる。代わりに、ちょっと気付いたことを幾つか挙げる。
※ NFS共有に sudo 設定ファイルを置くには: sudo の設定ファイルは /etc/sudoers と決まっている。ソースコードに手を入れてコンパイルし直せば変えられるだろうが、そんなことをしなくても済む仕組みがある。#include と #includedir という特殊ディレクティブだ。それについては「その他のディレクティブ」で述べる。
ホストエイリアスの中身には、ホストネーム, IPアドレス, ネットワークアドレス, ネットグループ または他のホストエイリアス が使えるとマニュアルにはある。ホストネームで自機を指定する場合には、hostname コマンドがドメイン付きの FQDN を返す環境ならば、`alpha.hoge.cxm' のように FQDN で書くのがよさそうだ。ただし、hosts ファイルで alpha というエイリアス名が解決できれば (たいていはできるだろう)、`alpha' だけでも通用する。IPアドレスで書いた場合、sudo はネットワークインターフェースのパラメータを照会すると書かれている。ネットマスクなしで IPアドレスかネットワークアドレスを書いた場合、その数値が自機の持っているいずれかのネットワークインターフェースに合致すればそこからネットマスクを求めようとする。とすれば、IPアドレスで自機を示そうとする時に最も理に適った書き方は、`192.168.1.10/32' という指定だと思われる。ネットマスクは 255.255.255.255 式でも良い。
上記に付随するが、自機を示すつもりで `localhost' と書くのは間違い。sudo では localhost がヒットすることはない。あるとすれば、自機のホストネームが文字通り localhost という名前の時だけだ。
「誰が」のユーザやグループを格納するエイリアス。中身には以下のものが指定できる。
表記 | 解説 |
username | UNIXユーザ名。UID に解決してから評価するのでなく、文字列として直接評価される |
#uid | ユーザのUID。ユーザ名に変換して評価するわけでなく、数字のまま評価される |
%groupname | 文字列としての UNIXグループ名。副グループ (supplementary group) も有効 |
+netgroup | ネットグループ |
%:nonunix_group | ADなど、UNIX以外のグループ |
USER_ALIAS | 他のUser_Alias |
いずれも、前に `!' を付けて「~でない」の意味にすることもできる。例えば、
root# usermod -G www taro
としてユーザ taro を副グループ www に属させてあるとしよう。そこで、www グループを指す User_Alias "WEBMNGERS" から taro だけを仲間はずれにしたい場合のユーザエイリアス定義は;
User_Alias WEBMNGERS = %www, !taro
「誰として」のユーザやグループを格納するエイリアス。定義規則は User_Alias と同じ。違いは、内容に含めることのできる別エイリアスが Runas_Alias であることくらい。
「何ができるか」を格納するエイリアス。中身に指定できるものは、大まかに言うと;
となるが、少々複雑なので以下それぞれについて述べる。
単純な定義例としては、
Cmnd_Alias STORAGE = /bin/mount, /bin/umount, /sbin/shutdown
右辺に入るものは、コマンドファイルの実体でなければならないという制限はない。シンボリックリンクでもシェルスクリプトでも動作が成り立つ。上記のようにコマンドファイルだけを指定すると、どんな引数も受け入れられる。引数を制限したい場合には、
/sbin/shutdown -h *
のように引数を限定することもできる。次のカンマ `,' が現れるまでがひとつのコマンド指定と解釈されるので、このようにスペースを含む時でもクォーテーションで囲む必要はない。逆に、引数の要素としてカンマを使いたい場合にはバックスラッシュ `\' でエスケープする必要がある。その他にエスケープの必要な文字としては、`@!=:()\' がある。
引数の使用を一切許可したくない時には、
/bin/date ""
のように定義する。最初の例で用いたように、ワイルドカード (正規表現ではなく POSIXグロブ) も使える;
ワイルドカード | 解説 |
* | 0個以上の文字。スペースにもヒットする。引数部分では `/' にもヒットするが、コマンドファイル名部分では `/' にはヒットしない。 |
? | 任意の1文字。 |
[xy...] | いずれかの1文字、x か y か ... にヒット。`[a-z]' や `[0-9]' といった範囲記述もできる。[]内の要素には文字クラスも使える。例えば `[[\:alnum\:][\:digit\:]]' といった具合 (`:' は sudoers の機能文字なので要エスケープ)。 |
[!xy...] | x でも y でも ... でもない1文字。 |
ワイルドカードの例:
Cmnd_Alias STORAGE_MIN = /sbin/fdisk -l /dev/[a-z][a-z][a-z], /sbin/parted
フルパスを書かなくてよい特別なものに、sudoedit がある。「Tag について」でも少々触れたが、sudoedit は sudo 経由で使うことを考慮して書かれた一種の vi クローンだ。編集中に `:sh' と打つなどしてシェルエスケープ機能を使用する際、通常の vi や vim とは異なり「誰として」の権限のシェルでなく「誰が」つまり元のユーザのシェルにしか入れないようにできている。実際に sudoedit を開始するには `sudoedit file ' と `sudo -e file ' というふた通りの実行方法がある。sudoedit を Cmnd_Alias に登録する際には、他のコマンドファイル同様に引数を制限することもできる。
コマンドファイル名にあたる部分がスラッシュ `/' で終わっていると、ディレクトリを指定しているとみなされる。ディレクトリを指定すると、そのディレクトリ直下の実行ファイル全てがそのエイリアスの対象となる。サブディレクトリ下のファイルは含まれない。ディレクトリに関しても前述のコマンドファイルの場合と同様に、他のディレクトリへのシンボリックリンクでも sudo は頓着しないし、ディレクトリ下のファイルがシェルスクリプトでもシンボリックリンクでも構いはしない。コマンドファイルと同様、ワイルドカードを使った指定もできる。ただし、スラッシュ `/' はワイルドカードにヒットしない。
もちろん、コマンドファイルの否定を組み合わせて、「ただし~はダメ」という設定はできる。例えば、
Cmnd_Alias LOCALBIN = /usr/local/bin/, !/usr/local/bin/ls
許可ディレクトリのパーミッションには注意が必要だ。直下に誰でもファイルやシンボリックリンクを作れる状態だと、やりたい放題になってしまう。まあ、結論から言って、ディレクトリによる指定は上手に使うには難しい。
sudo の挙動を、あらゆる「誰が」や「誰として」のユーザに対してや、特定のユーザやホストなどに関して一括してコントロールするディレクティブ。基本書式は;
Defaults[:User|>RunasUser|!Cmnd|@Host] Parameter[,Parameter[,..]]
`Defaults Parameter ' のように定義すれば、グローバルな規定となる。かたや、例えば `Defaults:ADMINS Parameter ' のように書くとユーザエイリアス ADMINS に対する規定となり、グローバルな規定をオーバーライドする。同じタイプのエイリアスなら、`Defaults:ADMINS,BACKUPS Parameter ' といった具合にまとめて定義することも可能。
Parameter 部は、パラメータの種別によって、`Param = Value ' のように値を採るものと、採らないものがある。前者では、グローバルな規定に対して値を追加 (`+=') したり一部を無効化する (`-=') 書き方もできる。後者の種類のものを無効化したい時には `!Parameter ' のように書く。以下に、興味を惹くパラメータだけ挙げる。
パラメータ及び記述例 | 解説 |
env_reset | 「誰が」の時に持っていたほとんどの環境変数を、「誰として」に成る時にクリアする。どんな変数がクリアされるか維持されるかは root で `sudo -V' とコマンドすれば分かる。これは既定値。 |
env_keep="VAR VAR .." | env_reset が定義されている時、「誰として」に成っても保持する「誰が」の環境変数名。複数の変数を指定したい時は左例のようにスペースで区切って全体をダブルクォートする必要がある。1つの時はクォートしてはいけない。なお、env_keep を定義せずに sudo 実行時に変数と値を直接渡す方法もある。 |
env_delete="VAR VAR .." | 上記の逆で、剥奪する「誰が」の環境変数名を指定する。 |
setenv | 「誰が」の時の環境変数を、sudo 実行時の -E オプションや VAR=Value 直接指定によって「誰として」へ引き継ぐことを可能にする。User定義の Tag の説明も参照のこと。 |
noexec | コマンドに関して、共有ライブラリにダイナミックリンクして実現している機能を阻止する。Defaults定義でこれを設定してあっても、個別の User定義文で EXEC: タグを指定すればオーバーライドできる。詳しくは User定義の項の Tag の説明を参照。 |
requiretty | tty (コンソールデバイス) がないと sudo の実行を許さない。sudo 自体の規定値ではないが、RHEL/CentOS 5 系の sudoers ファイルにはで既定でこれが書いてある。セキュリティのひとつなのだが、これが設定してあると、ssh にコマンド引数を付けてリモート上で実行する時、例えば `ssh taro@remote.host sudo /sbin/fdisk -l /dev/sda' とやろうとすると "sudo: sorry, you must have a tty to run sudo" といって怒られる。ssh に限って言えば、`ssh -t taro@remote.host ...' とやってやれば ssh が仮想的に tty を割り当てるのでエラーにならずに済む。 |
authenticate | 「誰として」に成るにはパスワードを必要とする。つまり、User定義でのPASSWD: タグにあたる。`!authenticate' は User定義での NOPASSWD: に相当する。 |
runas_default=User | sudo の実行時に `-u User ' を付けなかった時の既定の「誰として」ユーザを変更したい時に使う。暗黙の既定は root。 |
runaspw | 既定では、パスワードの必要なコマンドの場合 sudo は「誰が」自身のパスワードを求めてくる。runaspw を設定しておくと、上記 runas_default で設定したユーザのパスワードを訊くようになる。 |
rootpw | 上記のバリエーション。root のパスワードを訊くようになる。 |
targetpw | もうひとつのバリエーション。「誰として」のパスワードを訊くようになる。 |
passwd_tries=N | パスワードを間違った時のリトライ回数。暗黙の既定は 3回。0 に設定すると一度も訊かずに失敗するので、結果的に NOPASSWD: なコマンドしか実行できない状態になる。 |
timestamp_timeout=N | NOPASSWD: でないコマンドの時に、一度パスワードを入力してから次にまたパスワードを求めるまでの間隔(分)。言い換えれば、正しいパスワードでコマンドを実行してから再度入力しなくていい期間。既定では 5分。0 を指定すると毎回求められる。マイナス値を指定すると二度と聞かれない。 |
editor=/usr/bin/vim | visudo で使うエディタを指定。デフォルトでは vi になってしまうので使いにくい。 |
env_editor | visudo で使うエディタを環境変数 EDITOR または VISUAL から決定する。RHEL/CentOS 4 で使用されている sudo 1.6.x 系では既定値がONであり、危険なので `!env_editor' をグローバルデフォルトで指定したほうがいい。RHEL/CentOS 5 では既定値がOFFであり特に必要ない。併せて上記 editor=.. でエディタプログラムを決め打ちしておくのがお勧め。 |
記述例:
Defaults env_reset, !env_editor, editor=/usr/bin/vim
下記のようにパラメータ毎に別々に唱えてもいい;
Defaults env_reset Defaults !env_editor Defaults editor=/usr/bin/vim
ユーザエイリアス ADMINS に許可するコマンドは全て、パスワード不要にし、環境変数の保持も可能にするなら、
Defaults:ADMINS !authenticate, setenv
つまり上記は、User定義で
ADMINS MYBOX = (ALL) NOPASSWD:SETENV: STORAGE, SOFTWARE, ...
などと書くよりすっきりする。
NOEXEC: タグと同等の制限を Defaults定義でまとめて掛ける例;
Defaults!EDITORS,PAGERS noexec
グローバルな保持する環境変数のリストに、ユーザエイリアス ORACLE の時だけ更に変数を追加する例;
Defaults env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR \ LS_COLORS MAIL PS1 PS2 QTDIR USERNAME \ LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION \ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC \ LC_PAPER LC_TELEPHONE LC_TIME LC_ALL LANGUAGE LINGUAS \ _XKB_CHARSET XAUTHORITY" Defaults:ORACLE env_keep += "ORACLE_HOME ORACLE_SID"
/etc/sudoers ファイルの中で '#include /PATH/TO/FILE ' を書いておけば、そのディレクティブが現れたところで別の設定ファイル /PATH/TO/FILE が読み込まれる。大元の /etc/sudoers ファイルには #include 文だけを書いておき、/PATH/TO/ をNFSマウントで作っておけば、設定の全てをホスト間で共有することも可能だ。
`#includedir /PATH/TO/DIRECTORY ' の場合は、そのディレクトリ下のファイルがファイル名で文字列ソートされた順で読まれる。#includedir の場合のみ、インクルードディレクトリ下のファイルにはファイル名規制があり、`~' で終わるファイルと、ドットを含むファイル (頭であれ途中であれ) は読んでくれない。
実際に試してみると、どちらにしろ、別体設定ファイルを置くディレクトリのパーミッションは root:root の 750 でなくはならないようだ。700 だと読み込めない。なお、visudo で /etc/sudoers を書き換えると、その中で #include や #includedir で指してあるファイルも「編集するか?」と訊いてくる (逆に、それを断る方法が分からない...)。インクルード先のファイルを決め打ちで編集したい時は `visudo -f /PATH/TO/FILE ' とコマンドすればいい。インクルードした設定ファイルから更に #include や #includedir することも可能だが、ループに注意しなければならない。無限ループ防止のため、ネストは 128段階に制限されている。
興味のあるコマンドラインオプションだけ挙げる。
sudoコマンドオプション
オプション | 解説 |
-u user -u #uid |
既定の「誰として」ユーザ (通常は root) ではない別のユーザとしてコマンドを実行したい時に指定する。UID で指定するには `#' を付けるのだが、シェルに解釈されないよう `\#500' のようにタイプしなければならない。既定のユーザは Defaults定義の runas_default パラメータで変更しておくことも可能。 |
-E | 「誰が」の時点での環境変数を「誰として」に成っても保持する。User定義で SETENV: タグが設定されているか、または Defaults定義で setenv パラメータが有効にしてある時だけ有効。 |
VAR=Value | User定義で SETENV: タグが設定されているか、または Defaults定義で setenv パラメータが有効にしてある時だけ有効。`sudo SID=database command ' というように使う。この例では、変数 SID が、root 権限で command を実行する時点でも保持される。こうして設定する環境変数は、Defualts定義の env_keep パラメータで指定されていなくても持ち込むことができる。また、sudo 既定の禁止環境変数 (LD_*, LDPATH 等) や env_delete による規制もバイパスされる。何かのパスのように `INCPATH=/lib:/usr/lib' といった指定をするには、コロンは `\' でエスケープする必要があることに注意。 |
-e | sudo 環境用に作られた vi クローン sudoedit でファイルを編集する。`sudo -e /file/to/edit ' という風に使う。また、`sudoedit /file/to/edit ' でも同義。詳しくは Cmnd_Alias の解説を参照のこと。sudoers で実行許可コマンドに sudoedit が指定してある場合のみ使用できる。 |
-s [command] | command なしの時には、環境変数 SHELL に設定されているシェルか passwd ファイルに記されている「誰として」のデフォルトシェルを起動する。command も指定されていると、シェルの引数として command を実行する。sudoers で当該のシェルが実行許可されている場合だけ使用できる。 |
-i [command] | 上記 -s オプション同様にシェルを起動するが、SHELL 環境変数は評価されない。大きな違いは、通常のターミナルログインや `su -' をした時のように .bashrc など一連のログインスクリプトが処理される点。sudoers で当該のシェルが実行許可されていることが必要。 |
-V | sudo のバージョンを表示するオプションだが、root でこれを使った時だけは、sudo にコンパイルされた既定の Defaults値一覧も表示される。 |
-l | 「誰が」のユーザに適用する sudoers 設定の一覧を表示する。-ll とふたつ重ねると、より説明的な表示形式となる。-l の後ろにコマンドも指定すると、そのコマンドの絶対PATH が表示されるだけ。 |
-K -k |
「誰が」のユーザの NOPASSWD: でないコマンド用のパスワード認証通過タイムスタンプファイルをリセットする (Defaults定義の timestamp_timeout パラメータも参照)。結果として、次に NOPASSWD: でないコマンドを実行する時には必ずパスワードを求められる。-K の場合はタイムスタンプファイルは削除される。-k ではタイムスタンプファイルの更新日が 1970年1月1日にリセットされる。ただしどちらも、そのユーザが `!authenticate' な設定だと何も起こらない。 |