何度も要求されるパスワード入力を省略する

特定のソフトウェアを使っていると内部的に `sudo` を実行しており、毎回パスワードの入力を要求されて面倒！ということがあると思います。例えばVagrantのsynced_folder機能をnfsで使っていると `vagrant up` の度にパスワードを入力しなければなりませんが、正直面倒ですね。

というわけでパスワード入力を省略したいです。でもなんでもかんでもパスワード無しで `sudo` できてしまうのは怖いので、特定のコマンドだけパスワード無しでも `sudo` できるようにする方法の解説です。

## 免責

ご自身の責任の元に信頼できる相手だけに `sudo` 権限を渡すようにしてください。

## `sudo` しているコマンドを把握する

まずはパスワード入力を求められた際に、ソフトウェアの内部で実行しているコマンドが何なのか知らなければいけません。

それを知るにはシステムログを見るのが手っ取り早いです。環境によりけりですが、Macはデフォルトで /var/log/system.log に書きだすようです。ご自身の環境についてはsyslogとかrsyslogの設定ファイルを読んでください。

で、 `sudo` が実行されると全てのログが出てくるので `tail` しときます。

```bash
tail -f /var/log/system.log | grep sudo
```

この状態でそのパスワード入力を要求してくるソフトウェアを実行し、実際にパスワードを入力したりすると、ここにもりもりとログが出てきます。例えば `vagrant up` するとこんなログが得られます。（分かりやすいように若干加工しています）

```
% tail -f /var/log/system.log | grep sudo | cut -d';' -f 4
 COMMAND=/usr/bin/sed -E -e /^# VAGRANT-BEGIN:( 501)? ad65f9aa-b0f6-4c6a-820b-f8e51d79ab3e/,/^# VAGRANT-END:( 501)? ad65f9aa-b0f6-4c6a-820b-f8e51d79ab3e/ d -ibak /etc/exports
 COMMAND=/bin/zsh -c echo '# VAGRANT-BEGIN: 501 ad65f9aa-b0f6-4c6a-820b-f8e51d79ab3e' >> /etc/exports
 COMMAND=/bin/zsh -c echo '"/Users/yuku/src/qiita" 192.168.33.11 -alldirs -mapall=501:20' >> /etc/exports
 COMMAND=/bin/zsh -c echo '# VAGRANT-END: 501 ad65f9aa-b0f6-4c6a-820b-f8e51d79ab3e' >> /etc/exports
 COMMAND=/sbin/nfsd restart
```

というわけでこれらのコマンドを `sudo` できれば `vagrant up` 時のパスワード入力をスキップできることになります。

## 特定コマンドをパスワードなしで `sudo` できるようにする

先ほどのコマンドをパスワード無しでroot権限で実行できるようにする設定は例えばこんな感じで書けます。

```text:/etc/sudoers
yuku mba=(root) NOPASSWD: /bin/zsh -c echo '*' >> /etc/exports
yuku mba=(root) NOPASSWD: /sbin/nfsd restart
yuku mba=(root) NOPASSWD: /usr/bin/sed -E -e /*/\,/*/ d -ibak /etc/exports
```

詳しくはsudoers(5)を読んでもらうとして、軽く意味だけ説明すると「yukuユーザはmbaマシンでrootとしてパスワード無しでこれらのコマンドを実行できる」という意味になります。コマンドの中に含まれる `*` はワイルドカードです。3行目でやっているようにコマンド中に `,` が出てくる場合はバックスラッシュでエスケープする必要があります。

ところでmbaというのは僕のMACのマシン名なのでローカルマシンでのみ実行できることになります（localhostとか127.0.0.1と入力してもローカルマシンという意味にはならないので注意）。ちなみにマシンの名前は `hostname` で確認できます。

```bash
% hostname
mba
```

どのマシンでも、どのユーザとしても実行できるようにするにはALLを指定します。手軽な反面権限が強いのでご利用は計画的に。

```text
yuku ALL=(ALL) NOPASSWD: /sbin/nfsd restart
```

### Cmnd_Aliasを使って共通化

ただこれだとこれらの3つの設定が互いに関係しあったものだということが分かりませんし、権限を変えようと思うと3箇所も変えないといけなくて面倒なので、こういう時はCmnd_Aliasという機能を使います。

```text:/etc/sudoers
Cmnd_Alias VAGRANT_EXPORTS_ADD = /bin/zsh -c echo '*' >> /etc/exports
Cmnd_Alias VAGRANT_NFSD = /sbin/nfsd restart
Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/\,/*/ d -ibak /etc/exports
yuku mba=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE
```

## /etc/sudoersを編集する場合は必ず `visudo` を使う

ところで/etc/sudoersを直接編集しては **絶対にいけません**
/etc/sudoersは変更したら、ファイルが正しかろうが文法に間違いがあろうが、即座にその効果が適用されます。つまり、ファイルを壊したらあなたは `sudo` できなくなります。 `sudo` できないということは/etc/sudoersを編集することもできないので、何かしらの方法で直接rootになるか、ストレージを外部マシンにマウントしてそっちで編集するとかしないと復旧できなくなります。

`visudo` は/etc/sudoersを編集する専用のコマンドで、編集完了時に保存する前に構文をチェックして問題がある場合はロールバックしたりしてくれます。名前を見るとViじゃないとダメな雰囲気がありますが、`EDITOR`環境変数を指定しているとそのエディタが使われます。

### スクリプトから編集する場合は `visudo -c -f`

手動で編集する場合は `visudo` を使うとして、スクリプトなどから自動的に編集する場合は `visudo -c -f` を使うといいと思います。`-c` はcheck-onlyオプションで変更を適用せずに構文のチェックだけを行い、 `-f` は外部ファイルをチェックするオプションです。

例えば既存のものに新しい設定を追記するスクリプトはこんな感じで書いておくと安心感があります。

```bash
# 一時ファイルを作成
tmp=`mktemp -t vagrant_sudors`
# /etc/sudoersの内容を取ってくる
cat /etc/sudoers > $tmp
# 追記したい内容
cat >> $tmp <<EOF
Cmnd_Alias VAGRANT_EXPORTS_ADD = $SHELL -c echo '*' >> /etc/exports
Cmnd_Alias VAGRANT_NFSD = /sbin/nfsd restart
Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/\\,/*/ d -ibak /etc/exports
%staff `hostname`=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE
EOF
# 構文をチェック
visudo -c -f $tmp
if [ $? -eq 0 ]; then
    echo 'Vagrant用のコマンドをsudoersに追加'
    cat $tmp > /etc/sudoers
else
    echo '構文エラー。中止'
fi

rm -f $tmp
```

### 合わせて読みたい

- sudoers(5)
- visudo(8)
- https://gist.github.com/GUI/2864683

    元ネタになったシェルスクリプト。対応しているVagrantのバージョンが古いので注意。