要約
-
ssh
接続する際に、パスワード入力を補助してくれるツールexpect
とssh-agent
を試した。 -
expect
- Linuxのプロンプトを待ち受け、画面の出力に応じて文字を入力してくれる。
- パスワード入力以外でも使える。
- 時間かかる処理をするとタイムアウトしてしまう。
-
ssh-agent
- プロセスが鍵ファイルとパスワード情報を保持し入力が不要になる。
ssh
パスワードの入力を省略したいだけならssh-agent
をつかうと
タイムアウトや、画面の出力を考慮しなくてよいので楽でした。
2020/07/13 追記:expect の代わりに SSH_ASKPASS を使うとより簡単にパスワード入力を回避できる
コメントで教えていただきました。
expect
の問題点であった、タイムアウト等も回避できています。
単一スクリプトで簡単に実装するなら SSH_ASKPASS
複数スクリプトや複数のサーバをまたいで設定したいならssh-agent
と使い分けるといいと思います。
参考)
expectやsshpassを使わずにシェルでSSHパスワード認証を自動化する
参考にした諸先生方の記事
Linuxの対話がめんどくさい?そんな時こそ自動化だ!-expect編-
ssh-agentの使い方
経緯
私は普段プログラマとして働いていますが、サーバでコマンド発行する作業も少なくありません。
特にrpmパッケージのビルド作業には、複数サーバへのログインとコマンド発行が必要です。
定型作業やりたくない、いちいちコマンド覚えられへんし、めんどくさい…
そう思い自動化しました。
つまづいて結構時間かかったので記録を残しておきます。
ここまで調べて、時間かけてスクリプトを書くぐらいならパスワード入力する方が早いんじゃ…
そう思った人もいらっしゃるでしょう、しかし私はプログラマなのです。
プログラマという人種は、楽をするためであれば、どんな苦労も厭わない。
かつてビルゲイツが言ったとか言わないとか。
なので仕方ないのです。
Shell スクリプトを書いて自動化する
環境
CentOS 8.0
実装
早速発行してるコマンドを並べたシェルスクリプトを作ってみました。
#!/bin/bash
IP_A='10.0.0.1'
IP_B='10.0.0.2'
USER='login_user_name'
SSH_KEY='./private.key'
ssh ${USER}@${IP_A} -i ${SSH_KEY} sudo buildCommand.sh
ssh ${USER}@${IP_B} -i ${SSH_KEY} sudo buildCommand.sh
実行すると
# sh test.sh
Enter passphrase for key './private.key':
Enter passphrase for key './private.key':
当然のことながら、都度 ssh
パスワードを聞かれる。
これは自動やない…
入力の自動化ってどうすんのかなとおもってググると expect
が使えそうだった。
expect に挑戦
expect
はLinuxの対話処理を自動化してくれるコマンドです。
最小構成では入っていないので、パッケージのインストールが必要です。
早速実装
#!/bin/bash
IP_A='10.0.0.1'
IP_B='10.0.0.2'
USER='login_user_name'
SSH_KEY='./private.key'
SSH_PW='P@ssW0rd'
function runExpect () {
expect -c "
spawn env LANG=C ${*} // コマンドの実行
expect \"Enter passphrase for key \'${SSH_KEY}\':\" // 待ち受ける出力
send \"${SSH_PW}\n\" // マッチしたときに入力する内容
expect \"$\"
"
}
runExpect ssh ${USER}@${IP_A} -i ${KEYPATH} sudo buildCommand.sh
runExpect ssh ${USER}@${IP_B} -i ${KEYPATH} sudo buildCommand.sh
実行すると、簡単な ls
コマンドなどでは結果を返してくれるのですが、
本来実行したいビルドコマンドを実行すると途中で止まってしまう…
「動けexpect!なぜ動かん!!」
あれこれ試しましたが、終ぞ意図した動きをしてくれませんでした…
注意:タイムアウト秒数伸ばせないの?
timeout
コマンドを発行すると、タイムアウト時間を指定できるようでしたが、
ビルドするファイルごとに実行時間も異なるため、
タイムアウト時間を指定したくなく、使いませんでした。
ssh-agent に切り替え
途方に暮れ、会社の先輩に相談すると
「 ssh
のパスワード入力を省略したいだけなら、ssh-agent
使ったら?」と教えてもらいました。
「ssh-agent!!!」
ssh-agent
はssh
のパスワード情報を保持してくれるプロセスです。
最小構成でもインストールされています。
使い方
以下のコマンドを発行すると、ssh-agent
プロセスが立ち上がります。
eval `ssh-agent`
プロセス起動後、鍵ファイルを登録します。
ssh-add ./private.key
パスフレーズを聞かれるので、入力するとその後
ssh
コマンド実行時に鍵ファイルやパスワードの入力が不要になります。
使用後は以下のコマンドでプロセスを停止すれば、再度鍵情報が必要になります。
eval `ssh-agent -k`
スクリプトも少しだけ修正
#!/bin/bash
IP_A='10.0.0.1'
IP_B='10.0.0.2'
USER='login_user_name'
ssh ${USER}@${IP_A} sudo buildCommand.sh
ssh ${USER}@${IP_B} sudo buildCommand.sh
実行すると無事ビルドが成功しました。
ssh
に加えて scp
もパスワードの入力が不要です。
なんや expect 要らんやんけ
注意:なんで eval つけてんの?
ssh-agent
コマンドを発行すると以下のように、環境変数を設定するためのコマンドを出力します。
# ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-JfJ0l4VQtr3E/agent.27715; export SSH_AUTH_SOCK;
SSH_AGENT_PID=27716; export SSH_AGENT_PID;
echo Agent pid 27716;
出力されたコマンドをシェルで実行するために実行結果をevalに渡しています。
注意:これ誰でもsshパスなしにならん?
パスなしで実行するためには環境変数 SSH_AUTH_SOCK
にソケットファイルパスを指定する必要があります。
ソケットファイルは実行ユーザの権限で作成されるため、一般権限同士で参照することはできません。
ソケットファイルパスとプロセスIDを読まれない限りは安全です。
蛇足
sshパスなしでガンガンコマンドを発行していたのですが、
毎回プロセス立ち上げて、最初のパスワード入力するのも面倒になってきました。
とはいえプロセス立ち上げっぱなしは、心配…
よし自動化したろ!
自動化しようと思いシェルスクリプトを書き始めましたが、
再度、鍵ファイルのパスワード入力を自動化しないといけなくなりました。
そう expect
の出番です。
主人公のピンチにかつてのライバルが助けに来る胸熱展開!
完成版
#!/bin/bash
IP_A='10.0.0.1'
IP_B='10.0.0.2'
USER='login_user_name'
SSH_KEY='./private.key'
SSH_PW='P@ssW0rd'
eval `ssh-agent`
expect -c "
spawn env LANG=C ssh-add ${SSH_KEY}
expect \"Enter passphrase for \'${SSH_KEY}\':\"
send \"${SSH_PW}\n\"
expect \"$\"
"
ssh ${USER}@${IP_A} sudo buildCommand.sh
ssh ${USER}@${IP_B} sudo buildCommand.sh
eval `ssh-agent -k`
成し遂げたぜ!
注意:パスワードシェル平文で入ってるし、セキュリティ下がってない?
まぁほら、ね?
※ パスワードを平文でシェルスクリプトに書いているので危険です、ご利用は計画的に。
2019/12/05 追記:expect 使うくらいなら、大人しくsshキーのパスフレーズを解除したらどうでしょう
( 確かに!問題を解決することに固執するあまり、本来の目的を忘れていた。 )
( しかし、それではこの ”蛇足” の存在意義が......!! )
パスフレーズを解除した鍵ファイルは、だれでもパスなしで利用できてしまいます、
こちらもご利用は計画的に。
この指摘は先輩からいただきました、レビューいただきありがとうございました。
結論
-
ssh
鍵情報を自動入力するにはssh-agent
が使いやすくてよい -
expect
ももちろん有用なケースがある、特徴を理解して使い分けるのが大事 - 自動化とセキュリティホールは紙一重、気を付けよう!