ssh -J と config の差を踏み台で実測
踏み台越しの接続は 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

同じ鍵なのに -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 の内側の接続は、自分の代わりに踏み台へ行く代理人のようなもので、-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 にしておけば、どちらにも引っかかりません。

ファイル転送も同じ設定に乗る
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 ...の行) に集まります。奥の機械で何をしたかまでは踏み台に残りません

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