DockerコンテナにSSHして遊ぼう & ssh-agentの罠?
とある検証のため、SSHできる環境が欲しくなった。
ということで、Dockerを使ってお手軽にSSHサーバーを建てていく。
やること:
- SSHするためのサーバーをDockerコンテナで作る
-
ssh-agent
のキャッシュによる罠について検証する
メモ置き場
-
PUBLIC_KEY
環境変数で複数の公開鍵を設定する方法がわからん-
\n
で改行する: ダメだった -
ash
とかいうのが関係してる? - 結論: ボリュームでマウントしてファイル指定しよう
-
-
-i
で指定された鍵よりssh-agent
の鍵が優先される -
-o IdentitiesOnly=yes
を指定するとssh-agent
は無効になる by ChatGPT
1. サーバーを立てる
SSHするためのサーバーをDockerコンテナで作る。
方針
-
linuxserver/openssh-server
というDockerイメージを使用 - パスワード認証は使わず、公開鍵認証のみを使う
- ユーザー
nanasi
でlocalhost:22
にあるサーバーに接続する
linuxserver/openssh-server
とは?
簡単にSSHサーバーが建てられるDockerイメージ。
コンテナを起動する=SSHサーバーを起動する、というお手軽仕様。
ちなみに独自仕様もりもり(たぶん)。
設定は設定ファイルじゃなくて環境変数でやろう。
Openssh-server is a sandboxed environment that allows ssh access without giving keys to the entire server. Giving ssh access via private key often means giving full access to the server. This container creates a limited and sandboxed environment that others can ssh into. The users only have access to the folders mapped and the processes running inside this container.
環境変数での設定
linuxserver/openssh-server
では、コンテナ起動時に独自の環境変数を設定できる。
今回は要件的に環境変数で簡潔できるので、コンテナの起動コマンドで設定を終わらせる。
今回使う環境変数:
-
PUBLIC_KEY="略"
: 公開鍵認証に使う公開鍵 -
LOG_STDOUT=true
: ログを標準出力にも出力する、検証で使う -
USER_NAME=nanasi
: 環境にnanasi
というユーザーを作成し、SSHで接続できるようにする
ユーザーを設定する理由
デフォルトだとroot
としてSSHできないため。
なお、root
で試してみたらPermission denied
と言われた。
このエラーを回避するための最も簡単な方法は、独自のユーザーを設定すること。
しかもセキュリティがいい感じになるらしい。
こんなことが書いてあった。
Set to true to allow linuxserver.io, the ssh user, sudo access. Without USER_PASSWORD set, this will allow passwordless sudo access.
ポート
デフォルトだと2222
にサーバーを立てる。
どこかで設定できるのかもしれないが、設定項目が見つからなかった...
ただもちろん、コンテナではなくホストのどこのポートにサーバーを立てるかは、ポートフォワードで設定できる。
同じみの-p
フラグでフォワードできる。
ちなみに、何がどのようにフォワードされてるか知りたければ、docker port
コマンドが使える。
使い方は簡単で、docker port コンテナ名
で調べられる。
ssh
の結果がconnection refused
となったら見る価値はあると思う。
やり方
コンテナを起動する
まずはコンテナを起動するためのコマンドを書く。
ターミナルで片付けると辛いので、それ用のファイルを作ったほうがいい。
例えばこんな感じ。
docker run -d \
-p 22:2222 \ # ホストのポート:2222
-e PUBLIC_KEY="後で公開鍵を入れる" \
-e LOG_STDOUT=true \
-e USER_NAME=nanasi \
linuxserver/openssh-server
なお、このファイルはどこに置いてもいい。
というかどこのディレクトリで作業しても同じだと思う。
鍵ペアを生成する
次に鍵ペアを生成する。
ここではアルゴリズムにed25519
を使う。
ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key: # なし(デフォルト)
Enter passphrase (empty for no passphrase): # なし
Enter same passphrase again: #なし
ちなみに今回は、最初は~/.ssh
が存在しない想定。
ほかに鍵があると検証の妨げになる他、秘密鍵を指定する必要が生まれるため。
鍵が~/.ssh
にあったら成功。
ls ~/.ssh
id_ed25519 id_ed25519.pub
鍵が生成できたら、公開鍵をコンテナ起動コマンドに書く。
公開鍵は生成された鍵ファイルのうち.pub
がついているほう。
おそらくpublic
のpub
なのでわかりやすい。
以下のコマンドで中身を出力し、クリップボードにコピーする。
less ~/.ssh/id_ed25519.pub
そしてssh-server
に書く。
docker run -d \
-p 22:2222 \ # ホストのポート:2222
-e PUBLIC_KEY="ssh-ed25519 AAAAC3a略9WVHz nanasi@nanasi-mac.local" \ # ここ
-e LOG_STDOUT=true \
-e USER_NAME=nanasi \
linuxserver/openssh-server
そしたらssh-server
ファイルを実行し、SSHサーバーを起動する。
bash ./ssh-server
ちなみにDocker Desktopのダッシュボードは非常に便利なので使おう。
今回はログの確認をDocker Desktopから行う。
ということでssh
コマンドで接続してみる。
初回の接続=フィンガープリントが見つからないので、yes
と入力して登録する。
すると接続できる。
ssh nanasi@localhost
# 略
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Welcome to OpenSSH Server
接続について
ここで一度現状確認。
- 使用している秘密鍵:
~/.ssh/id_ed25519
- 今回は
ssh
コマンドに-i
を使用していない - そのため
~/.ssh
内にある鍵がデフォルトで使われる
- 今回は
- 接続があるとサーバーは標準出力にログを吐く
- ここからどのアルゴリズムの鍵で接続されたかがわかる
- 今回なら
ED25519
と出力される
-
22
番=SSHのデフォルトのポートにサーバーがあるため、ポートを指定しなくても接続できる -
~/.ssh/known_hosts
にはlocalhost
のことが書かれている- コンテナを起動し直すとホスト鍵が変わってしまうため、公開鍵だかフィンガープリントが一致しなくなりエラーが出る
- 対策: コンテナを起動するたびに
known_hosts
を削除する
- ちなみに使用OSはMac
ssh-agent
について検証する
2. SSH接続ができる環境が整ったということで、さっそく検証。
内容はざっくり言うと、意図せずにssh-agent
の秘密鍵で接続されること。
概要
-
ssh-agent
は鍵をキャッシュする - MacOSではキーチェーンに鍵を追加できる=半永久的に鍵が保管される
- 実際に使うには別で
.bashrc
などでssh-add
する必要あり - 検証では鍵が使える状態になっている想定
- 実際に使うには別で
- キーチェーンの鍵は
~/.ssh
を消しても残る -
ssh
の-i
オプションでは秘密鍵を指定できる- 指定されなかった場合は
~/.ssh
にある(デフォルト名の?)鍵で接続される -
-i
とssh-agent
ならssh-agent
が優先
- 指定されなかった場合は
=> ~/.ssh/id_ed25519
で接続してるつもりなのに、ssh-agent
のRSA鍵で接続してる!?
...ということが起こりうるのでは?
検証手順
大きく3ステップに分かれている
-
RSA鍵を追加:
- RSA鍵(
R
とする)を用意し、ssh-agent
を通してキーチェーンに追加する - SSHサーバーに公開鍵
R'
を追加する -
ssh-add --apple-use-keychain
でssh-agent
に秘密鍵R
を追加する - 秘密鍵
R
で接続を試み、成功する
- RSA鍵(
-
RSA秘密鍵を削除:
- 鍵ペア
R
を~/.ssh
フォルダごと削除する - ED25519鍵(
E
)を用意する - SSHサーバーに公開鍵
E'
を追加する - 接続を試みたとき、使われるのは秘密鍵
R
orE
?<= 検証1
- 鍵ペア
-
RSA公開鍵を削除:
- 別のED25519鍵(
E2
)を用意する - SSHサーバーから公開鍵
R'
を削除する - SSHサーバーに公開鍵
E2'
を追加する(置き換える形) - 接続を試みたとき、使われるのは秘密鍵
R
orE2
? <= 検証2
- 別のED25519鍵(
公開鍵ファイル
検証には公開鍵を複数登録する必要があるので、PUBLIC_KEY
ではなくちゃんとしたファイルで管理したい。
そんなときは、ボリューム + PUBLIC_KEY_FILE
を組み合わせる。
rm ~/.ssh/known_hosts
docker run -d -p 22:2222 \
-e PUBLIC_KEY_FILE="/config/user/authorized_keys" \
-e LOG_STDOUT=true \
-e USER_NAME=nanasi \
-v config:/config/user \ # 既存のファイルは消えないので安心
linuxserver/openssh-server
# これは公開鍵を入れるファイル
1. RSA鍵を追加
これは通常のSSH接続。
ただし、キーチェーンに鍵を追加する点に注意。
-
ssh-keygen -t rsa
で鍵ペアR
を生成する -
./config/authorized_keys
に公開鍵R'
を追加する -
ssh-add --apple-use-keychain
でssh-agent
に秘密鍵R
を追加する -
ssh nanasi@localhost
で接続を試みる => 成功
ssh-add
本当は鍵を半永久的に使える工夫がされていたが、今回は短期で終わらせるので省略。
実際の方法は以下:
ssh-add --apple-use-keychain
ssh-add --apple-load-keychain
2. 別のED25519鍵を追加
ここでは秘密鍵R
を削除し、それでもキーチェーンの鍵は有効か?を調べる。
結論から言うと有効なので、~/.ssh
を消しただけだと鍵は完全に消せていないことになる。
-
rm -r ~/.ssh
で秘密鍵R
を削除する -
ssh-keygen -t ed25519
で鍵ペアE
を生成する -
./config/authorized_keys
に公開鍵E'
を追加する- 公開鍵
R'
はそのまま - 各公開鍵の区切りは改行にする
- 公開鍵
- SSHサーバーのコンテナを作り直す
-
ssh nanasi@localhost
で接続を試みる => 秘密鍵R
が使われている
R'
を削除
公開鍵サーバーから公開鍵R'
を削除する。
かつ、あまり意味はないだろうが、新しいED25519鍵(E2
)の再生成もする。
このときssh-agent
の秘密鍵R
は使えなくなる。
では代わりに手元の使用可能である秘密鍵E2
が使われるのか? = フォールバックはあるか?が今回の検証内容。
結論
結論から言うとフォールバックされる。
しかもデバッグモード(-v
)ではない場合、特にそれを示すようなログは出ない。
このため、
- 実は
ssh-agent
に秘密鍵R
があった - 公開鍵
R'
が削除されたから秘密鍵E2
が使われるようになった
という事実が発覚しにくい。
-
rm -r ~/.ssh
で秘密鍵E
を削除する-
id_ed25519
以外にリネームでも可
-
-
ssh-keygen -t ed25519
で鍵ペアE2
を生成する -
./config/authorized_keys
から公開鍵R'
を削除する -
./config/authorized_keys
に公開鍵E2'
を追加する(置き換える形) - SSHサーバーのコンテナを作り直す
-
ssh -v nanasi@localhost
で接続を試みたとき、- 秘密鍵
R
での接続が失敗する - フォールバックとして秘密鍵
E2
が使用され、成功する
- 秘密鍵
SSHサーバーのコンテナを作り直す
これは./config/authorized_keys
の変更が自動で再適用されないから。
Dockerコンテナ内で変わっても、それが実際のauthorized_keys
にまで反映されるとは限らない。
一度ssh-agent
の秘密鍵R
で接続しようとしたが失敗した、というのがわかる。
debug1: Offering public key: nanasi@nanasi-mac.local RSA SHA256:~略~ agent
debug1: Authentications that can continue: publickey,keyboard-interactive
# 略
debug1: Offering public key: /Users/nanasi/.ssh/id_ed25519 ED25519 SHA256:~略~
debug1: Server accepts key: /Users/nanasi/.ssh/id_ed25519 ED25519 SHA256:~略~
デバッグモードだとこういったログが出力される。
自分がどの秘密鍵で接続してるのか知りたいときに便利。
要約
- 削除した鍵でも
ssh-agent
に残っていることがある -
ssh-agent
の鍵での接続が失敗するとほかの鍵にフォールバックされる
この2つが牙を剥く?のは、以下の条件が重なったとき。
-
ssh-agent
に鍵を登録したのを忘れたとき - 公開鍵を削除するのが手間なとき
知らずのうちに削除したはずの鍵を使っていた、解決のために前の公開鍵消したら何か知らんけど動いた、ということになる。
公開鍵を削除するのが手間なとき
これが実現する可能性があるのが、GitHubのアカウント移行になる。
新しくGitHubアカウントを作ったはいいものの、SSH接続しようとしたら前のアカウントで接続され.る、といったことは起こりうる...と思う。
前のアカウントをそれなりに使っていたら、ssh-agent
に鍵を登録したことを忘れている可能性は十分にある。
そして、前のアカウントからわざわざ公開鍵を消すのか?と言われたら、面倒 or 存在を忘れていて消さないことも多いはず。
...といってもその事例に遭遇したことはないんだけど。
おまけ
どのGitHubアカウントが使われているか知りたかったら: SSHしてみよう by ChatGPT
ssh -T git@github.com
どの鍵でSSH接続しているか知りたかったら:-v
オプション
ssh -v nanasi@localhost