Help us understand the problem. What is going on with this article?

シェルスクリプトで ssh 秘密鍵のパスワード入力を回避するときは ssh-agent を使うと楽

要約

  • ssh 接続する際に、パスワード入力を補助してくれるツール expectssh-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-agentsshのパスワード情報を保持してくれるプロセスです。
最小構成でもインストールされています。

使い方

以下のコマンドを発行すると、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 ももちろん有用なケースがある、特徴を理解して使い分けるのが大事
  • 自動化とセキュリティホールは紙一重、気を付けよう!
yuukichi_nankou
プログラマーです。主にPHP、GolangとShellScriptを書いています。クラウド環境はGCPが好きです。
x-trans
AWS、GCP、Azureの導入設計、環境構築、運用・保守までサポートするエンジニア軍団
https://x-trans.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away