sshと、keychain、expectを用いた自動化について
sshのパスフレーズ入力を自動化させることで、手間を省きます。
基本的にssh-agentについては、keychainに任せます。
expectを用いて、sshのパスフレーズの入力を自動化します。
sshのパスフレーズはgpgで暗号化させたファイルから読み込むものとします。
gpgでの暗号化は公開鍵認証による暗号化、またはパスフレーズによる暗号化を用います。
gpgのパスフレーズによる暗号化の場合、平文ファイルgpg-passphraseを適切な場所に用意するものとします。
- 2022-11-07 nohupでも停止しないように微修正しました。
準備
パスフレーズファイルの暗号化
公開鍵認証による暗号化
sshのパスフレーズはssh-passphraseファイルに書き込まれているとします。
gpg -r <email address> -e ssh-passphrase
パスフレーズによる暗号化
gpgのパスフレーズはgpg-passphraseファイルとして用意します。
gpg-passphraseは、保管する場所と、パーミッションに注意しましょう。
gpg --passphrase-file gpg-passphrase --batch --pinentry-mode=loopback -c ssh-passphrase
復号化の確認
公開鍵認証あるいはパスフレーズによる暗号化で、ssh-passphrase.gpgファイルが作成されました。無事、復号化できるか確認してみましょう。
公開鍵認証で復号
gpg -dq ssh-passphrase.gpg
パスフレーズで復号
gpg --passphrase-file gpg-passphrase --batch --pinentry-mode=loopback -dq ssh-passphrase.gpg
無事、復号化できることを確認したら、ssh-passphraseファイルを削除しておきます。
今後は、ssh-passphrase.gpgファイルを復号化してsshのパスフレーズを用います。
gpgでパスフレーズで暗号化する場合、gpg-passphraseが平文なのは、仕方ないかな?
gpg-passphraseファイルには、適切にパーミッションを設定しておきましょう。
shred -u ssh-passphrase
サンプルスクリプト
以上の準備をふまえて、以下が自動化のサンプルスクリプトです。
###### expectによる、履歴を残さない
export HISTIGNORE="expect*";
###### スクリプト終了時には、keychainをクリアして、ssh-agentを停止する。
if [ $ZSH_VERSION ]
then
zshexit() {
keychain --clear
keychain -k mine
}
trap exit HUP INT TERM
else
trap "
keychain --clear
keychain -k mine
" EXIT
fi
###### sshのパスフレーズを復号化する
PASSPHRASE=$(gpg --passphrase-file gpg-passphrase --batch --pinentry-mode=loopback -dq ssh-passphrase.gpg)
###### expectでパスフレーズ入力を自動化
expect << EOF
spawn keychain --agents ssh --eval id_ed25519
expect "* passphrase *:" {
send "$PASSPHRASE\r"
}
expect {
"denied" { exit 1 }
eof { exit 0 }
}
EOF
###### このスクリプト内で、有効化させる。
eval `keychain --eval id_ed25519 2>/dev/null`
###### ここから下に、行いたい処理を記述
gpgとexpectを組み合わせた自動化の場合
自動化は、公開鍵認証でパスフレーズなしなら簡単です。
結局はgpgの秘密鍵が漏洩したら、暗号が解かれることには変わりありません。
定期的に変更するのが推奨されるのは、そのためでしょう。
より良く行うとしたら、秘密鍵を複数用意します。
そして、そのうちの1つをパスフレーズなしの鍵にして、それで他の鍵のパスフレーズを暗号化し、復号化するというステップを踏めば、自動化も幾分は安全になるのではないでしょうか?
手間暇かけて、多段構成にすることで、時間を稼ぎ、安全を担保するということには、変わりありませんよね?
仮にgpgとexpectで自動化する場合の手順を確認します。
便宜的に平文テキストファイルからパスフレーズを読み込ませます。
#!/bin/bash
#
## 便宜的に平文テキストファイルからパスフレーズを読み込む。
password=$(cat $HOME/.ssh/gpg-passphrase)
# expectでgpgに解読させる。出力は抑制する。
LC_ALL=C expect << EOF
spawn gpg --pinentry-mode=loopback --output /dev/null -dq $HOME/.ssh/gpg-passphrase.gpg
expect "* passphrase:" {
send "$password\r"
}
expect eof
EOF
# パスフレーズを変数に格納する。
passphrase=$(gpg -dq ~/.ssh/gpg-passphrase.gpg)
# キャッシュをクリアするために、gpg-agentをreloadする。
gpg-connect-agent reloadagent /bye
# gpgのパスフレーズの復号確認
# 一致しないとおかしい。
[[ "$passphrase" == "$password" ]] && echo "平文パスフレーズと復号させたパスフレーズが一致しました" || echo "一致しないのは、おかしいです。"
これでsshのパスフレーズを暗号化するための、gpgのパスフレーズを得ることができました。
秘密鍵やパスフレーズの取り扱いは大事ですね。
以上、ご参考まで。
Discussion