MENU

デプロイのたびにSSHパスフレーズを聞かれる――真犯人はagentの生存判定だった

デプロイのたびにSSHパスフレーズを聞かれる――真犯人はagentの生存判定だった

デプロイスクリプトでSSH鍵のパスフレーズを毎回聞かれる場合、最初に疑いたくなるのは「鍵がssh-agentに載っているか」の判定です。

ただ、今回の原因候補はその一段手前でした。既存のssh-agentが生きているかを判定する処理が失敗し、毎回新しいagentを起動していたため、結果として毎回鍵を読み込む流れになっていた可能性が高い、というケースです。

この記事では、複数サイトをまとめてデプロイするbashスクリプトを前提に、ssh-agent再利用の切り分け方と、kill -0 を使った生存確認への置き換えを整理します。

  • 対象: Git + SSH鍵 + ssh-agent を使うデプロイスクリプト
  • 症状: 同一ログインセッション内でも、デプロイのたびにSSH鍵のパスフレーズを聞かれる
  • 修正: agentの生存判定を ps 系コマンドから kill -0 に変更
  • 確認: bash -n は通過し、本番環境での動作確認も成功
目次

「対策したのに毎回パスフレーズ」その時どこを疑うか

今回のポイントは、鍵そのものではなく、鍵を保持している ssh-agentを再利用できているか です。

スクリプトには、すでに次のような改修が入っていました。

  • 鍵がすでにagentに読み込まれているかを判定する
  • 未ロードなら ssh-add で鍵を読み込む
  • 鍵には有効期限を付ける

一見すると、これで「一度パスフレーズを入力したら、しばらく再入力しない」動きになりそうです。

しかし、その処理に到達する前に、スクリプトが毎回「既存agentは使えない」と判断していたらどうなるでしょうか。

流れはこうなります。

  1. 保存済みのagent情報を読み込む
  2. agentの生存判定に失敗する
  3. 新しいssh-agentを起動する
  4. 新しいagentには鍵が載っていない
  5. ssh-add が必要になる
  6. パスフレーズを聞かれる

つまり、鍵ロード判定を改善しても、その手前のagent再利用判定が壊れていると効果が出ません

ここがポイント: 「鍵が載っているか」だけでなく、「同じagentを見に行けているか」を確認する必要があります。

ssh-agentで鍵入力を1回にする仕組みのおさらい

ssh-agentは、SSH秘密鍵の認証情報を一時的に保持する常駐プロセスです。

パスフレーズ付きの秘密鍵を使う場合でも、一度 ssh-add で鍵を読み込めば、そのagentプロセスが生きている間は再入力を避けられます。

デプロイスクリプトでは、だいたい次のような構造になります。

# 保存済みのagent環境情報を読み込む
source "$HOME/.ssh/.agent_env"

# 既存agentが使えなければ新規起動する
eval "$(ssh-agent -s)"

# 必要なときだけ鍵を追加する
ssh-add -t 8h "$HOME/.ssh/id_ed25519_example"

実際の運用では、SSH_AUTH_SOCKSSH_AGENT_PID をファイルに保存しておき、次回実行時に読み込みます。

期待する動きは次の通りです。

  • 同一ログインセッション内では、既存agentを再利用する
  • agentに鍵が載っていれば、ssh-add しない
  • 鍵が未ロードなら、その時だけパスフレーズを入力する
  • ログインし直した後に再入力が必要になるのは許容する

今回問題にしたのは、最後の「ログインし直した後」ではありません。同じセッション内で続けて実行しても毎回聞かれる、という点です。

改修が“実行に到達していなかった”という盲点

直前の改修では、鍵ロード周りの処理が追加されていました。

具体的には、既存agentに鍵が載っているかを確認し、必要な場合だけ有効期限付きで鍵を読み込む処理です。

ただし、agentの生存判定そのものは変更されていませんでした。

ここが盲点でした。

デプロイ処理の分岐を分解する

ssh-agentを使うデプロイスクリプトでは、処理がいくつかの段階に分かれます。

  • 保存した環境情報ファイルを読み込む
  • SSH_AGENT_PID のプロセスが生きているか確認する
  • SSH_AUTH_SOCK が使えるか確認する
  • Gitリモートへアクセスできるか確認する
  • 必要なら ssh-add する
  • git fetchgit reset を実行する

今回のように「毎回パスフレーズを聞かれる」場合、ssh-add の条件式だけを見ると遠回りになります。

先に見るべきなのは、保存済みのagent情報が実際に再利用されているかです。

毎回新しいagentなら、鍵は毎回空になる

新しく起動したssh-agentには、当然まだ鍵が載っていません。

そのため、鍵ロード判定は正しく「未ロード」と判断します。ここだけ見ると処理は正常です。

しかし原因はその前にあります。既存agentを使えるはずなのに、使えないと判定してしまう。その結果、毎回空のagentを作り直していた可能性が高い、という見立てです。

プロセス生存判定の落とし穴

今回のスクリプトでは、agentの生存判定に ps 系のコマンドを特定の出力指定付きで使っていました。

ps 自体が悪いわけではありません。ただ、オプションや出力指定は環境によって差が出ることがあります。

たとえば、Linuxディストリビューション、BusyBox系の環境、コンテナ、ホスティング環境などでは、使える ps のオプションや出力形式が異なる場合があります。

今回の症状は、次の連鎖だった可能性があります。

  • 保存済みの SSH_AGENT_PID は存在する
  • しかし ps による確認が環境依存で失敗する
  • スクリプトは「agentが生きていない」と判断する
  • 新しいagentを起動する
  • 鍵が空なので ssh-add が走る
  • パスフレーズ入力が毎回発生する

これは推測を含む説明ですが、修正後に本番環境で成功しているため、今回の切り分けとしては妥当な原因候補でした。

単純で堅牢な存在確認に置き換える

修正では、agentの生存判定を kill -0 に置き換えました。

kill -0 <PID> は、プロセスに実際の終了シグナルを送るのではなく、そのPIDに対してシグナル送信できるかを確認する用途で使えます。

デプロイスクリプト内では、次のように確認します。

if [[ -n "${SSH_AGENT_PID:-}" ]] \
  && kill -0 "${SSH_AGENT_PID}" 2>/dev/null \
  && [[ -S "${SSH_AUTH_SOCK:-/dev/null}" ]]; then
  echo ">>> Using existing ssh-agent"
  return
fi

ここで見ているのは2点です。

  • SSH_AGENT_PID のプロセスが存在するか
  • SSH_AUTH_SOCK がUnixドメインソケットとして存在するか

PID だけでは、別プロセスに置き換わっている可能性を完全には排除できません。そのため、socketの存在も合わせて見ています。

今回の目的は「セッションを跨いでagentを永続化する」ことではなく、同一セッション内で不要な再入力を避けることです。linger などのセッション管理を変える話には踏み込んでいません。

サンプル: ensure_agentの考え方

固有のパスやサイト名を一般化すると、修正後の考え方は次のようになります。

ensure_agent() {
  if [[ -f "${AGENT_ENV}" ]]; then
    # shellcheck disable=SC1090
    source "${AGENT_ENV}" >/dev/null 2>&1 || true
  fi

  if [[ -n "${SSH_AGENT_PID:-}" ]] \
    && kill -0 "${SSH_AGENT_PID}" 2>/dev/null \
    && [[ -S "${SSH_AUTH_SOCK:-/dev/null}" ]]; then
    echo ">>> Using existing ssh-agent (pid: ${SSH_AGENT_PID})"
    return
  fi

  echo ">>> Starting new ssh-agent"
  eval "$(ssh-agent -s)" >/dev/null

  umask 077
  {
    echo "export SSH_AUTH_SOCK=${SSH_AUTH_SOCK}"
    echo "export SSH_AGENT_PID=${SSH_AGENT_PID}"
  } > "${AGENT_ENV}"
}

AGENT_ENV にはagentの接続情報を書き出します。認証情報そのものを書くわけではありませんが、ssh-agentに接続するための情報なので、umask 077 で他ユーザーから読みにくくしておくのが無難です。

同一セッション連続実行で検証する

この種の修正は、1回だけ実行しても十分に確認できません。

確認したいのは、「1回目に必要ならパスフレーズを聞かれ、2回目以降は同一セッション内で聞かれない」ことです。

最低限の確認手順

対象はbashスクリプトです。実行場所は、デプロイスクリプトを置いているサーバー上を想定します。

bash -n multi-deploy.sh

まず構文チェックを通します。今回の修正では、この bash -n は通過しています。

次に、同じログインセッション内で連続実行します。

./multi-deploy.sh site-a main
./multi-deploy.sh site-a main

見るポイントは次の通りです。

  • 1回目に必要ならパスフレーズを聞かれる
  • 2回目で既存agentを使っているログが出る
  • 2回目でパスフレーズを聞かれない
  • Gitリモートへのアクセス確認が通る
  • fetch / checkout / reset が最後まで完了する

今回のケースでは、ユーザーが本番環境で動作確認を行い、成功したと報告しています。

注意: git reset –hard を含むスクリプトは実行前に対象を確認する

デプロイスクリプトには、作業ツリーをクリーンにする目的で git reset --hard HEADgit reset --hard origin/<branch> が含まれることがあります。

これは未コミットの変更を消す操作です。実行前に、少なくとも次を確認しておきます。

git status --short
git remote -v
git branch --show-current

デプロイ専用ディレクトリであれば問題になりにくいですが、手作業の変更が混ざる運用では危険です。

代替案とトレードオフ

今回採用したのは、既存の構造を大きく変えずに、生存判定だけを置き換える方法です。

他にも選択肢はあります。

  • ssh-add -l を中心に判定する
  • agent環境ファイルを毎回破棄して起動し直す
  • セッションを跨いでagentを残す仕組みを検討する
  • OSやログイン管理側でagentを扱う

ただし、今回の要件は「同一セッション内で毎回聞かれないようにする」ことでした。

そのため、セッションを跨ぐ永続化までは採用していません。運用上の便利さは増えるかもしれませんが、鍵の保持時間、ログアウト後の扱い、サーバー上のユーザー分離など、別の確認事項が増えるためです。

kill -0 も万能ではありません。PID再利用や権限の問題を完全に吸収するものではないため、SSH_AUTH_SOCK の確認や、実際の git ls-remote による接続確認と組み合わせて見るのが現実的です。

再発防止で見るポイント

同じ問題を再度調べるときは、いきなり ssh-add から見ないほうが早いです。

次の順で確認すると、分岐の破綻箇所を見つけやすくなります。

  • 保存済みのagent環境ファイルが読み込まれているか
  • SSH_AGENT_PID が毎回変わっていないか
  • SSH_AUTH_SOCK が存在しているか
  • 既存agentを使う分岐に入っているか
  • ssh-add -l で鍵が見えているか
  • git ls-remote -h origin HEAD が通るか
  • 2回連続実行したとき、2回目でパスフレーズを聞かれないか

ログには、最低限次の情報を出しておくと切り分けが楽になります。

echo ">>> Using existing ssh-agent (pid: ${SSH_AGENT_PID}, sock: ${SSH_AUTH_SOCK})"
echo ">>> Existing ssh-agent not usable -> starting new ssh-agent"
echo ">>> SSH key already loaded in agent"
echo ">>> Adding SSH key (passphrase may be requested once)"

秘密鍵のパスやリモートURLをログに出す場合は、公開してよい情報か確認してください。パスフレーズや秘密鍵の中身を出力してはいけません。

まとめ

今回の教訓はシンプルです。

「鍵を読み込む処理」を直しても効かないときは、その手前の「同じagentを再利用できているか」を見る必要があります。

特に、次のような状況ではagent生存判定を疑う価値があります。

  • 同一セッション内なのに毎回パスフレーズを聞かれる
  • ssh-add 周りを直しても改善しない
  • 実行のたびに SSH_AGENT_PID が変わっている
  • agent環境ファイルはあるのに再利用されていない
  • ps 系の判定を環境依存のオプション付きで使っている

今回の環境では、agentの生存確認を kill -0 に置き換え、bash -n を通したうえで、本番環境での連続実行に成功しました。

次に同じ症状を見たら、まず「鍵が載っているか」ではなく、「そもそも前回と同じagentを見ているか」から確認します。

参照リンク

  • 本文中で参照した外部URLはありません。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

目次