🧹

sshと、keychain、expectを用いた自動化について

2022/11/05に公開

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