Closed32

DockerコンテナにSSHして遊ぼう & ssh-agentの罠?

nanasinanasi

とある検証のため、SSHできる環境が欲しくなった。
ということで、Dockerを使ってお手軽にSSHサーバーを建てていく。

やること:

  1. SSHするためのサーバーをDockerコンテナで作る
  2. ssh-agentのキャッシュによる罠について検証する
nanasinanasi

メモ置き場

  • PUBLIC_KEY環境変数で複数の公開鍵を設定する方法がわからん
    • \nで改行する: ダメだった
    • ashとかいうのが関係してる?
    • 結論: ボリュームでマウントしてファイル指定しよう
  • -iで指定された鍵よりssh-agentの鍵が優先される
  • -o IdentitiesOnly=yesを指定するとssh-agentは無効になる by ChatGPT
nanasinanasi

1. サーバーを立てる

SSHするためのサーバーをDockerコンテナで作る。

方針

  • linuxserver/openssh-serverというDockerイメージを使用
  • パスワード認証は使わず、公開鍵認証のみを使う
  • ユーザーnanasilocalhost:22にあるサーバーに接続する
nanasinanasi

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.

nanasinanasi

環境変数での設定

linuxserver/openssh-serverでは、コンテナ起動時に独自の環境変数を設定できる。
今回は要件的に環境変数で簡潔できるので、コンテナの起動コマンドで設定を終わらせる。

今回使う環境変数:

  • PUBLIC_KEY="略": 公開鍵認証に使う公開鍵
  • LOG_STDOUT=true: ログを標準出力にも出力する、検証で使う
  • USER_NAME=nanasi: 環境にnanasiというユーザーを作成し、SSHで接続できるようにする
nanasinanasi

ユーザーを設定する理由

デフォルトだと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.

nanasinanasi

ポート

デフォルトだと2222にサーバーを立てる。
どこかで設定できるのかもしれないが、設定項目が見つからなかった...

ただもちろん、コンテナではなくホストのどこのポートにサーバーを立てるかは、ポートフォワードで設定できる。
同じみの-pフラグでフォワードできる。

ちなみに、何がどのようにフォワードされてるか知りたければ、docker portコマンドが使える。
使い方は簡単で、docker port コンテナ名で調べられる。
sshの結果がconnection refusedとなったら見る価値はあると思う。

nanasinanasi

やり方

コンテナを起動する

まずはコンテナを起動するためのコマンドを書く。
ターミナルで片付けると辛いので、それ用のファイルを作ったほうがいい。

例えばこんな感じ。

ssh-server
docker run -d \
  -p 22:2222 \ # ホストのポート:2222
  -e PUBLIC_KEY="後で公開鍵を入れる" \
  -e LOG_STDOUT=true \
  -e USER_NAME=nanasi \
  linuxserver/openssh-server

なお、このファイルはどこに置いてもいい。
というかどこのディレクトリで作業しても同じだと思う。

nanasinanasi

鍵ペアを生成する

次に鍵ペアを生成する。
ここではアルゴリズムに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
nanasinanasi

鍵が生成できたら、公開鍵をコンテナ起動コマンドに書く。

公開鍵は生成された鍵ファイルのうち.pubがついているほう。
おそらくpublicpubなのでわかりやすい。
以下のコマンドで中身を出力し、クリップボードにコピーする。

less ~/.ssh/id_ed25519.pub

そしてssh-serverに書く。

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
nanasinanasi

そしたら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
nanasinanasi

接続について

ここで一度現状確認。

  • 使用している秘密鍵: ~/.ssh/id_ed25519
    • 今回はsshコマンドに-iを使用していない
    • そのため~/.ssh内にある鍵がデフォルトで使われる
  • 接続があるとサーバーは標準出力にログを吐く
    • ここからどのアルゴリズムの鍵で接続されたかがわかる
    • 今回ならED25519と出力される
  • 22番=SSHのデフォルトのポートにサーバーがあるため、ポートを指定しなくても接続できる
  • ~/.ssh/known_hostsにはlocalhostのことが書かれている
    • コンテナを起動し直すとホスト鍵が変わってしまうため、公開鍵だかフィンガープリントが一致しなくなりエラーが出る
    • 対策: コンテナを起動するたびにknown_hostsを削除する
  • ちなみに使用OSはMac
nanasinanasi

対策: コンテナを起動するたびにknown_hostsを削除する

ということでちょっと修正。

ssh-server
rm ~/.ssh/known_hosts
docker run -d -p 22:2222 \
# 略
nanasinanasi

Docker Desktopのダッシュボード

以下の操作はダッシュボードで行う

  • サーバーログの閲覧
  • コンテナの削除
  • etc...

ダッシュボード、本当に直感的に操作できるからおすすめ。
ちょっとした実験くらい、みんなこっちに移行しようよ。

nanasinanasi

2. ssh-agentについて検証する

SSH接続ができる環境が整ったということで、さっそく検証。
内容はざっくり言うと、意図せずにssh-agentの秘密鍵で接続されること。

概要

  • ssh-agentは鍵をキャッシュする
  • MacOSではキーチェーンに鍵を追加できる=半永久的に鍵が保管される
    • 実際に使うには別で.bashrcなどでssh-addする必要あり
    • 検証では鍵が使える状態になっている想定
  • キーチェーンの鍵は~/.sshを消しても残る
  • ssh-iオプションでは秘密鍵を指定できる
    • 指定されなかった場合は~/.sshにある(デフォルト名の?)鍵で接続される
    • -issh-agentならssh-agentが優先

=> ~/.ssh/id_ed25519で接続してるつもりなのに、ssh-agentのRSA鍵で接続してる!?
...ということが起こりうるのでは?

nanasinanasi

検証手順

大きく3ステップに分かれている

  1. RSA鍵を追加:

    1. RSA鍵(Rとする)を用意し、ssh-agentを通してキーチェーンに追加する
    2. SSHサーバーに公開鍵R'を追加する
    3. ssh-add --apple-use-keychainssh-agentに秘密鍵Rを追加する
    4. 秘密鍵Rで接続を試み、成功する
  2. RSA秘密鍵を削除:

    1. 鍵ペアR~/.sshフォルダごと削除する
    2. ED25519鍵(E)を用意する
    3. SSHサーバーに公開鍵E'を追加する
    4. 接続を試みたとき、使われるのは秘密鍵R or E?<= 検証1
  3. RSA公開鍵を削除:

    1. 別のED25519鍵(E2)を用意する
    2. SSHサーバーから公開鍵R'を削除する
    3. SSHサーバーに公開鍵E2'を追加する(置き換える形)
    4. 接続を試みたとき、使われるのは秘密鍵R or E2? <= 検証2
nanasinanasi

実験前のリセット手順:

  • ~/.sshを消す
  • 起動しているSSHサーバーのDockerコンテナを削除する
  • ssh-add -Dssh-agentの鍵を全て削除する
nanasinanasi

(独自表記なのは気にしない)

  • 公開鍵: 鍵ペア名 + '
  • 秘密鍵: 鍵ペア名そのまま
nanasinanasi

公開鍵ファイル

検証には公開鍵を複数登録する必要があるので、PUBLIC_KEYではなくちゃんとしたファイルで管理したい。
そんなときは、ボリューム + PUBLIC_KEY_FILEを組み合わせる。

ssh-server
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
./config/authorized_keys
# これは公開鍵を入れるファイル
nanasinanasi

1. RSA鍵を追加

これは通常のSSH接続。
ただし、キーチェーンに鍵を追加する点に注意。

  1. ssh-keygen -t rsaで鍵ペアRを生成する
  2. ./config/authorized_keysに公開鍵R'を追加する
  3. ssh-add --apple-use-keychainssh-agentに秘密鍵Rを追加する
  4. ssh nanasi@localhostで接続を試みる => 成功
nanasinanasi

ssh-add

本当は鍵を半永久的に使える工夫がされていたが、今回は短期で終わらせるので省略。
実際の方法は以下:

ターミナル
ssh-add --apple-use-keychain
.bashrc or .zshrc
ssh-add --apple-load-keychain
nanasinanasi

2. 別のED25519鍵を追加

ここでは秘密鍵Rを削除し、それでもキーチェーンの鍵は有効か?を調べる。
結論から言うと有効なので、~/.sshを消しただけだと鍵は完全に消せていないことになる。

  1. rm -r ~/.sshで秘密鍵Rを削除する
  2. ssh-keygen -t ed25519で鍵ペアEを生成する
  3. ./config/authorized_keysに公開鍵E'を追加する
    • 公開鍵R'はそのまま
    • 各公開鍵の区切りは改行にする
  4. SSHサーバーのコンテナを作り直す
  5. ssh nanasi@localhostで接続を試みる => 秘密鍵Rが使われている
nanasinanasi

サーバーログ:

Accepted publickey for nanasi from 172.17.0.1 port 64044 ssh2: RSA ~略~

RSAって書いてあるはず

nanasinanasi

-iで秘密鍵Eを指定した場合:

ターミナル
ssh -i ~/.ssh/id_ed25519 nanasi@localhost 
サーバーログ
Accepted publickey for nanasi from 172.17.0.1 port 64206 ssh2: RSA SHA256:~略~

ssh-agentが優先される模様。

nanasinanasi

公開鍵R'を削除

サーバーから公開鍵R'を削除する。
かつ、あまり意味はないだろうが、新しいED25519鍵(E2)の再生成もする。

このときssh-agentの秘密鍵Rは使えなくなる。
では代わりに手元の使用可能である秘密鍵E2が使われるのか? = フォールバックはあるか?が今回の検証内容。

結論

結論から言うとフォールバックされる
しかもデバッグモード(-v)ではない場合、特にそれを示すようなログは出ない。

このため、

  • 実はssh-agentに秘密鍵Rがあった
  • 公開鍵R'が削除されたから秘密鍵E2が使われるようになった

という事実が発覚しにくい。

nanasinanasi
  1. rm -r ~/.sshで秘密鍵Eを削除する
    • id_ed25519以外にリネームでも可
  2. ssh-keygen -t ed25519で鍵ペアE2を生成する
  3. ./config/authorized_keysから公開鍵R'を削除する
  4. ./config/authorized_keysに公開鍵E2'を追加する(置き換える形)
  5. SSHサーバーのコンテナを作り直す
  6. ssh -v nanasi@localhostで接続を試みたとき、
    1. 秘密鍵Rでの接続が失敗する
    2. フォールバックとして秘密鍵E2が使用され、成功する
nanasinanasi

SSHサーバーのコンテナを作り直す

これは./config/authorized_keysの変更が自動で再適用されないから。
Dockerコンテナ内で変わっても、それが実際のauthorized_keysにまで反映されるとは限らない。

nanasinanasi

一度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:~略~

デバッグモードだとこういったログが出力される。
自分がどの秘密鍵で接続してるのか知りたいときに便利。

nanasinanasi

要約

  • 削除した鍵でもssh-agentに残っていることがある
  • ssh-agentの鍵での接続が失敗するとほかの鍵にフォールバックされる
nanasinanasi

この2つが牙を剥く?のは、以下の条件が重なったとき。

  • ssh-agentに鍵を登録したのを忘れたとき
  • 公開鍵を削除するのが手間なとき

知らずのうちに削除したはずの鍵を使っていた、解決のために前の公開鍵消したら何か知らんけど動いた、ということになる。

nanasinanasi

公開鍵を削除するのが手間なとき

これが実現する可能性があるのが、GitHubのアカウント移行になる。
新しくGitHubアカウントを作ったはいいものの、SSH接続しようとしたら前のアカウントで接続され.る、といったことは起こりうる...と思う。

前のアカウントをそれなりに使っていたら、ssh-agentに鍵を登録したことを忘れている可能性は十分にある。
そして、前のアカウントからわざわざ公開鍵を消すのか?と言われたら、面倒 or 存在を忘れていて消さないことも多いはず。

...といってもその事例に遭遇したことはないんだけど。

nanasinanasi

おまけ

どのGitHubアカウントが使われているか知りたかったら: SSHしてみよう by ChatGPT

ssh -T git@github.com

どの鍵でSSH接続しているか知りたかったら:-vオプション

ssh -v nanasi@localhost
このスクラップは22時間前にクローズされました