ssh-agent

2015年01月06日

このエントリーをはてなブックマークに追加

快適なOpenSSH作業環境を手に入れるまでの道のり

OpenSSHによるリモートログイン対象が多く、整理せざるを得ない状況に追い込まれた所から始まり、改善を重ね、今の所は快適な環境を手に入れた。これまでの記録を、ここにまとめておく。

主な特徴

  • keychainのような機能
    • ssh-agent多重起動防止
    • ssh-addによる複数の秘密鍵登録管理
    • SSH_AUTH_SOCKパス固定化(screen/tmux detach/reattach連携対応用途)
  • keychainよりも軽量
    • 軽量な理由は、keychainよりも処理してる内容が少ないから

背景・経緯

  1. 作業対象が数台だった頃、各サーバに秘密鍵を配置する運用でしのいで来た
    1. ログイン対象数が増え始める
    2. 管理対象キーペア数が増え始め、鍵管理が面倒臭くなる
    3. SSHクライアントとなる作業用ラップトップ数が増え始める
    4. ~/.ssh/をバージョン管理し、ssh_configと秘密鍵を管理
    5. ~/.ssh/をデプロイする所までは解決 (本エントリにおいては省略)
  2. ログイン時のパスフレーズ入力が面倒になり、ssh-agentについて調査・検証を開始
    1. ssh-agentが自動起動するように~/.bashrcを改良
    2. ssh-agent多重起動問題に遭遇
    3. mitchellh謹製bashrc と出会い、コードを拝借
    4. ssh-agent多重起動防止を実装
  3. 複数の秘密鍵を扱いたいが、mitchellh版ではssh-addのデフォルトパスしか扱ってなかった
    1. 複数の秘密鍵を扱えるように改良
  4. リモートログイン先にて新たなssh-agentが起動して来てしまう問題に遭遇
    1. 大元のSSHクライアント環境においては、単一ssh-agentプロセスのみ起動していて問題は無い
    2. しかし、リモートログインすると、リモートログイン先で新たにssh-agentが起動してしまう問題に遭遇
    3. リモートログイン状態かどうかを判定する仕組みを追加
  5. screenでdetachした後、reattachすると、UNIXソケットパスが切り替わってしまう問題に遭遇
    1. ssh-agentscreenの中から使う方法を発見・拝借し、SSH_AUTH_SOCKを固定化
    2. 更にtmux内からもSSH_AUTH_SOCKが固定化されてる事を確認
  6. リモートログイン先がMacOSの場合、SSH_AUTH_SOCK が固定されない問題に遭遇
    1. Yosemiteでの外部からログインしてssh-agentを正しく使う方法を発見・拝借し、SSH_AUTH_SOCK を固定化
  7. 快適なssh生活を手に入れた
  8. 実は、ここまでまとめ来た内容と似たツールとしてkeychainの存在を知る・・・
    1. keychainモードへの切り替えを試みる
    2. プロトタイプ版を作り、使ってみると、起動するまでが遅かった
    3. keychainモードへの切り替えを保留
    4. keychainの機能のうち、ホームディレクトリがNFSマウントされた環境を考慮した仕組みだけは拝借し、機能追加
  9. 2015/01/06現在、似非keychainで快適生活を過ごしている
  10. YosemiteへのSSHログイン後の鍵が期待通りではない事に気付く
    1. 手元の環境に置いてはYosemiteでの外部からログインしてssh-agentを正しく使う方法が不要だったので、修正・削除
  11. 2015/01/08現在、似非keychainで快適生活を過ごしている

対象環境

  • ログインシェルをbashに設定しているUNIXアカウント
  • ログインシェルをzshに設定しているUNIXアカウント

対象者?(自分が置かれた環境とも言う)

  • ログイン元環境が複数台存在する
  • ログイン先環境が複数台存在する
  • 1日にSSH接続する回数が恐らく多い方だ
  • keychainが遅いと感じている

動作確認済み環境

$ bash --version
$ ssh -V
  • Cygwin 1.7 / Windows 8.1
    • GNU bash, version 4.1.17(9)-release (i686-pc-cygwin)
    • OpenSSH_6.7p1, OpenSSL 1.0.1j 15 Oct 2014
  • MacOS 10.10 (Yosemite)
    • GNU bash, version 3.2.53(1)-release (x86_64-apple-darwin14)
    • OpenSSH_6.2p2, OSSLShim 0.9.8r 8 Dec 2011
  • Raspbian 7.6 (Wheezy)
    • GNU bash, version 4.2.37(1)-release (arm-unknown-linux-gnueabihf)
    • OpenSSH_6.0p1 Debian-4+deb7u2, OpenSSL 1.0.1e 11 Feb 2013
  • Fedora release 20 (Heisenbug)
    • GNU bash, version 4.2.53(1)-release
    • OpenSSH_6.4p1, OpenSSL 1.0.1e-fips 11 Feb 2013
  • CentOS-6.6
    • GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
    • OpenSSH_5.3p1, OpenSSL 1.0.1e-fips 11 Feb 2013

使い方

  1. 【設定】 後述する作業対象ファイルを作成・修正・保存
  2. 【準備】 新規bashプロセスを作成し、ssh-agentssh-addを自動起動させる
  3. 【実行】 ssh -A <remotehost> にてリモートホストにSSH接続

作業対象ファイル

  1. ~/.bashrc or ~/.zshrc - 必須
    • 追記。無い場合は新規作成。
    • SSHクライアントおよび全SSH接続対象に反映
  2. ~/.ssh/agent_keys - 必要に応じて
    • 新規作成 +SSHクライアント環境のみ
~/.bashrc or ~/.zshrc- 必須

~/.zshrcにおいても動作を確認。bash前提で話を進めて行くので、zshの場合はbashzshで読み替えて頂きたい。

SSHクライアント環境および全SSH接続対象の~/.bashrcに追記・反映する必要がある。下記内容を、~/.bashrcに追記。追記場所は行末などで良い。

#-------------------------------------------------------------------------------
# SSH Agent
# + based on https://github.com/mitchellh/dotfiles/blob/master/bashrc#L181-L203
#-------------------------------------------------------------------------------

ssh_env=${HOME}/.ssh/environment.${HOSTNAME}

function start_ssh_agent() {
  # remote?
  [[ -z "${SSH_CLIENT}" ]] || return 0

  ssh-agent | sed 's/^echo/#echo/' > ${ssh_env}
  chmod 0600 ${ssh_env}
  . ${ssh_env} > /dev/null

  local ssh_agent_keys=${HOME}/.ssh/agent_keys

  if [[ -f "${ssh_agent_keys}" ]]; then
    local privkey=
    while read privkey; do
      # expand a file path using "~" or "${HOME}"
      eval privkey=${privkey}
      [[ -f "${privkey}" ]] || continue
      ssh-add ${privkey}
    done < ${ssh_agent_keys}
  else
    ssh-add
  fi
}

# Source SSH agent settings if it is already running, otherwise start
# up the agent proprely.

if [[ -f "${ssh_env}" ]]; then
  . ${ssh_env} > /dev/null
  ps -p ${SSH_AGENT_PID} > /dev/null || {
    start_ssh_agent
  }
else
  start_ssh_agent
fi

# static ssh agent sock path

ssh_agent_sock=${HOME}/.ssh/agent.sock.${HOSTNAME}

# based on http://www.gcd.org/blog/2006/09/100/
if ! [[ -L "${SSH_AUTH_SOCK}" ]] && [[ -S "${SSH_AUTH_SOCK}" ]]; then
  ln -fs ${SSH_AUTH_SOCK} ${ssh_agent_sock}
  export SSH_AUTH_SOCK=${ssh_agent_sock}
fi
~/.ssh/agent_keys - 必要に応じて作成

このファイルは作成しなくても良い。もしも使う場合、記述例は下記の通り。

~/.ssh/keys/github/hansode
~/.ssh/keys/wakame/deploy

この仕組みが威力を発揮する主な状況は、

  • 扱う秘密鍵が複数存在する時
  • 扱う秘密鍵は単数だが、ssh-addコマンドで追加させたい秘密鍵パスがデフォルト検索パスではなく、別の場所か複数指定したい時

なお、デフォルト検索パスはssh-addmanで述べられている。

man ssh-add より:

 DESCRIPTION
     ssh-add adds private key identities to the authentication agent,
    ssh-agent(1).  When run without arguments, it adds the files
    ~/.ssh/id_rsa, ~/.ssh/id_dsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ed25519 and
    ~/.ssh/identity.  After loading a private key, ssh-add will try to load
    corresponding certificate information from the filename obtained by
    appending -cert.pub to the name of the private key file.  Alternative
    file names can be given on the command line.

manに述べられている通り、引数無しssh-addコマンド実行の場合は、下記ファイルが追加対象となる。

  • ~/.ssh/id_rsa
  • ~/.ssh/id_dsa
  • ~/.ssh/id_ecdsa
  • ~/.ssh/id_ed25519
  • ~/.ssh/identity

これらに該当しないファイルパス、または複数ファイルを扱いたい場合、~/.ssh/agent_keysに記述しておくと、秘密鍵を容易に扱えるようになる。例え扱う秘密鍵が単一であり、しかもデフォルト検索対象であったとしても、明示的な宣言により、何を利用しているのかが見えやすいと言うメリットがある。それゆえ、個人的には積極的に利用している。

動作確認: ローカル環境編

前提条件
  • ローカル環境の~/.bashrc 修正が完了している事
確認項目
  1. ssh-agentが自動起動する
  2. ssh-addにより秘密鍵が登録される事
  3. ssh-agentが多重起動しない事
  4. SSH_AUTH_SOCKが固定化される事
確認作業: ssh-agent自動起動、ssh-addによる鍵登録、多重起動防止

bashを起動する。設定が正しければ、ssh-agent起動とssh-addによる複数鍵登録に成功し、下記のような出力が得られる。キーペアにパスフレーズが設定されている場合は、パスフレーズ入力待ちとなる。

localhost$ bash
Identity added: /home/hansode/.ssh/keys/github/hansode (dsa w/o comment)
Identity added: /home/hansode/.ssh/keys/wakame/deploy (rsa w/o comment)

次に、ssh-agentが多重起動しない事を確認する。新たなbash起動前のPIDを確認。

localhost$ echo $$
6796

PID6796である事が分かった。更にbashを起動。

localhost$ bash
localhost$ echo $$
2388

bash起動後、今度は何も出力されていない。そしてPID2388。これにより、別PIDである事が分かる。更にもう1つbashを起動してみる。

localhost$ bash
localhost$ echo $$
3916

PIDssh-agentの起動状況をまとめると、

  1. PID=6796 - ssh-agentが起動
  2. PID=2388 - ssh-agentは起動しない
  3. PID=3916 - ssh-agentは起動しない

整理すると、

  • ssh-agnetが起動してない場合は、ssh-agentを起動し、ssh-addで秘密鍵を登録
  • ssh-agentが起動している場合は、何もしない
  • ssh-agent多重起動を防止
確認作業: SSH_AUTH_SOCK固定化

SSHに関する環境変数を確認。

localhost$ env | sort | egrep ^SSH_
SSH_AGENT_PID=192
SSH_AUTH_SOCK=/home/hansode/.ssh/agent.sock.localhost

この結果から、SSH_AUTH_SOCK~/.ssh/agent.sock.${HOSTNAME}である事が分かる。ファイルパスに${HOSTNAME}を含めている理由は、ホームディレクトリがNFSマウントされた環境においても動作させる為。

~/.ssh/agent.sock.${HOSTNAME}のファイル情報を確認してみると、

localhost$ ls -l ~/.ssh/agent.sock.localhost
lrwxrwxrwx 1 hansode なし 32 Jan  3 21:39 /home/hansode/.ssh/agent.sock.localhost -> /tmp/ssh-3RRm1KL1FZ1A/agent.6700=

SSH_AUTH_SOCKに指定されているファイルは、シンボリックリンクである事が分かる。

シンボリックリンク先は/tmp/ssh-3RRm1KL1FZ1A/agent.6700である事も分かる。/tmp/ssh-3RRm1KL1FZ1A/agent.6700のファイル情報を確認してみると、実態となるUNIXソケットである事が分かる。

localhost$ ls -l /tmp/ssh-3RRm1KL1FZ1A/agent.6700
srw------- 1 hansode なし 0 Jan  3 21:29 /tmp/ssh-3RRm1KL1FZ1A/agent.6700=

整理すると、

  1. SSH_AUTH_SOCK~/.ssh/agent.sock.${HOSTNAME}
  2. ~/.ssh/agent.sock.${HOSTNAME}は、シンボリックリンク
  3. リンク先は/tmp/ssh-3RRm1KL1FZ1A/agent.6700

ssh-agentが改めて起動する場合は、シンボリックリンク情報だけが張り替わり、SSH_AUTH_SOCK=~/.ssh/agent.sock.${HOSTNAME}は固定化・定数化される。

動作確認: リモート環境編

前提条件
  • ローカル環境の~/.bashrc 修正が完了している事
    • ssh-agentが起動してる事
    • リモート環境用キーペアの秘密鍵がssh-addによる登録されてる事
  • リモート環境の~/.bashrc 修正が完了している事
    • リモート環境用キーペアの公開鍵が~/.ssh/authorized_keysに登録されてる事
確認項目
  1. SSH_AUTH_SOCKが固定化される事
確認作業: SSH_AUTH_SOCK固定化

参考までに、ForwardAgent noの場合は、SSH_AUTH_SOCKが存在しない。

localhost$ ssh remotehost
remotehost$ env | sort | egrep ^SSH_
SSH_CLIENT=192.0.2.68 39922 22
SSH_CONNECTION=192.0.2.68 39922 192.0.2.101 22
SSH_TTY=/dev/pts/0

ForwardAgent yes(-Aオプション付き)の場合は、SSH_AUTH_SOCKが設定される。そして、リモート環境においても~/.ssh/agent.sock.remotehostが設定されている事も分かる。

localhost$ ssh -A remotehost
remotehost$ env | sort | egrep ^SSH_
SSH_AUTH_SOCK=/home/hansode/.ssh/agent.sock.remotehost
SSH_CLIENT=121.114.159.68 39934 22
SSH_CONNECTION=192.0.2.68 39922 192.0.2.101 22
SSH_TTY=/dev/pts/0

ローカル環境と同じ様に、リモート環境も~/.ssh/agent.sock.remotehostは、実態ソケットファイルを参照するシンボリックリンクとなっている事が分かる。

remotehost$ ls -l ~/.ssh/agent.sock.remotehost
lrwxrwxrwx 1 hansode hansode 31 Jan  3 22:33 /home/hansode/.ssh/agent.sock.remotehost -> /tmp/ssh-2cwxr7qAo7/agent.30716

動作確認: リモート環境screen連携編

前提条件
  • ローカル環境の~/.bashrc 修正が完了している事
  • リモート環境の~/.bashrc 修正が完了している事
確認項目
  1. SSH_AUTH_SOCK
    1. screenの外から固定化されている事
    2. screenの中から固定化されている事
    3. detach/reattachしたscreenの中からも固定化されている事
確認作業: SSH_AUTH_SOCK固定化

ForwardAgent yes(-Aオプション付き)で対象サーバにSSHログイン。

localhost$ ssh -A remotehost

screen起動前のPIDと環境変数SSH_AUTH_SOCKを確認。

remotehost$ echo $$
27973
remotehost$ printenv SSH_AUTH_SOCK
/home/hansode/.ssh/agent.sock.remotehost

期待通り~/.ssh/agent.sock.${HOSTNAME}が指定されている。

この時のファイルパスが、ローカル環境におけるファイルパスとは違う事に注目したい。リモート環境はリモート環境特有ファイルパスが生成されいてる。

  • localhost: ~/.ssh/agent.sock.localhost
  • remotehost: ~/.ssh/agent.sock.remotehost

これはローカル環境と同様にNFSマウントされたホームディレクトリ環境を考慮した仕組みである。

次にscreenを起動。

remotehost$ screen

screen内のプロセスIDと環境変数SSH_AUTH_SOCKを確認。

remotehost:screen$ echo $$
28303
remotehost:screen$ printenv SSH_AUTH_SOCK
/home/hansode/.ssh/agent.sock.remotehost

期待通り~/.ssh/agent.sock.${HOSTNAME}が指定されている。

次に、detachしreattachする。

remotehost$ screen -r

再度screen内のプロセスIDと環境変数SSH_AUTH_SOCKを確認。

remotehost:screen$ echo $$
28303
remotehost:screen$ printenv SSH_AUTH_SOCK
/home/hansode/.ssh/agent.sock.remotehost

期待通り~/.ssh/agent.sockが指定されている。これにより、detachしたscreen内の作業を後日行う事が可能だ。

余談:Keychain化を試みたが・・・

ある程度の仕組みをまとめ終えてからkeychainの存在を知り、一度はkeychainに乗り換え作業をしてみた。結果は、keychainを使わない事を選択した。理由は、keychain起動速度が遅い事。多機能ゆえに速度が犠牲になっているのだろう。秘密鍵を常時5つ登録している状況においては、特に遅く感じられた。独自実装だけでも機能要件を満たしていたので、敢えてkeychain化を中断した。

個人利用の限りでは、似非keychainで問題は無い。仮に多人数を対象にした鍵管理フローを構築したい場合は、keychain仕様が良いのかも知れない。少なくとも誰か1人が不満を言うまでは。

現バージョンの課題

  • ssh-agent再起動を考慮してない
    • 解決策はssh-agent -kを手動実行し、bashを起動

あとがき

本エントリを書くきっかけとなったのは、身近なメンバーに対して自分のssh-agent・秘密鍵管理術の説明資料が無かった事。彼らに作業改善案を口頭で説明・提案するにしては、伝える量が多く、手頃な説明資料が欲しくなった。そして、今回の末年始休みを活用し、まとめ終えた。

エントリ初版は数時間で書き終えたが、どうしても関連するコードが気になり、整理を開始。そして再検証作業、文書修正・・・。最終的に書き終えるまでにかかったのは、合計6日間。関連物も含め、整理する良い期間となった。

とにかく、手頃な説明資料を手に入れた!喜ばしい。

関連成果物

参考文献

続きを読む


編集
@hansode at 10:30|PermalinkComments(0)TrackBack(0)