← シリーズに戻る

ssh -J と config の差を踏み台で実測

Part 4 / 6 SSH接続の5方式を7軸比較(4方式実測)

踏み台越しの接続は ssh -J で書けます。ただし同じ鍵で踏み台に直接入れる状態でも、-J 経由だと踏み台側で拒否されます。

原因は権限でも鍵の中身でもありません。-i が踏み台への接続に渡らないからで、~/.ssh/config に踏み台の Host エントリを書くと同じ鍵のまま通ります。実測で並べます。

踏み台が本当に必要な状態を作る

クライアントから送信先へ直接は届かず、踏み台からだけ届くネットワークを用意しました。

client  ──→ bastion      届く
client  ──→ target       届かない (名前解決もできない)
bastion ──→ target       届く

直接つなごうとすると、ここで止まります。

$ ssh -i .ssh/lab deploy@lab-target
ssh: Could not resolve hostname lab-target: Temporary failure in name resolution

client、bastion、target の3台の到達性の図。client から bastion へ緑の矢印で「届く」、bastion から target へも「届く」。client から target へは赤い点線が伸びて途中にバツ印があり「届かない (名前解決もできない)」。target は送信先の内部ネットワーク (クライアントからは見えない) の中にあり、bastion はその境目に立っている。下に ssh -i .ssh/lab deploy@lab-target の実行結果 Could not resolve hostname lab-target

同じ鍵なのに -J だと拒否される

まず、踏み台へ直接入れることを確かめます。ここが通らないまま先に進むと、あとで出る拒否が踏み台のせいなのか経路のせいなのか分からなくなります。

$ ssh -i .ssh/lab -o BatchMode=yes deploy@lab-bastion 'whoami; hostname'
入れる: deploy@73507272c769

この鍵で踏み台に入れています。同じ鍵を -J に使うと、こうなります。

$ ssh -i .ssh/lab -o BatchMode=yes -J deploy@lab-bastion deploy@lab-target true
deploy@lab-bastion: Permission denied (publickey,password).
Connection closed by UNKNOWN port 65535

拒否しているのは lab-bastion です。鍵は同じ、権限も同じ、直前に手で入れたばかりなのに拒否されます。

踏み台側にも同じ鍵を明示すると通ります。

ssh -i .ssh/lab -o BatchMode=yes \
  -o "ProxyCommand=ssh -i /root/.ssh/lab -o BatchMode=yes -W %h:%p deploy@lab-bastion" \
  deploy@lab-target 'whoami; hostname'
通った: deploy@3b66cc2d143e

つまり -i は最終的な接続先に対する指定で、踏み台への接続には適用されていません。踏み台への接続はもう一段内側で別プロセスとして張られていて、そこで使われるのは既定の場所にある鍵 (~/.ssh/id_ed25519 など)、ssh-agent に登録済みの鍵、~/.ssh/config の Host * などで踏み台にも当たる IdentityFile です。鍵を既定以外の名前で置いていて、agent にも config にも載せていない環境では、ここで落ちます。

config に書くと両方に効く

~/.ssh/config は踏み台にも Host エントリを与えられます。これが -J との決定的な差です。

Host bastion
    HostName lab-bastion
    User deploy
    IdentityFile ~/.ssh/lab

Host target
    HostName lab-target
    User deploy
    IdentityFile ~/.ssh/lab
    ProxyJump bastion

ProxyJump bastion の bastion は、ホスト名ではなく上で定義した Host ブロックの名前です。踏み台への接続もこの設定を読むので、鍵が渡ります。

$ ssh target 'whoami; hostname'
ssh target だけで着いた: deploy@3b66cc2d143e

-J と config の差を2段で比べた図。上段は ssh -i .ssh/lab -J deploy@lab-bastion deploy@lab-target で、client から bastion への内側の接続が赤くバツ印で止まり、bastion が赤く、target は点線で届いていない。client から target への最終的な接続には「-i はここにだけ効く」と書かれている。説明は「内側の接続で使われるのは、既定の場所の鍵、ssh-agent の鍵、Host * などの IdentityFile。-i .ssh/lab は渡らない」で、出力は deploy@lab-bastion: Permission denied (publickey,password). と Connection closed by UNKNOWN port 65535。下段は ssh target (~/.ssh/config に Host bastion と Host target) で、内側の接続も最終的な接続も緑でつながり、「内側の接続も Host bastion のブロックを読むので、IdentityFile ~/.ssh/lab が渡る」、出力は ssh target だけで着いた: deploy@3b66cc2d143e。最下部に「-J でも、既定の場所・agent・Host * の IdentityFile のどれかに鍵があれば通る。落ちるのは、鍵を既定以外の名前で置き、agent にも config にも載せていない環境」

(-J の内側の接続は、自分の代わりに踏み台へ行く代理人のようなもので、-i で渡した合鍵はその代理人の手には渡っていません。config に書くと、代理人も Host bastion の鍵を持って出かけます)

~/.ssh/config の権限も見られます。他人が書き込める権限だと、ssh は Bad owner or permissions on ~/.ssh/config と出して使用を拒否します。OpenSSH 10.0p2 (Debian) のコンテナで試すと、600・644・640・604 は通り、646・666 と、所有者が root 以外の別の利用者になっているファイルは拒否されました。グループが書き込める 664・660 は、ファイルのグループに自分以外の利用者が居ると拒否され、本人だけのグループ (useradd が作る既定のグループや root) なら通りました。後者は Debian 系の追加パッチによる緩和で、本家の OpenSSH (Alpine の同じ版で確認) はグループが書き込めるだけで拒否します。読めるだけでは拒否されないので、SSHの鍵認証でパスワードを消す4手順の章で踏んだ秘密鍵のエラー (644 で拒否) とは基準が違います。迷ったら 600 にしておけば、どちらにも引っかかりません。

/.ssh/config と秘密鍵の権限ごとの結果を並べた表の図。列は 600 (本人だけ)、644・640・604 (グループと他人は読めるだけ)、664・660 (グループが書ける)、646・666 (他人が書ける)。/.ssh/config (ssh -G で確認) は 600・644・640・604 が通る、664・660 は条件つき (グループに自分以外が居ると拒否)、646・666 は拒否。秘密鍵 (ssh-keygen -y で確認) は 600 だけ通り、644 から 666 まで残り7つは全部拒否。~/.ssh/config は他人が書けると拒否 (Bad owner or permissions、所有者が root 以外の他人でも拒否)、秘密鍵は自分以外が読めるだけで拒否 (UNPROTECTED PRIVATE KEY FILE、2章)。下に「迷ったら 600。どちらにも引っかからない。条件つきの 664・660 は Debian 系の緩和で、グループが本人だけ (useradd の既定のグループや root) なら通る。本家の OpenSSH (Alpine の 10.0p2 で確認) はグループが書けるだけで拒否する。表は OpenSSH 10.0p2 (Debian) のコンテナでの結果」

ファイル転送も同じ設定に乗る

scp も rsync も同じ ~/.ssh/config を読むので、踏み台の指定を書き足す必要はありません。

$ scp payload.bin target:~/
  向こうに着いたファイル: 100000 バイト

$ rsync -a payload.bin target:~/payload2.bin
  rsync も通った

経路の定義が1箇所に集まりました。踏み台が変わったときに直すのも1箇所です。

効いている設定を目で確かめる

~/.ssh/config は先に一致した Host ブロックが勝ちます。Host * を上のほうに書くと、そこで決めた項目は後ろの個別の Host ブロックで上書きできません。ファイルを読むより ssh -G で展開させるほうが確実です。

$ ssh -G target | grep -E '^(hostname|user|identityfile|proxyjump) '
user deploy
hostname lab-target
identityfile ~/.ssh/lab
proxyjump bastion

接続はせずに、その名前に最終的に適用される設定だけを出します。設定を直したのに挙動が変わらないときは、まずここを見ます。

7軸で見た踏み台

pillar のマトリクスでは、踏み台経由 (C) の失効と監査を ○ (条件付き) にしています。根拠はこの2つで、どちらも仕組みからの評価です。ラボでは人を外す操作も、ログの集め方も試していません。

  • 失効 — 奥の機械へ踏み台からしか届かない構成なら、人を外すときは踏み台の authorized_keys からその人の鍵を消せば入口が閉じます。直す場所が1箇所で済む点は公開鍵認証だけの構成より良く、条件は2つあります。奥の機械の authorized_keys には鍵が残るので、踏み台を通らない経路ができた時点で効かなくなることと、消す作業そのものは手作業のままであることです
  • 監査 — 誰がどの鍵で入ったかは、踏み台の sshd のログ (Accepted publickey for ... の行) に集まります。奥の機械で何をしたかまでは踏み台に残りません

踏み台の失効と監査が ○ 止まりな理由の図。外した人 (鍵はまだ手元にある) から踏み台への矢印にバツ印があり「入れない」。踏み台の authorized_keys からその人の鍵を消すと入口が閉じる。踏み台の奥の機械 1 と機械 2 (踏み台からしか届かない) の authorized_keys には鍵が残ったまま (赤)。赤い点線の「踏み台を通らない経路」ができた時点で、残った鍵で入れる。踏み台には Accepted publickey for ... のログ。下の2つの囲みは、失効: 直す場所は踏み台の1箇所で公開鍵認証だけの構成より良いが、条件が2つ (奥の機械に鍵が残るので踏み台を通らない経路で効かなくなる / 消す作業そのものは手作業のまま)。監査: 誰がどの鍵で入ったかは踏み台の sshd のログに集まるが、奥の機械で何をしたかまでは踏み台に残らない。最下部に「どちらも仕組みからの評価。ラボでは人を外す操作も、ログの集め方も試していない」

この章の結論

踏み台は ~/.ssh/config で組みます。-J は -i の鍵を踏み台側に渡さないので、鍵を既定以外の名前で置き、agent にも載せていない環境では通らず、しかもそのときのエラーがホスト名もポートも示さない Connection closed by UNKNOWN port 65535 なので、原因が接続先にあるように見えてしまいます。config に書けば、ssh も scp も rsync も同じ経路に乗ります。

ここまでで、到達できる相手に対する設定は揃いました。次の章ではそもそも届かない相手を扱います。