SSH ProxyJump:经由跳板机登录内网服务器

有些服务器不能从办公电脑直接访问,但可以先登录一台跳板机,再由跳板机访问内网目标服务器。

一种做法是先登录跳板机,再在跳板机上执行第二次 ssh。SSH 本身支持这种连接路径,使用 -JProxyJump)就能把两次连接写成一条命令。

假设网络关系如下:

当前电脑
    └── SSH ──> 跳板机 192.0.2.10:22022
                    └── SSH ──> 目标服务器 198.51.100.20:22

本文中的账号和地址都是脱敏示例:跳板机使用 jump-user@192.0.2.10:22022,目标服务器使用 target-user@198.51.100.20。请替换为实际环境的值。

假设当前电脑不能直接访问 198.51.100.20,但跳板机可以访问它。连接目标服务器只需要:

ssh -J jump-user@192.0.2.10:22022 target-user@198.51.100.20

-J 到底做了什么

命令里出现两个 SSH 地址:

ssh -J jump-user@192.0.2.10:22022 \
    target-user@198.51.100.20

它们的角色不同:

  • -J jump-user@192.0.2.10:22022:先连接跳板机,用户名是 jump-user,SSH 端口是 22022
  • target-user@198.51.100.20:最终要登录的目标服务器,未指定端口时使用默认的 22

-J 的格式是:

-J [跳板机用户@]跳板机地址[:跳板机端口]

如果目标服务器的 SSH 端口不是 22,使用 -p 指定目标端口:

ssh -J jump-user@192.0.2.10:22022 \
    -p 2222 \
    target-user@198.51.100.20

这里的 -p 2222 属于目标服务器;跳板机的端口仍然写在 -J 后面。

连接过程

执行命令时,本地 SSH 客户端会先处理跳板机连接:

  1. 先使用本地的密钥或密码认证跳板机
  2. 请求跳板机上的 sshd 建立一条到 198.51.100.20:22 的 TCP 通道

随后,本地 SSH 客户端通过这条通道和目标服务器完成真正的 SSH 握手与认证。

连接路径如下:

本地 ssh 客户端
    │
    │ 连接跳板机 192.0.2.10:22022
    ▼
跳板机上的 sshd
    │
    │ 连接 198.51.100.20:22
    ▼
目标服务器上的 sshd

跳板机只转发 TCP 字节流,交互式 shell 不会在跳板机上启动。目标服务器的私钥认证仍由当前电脑完成,目标私钥无需复制到跳板机。

它和下面这种手动操作的区别在于:

ssh -p 22022 jump-user@192.0.2.10
ssh target-user@198.51.100.20

手动方式会先进入跳板机,再从那里发起第二次连接。使用 -J 时,跳板机只作为目标连接的代理通道。

两台机器使用不同的账号和密钥

跳板机和目标服务器可以使用不同的账号和密钥。

例如跳板机使用 jump-key,目标服务器使用 target-key。可以把配置写入 ~/.ssh/config,分别为两个主机设置认证信息,让两台机器的配置彼此独立。

Host my-jump
    HostName 192.0.2.10
    User jump-user
    Port 22022
    IdentityFile ~/.ssh/jump-key

Host my-target
    HostName 198.51.100.20
    User target-user
    IdentityFile ~/.ssh/target-key
    ProxyJump my-jump

之后直接执行:

ssh my-target

这样,目标服务器地址、跳板机端口和密钥路径只需要维护一次。后续执行 scpsftp 或其他 SSH 工具时也能复用这份配置。

例如使用 scp

scp ./example.txt my-target:/tmp/

多级跳板机

如果目标服务器前面还有多层网络隔离,可以在 -J 中用逗号分隔多个跳板机:

ssh -J user1@jump-1:22022,user2@jump-2:22 \
    user3@198.51.100.20

连接顺序是:

当前电脑 -> jump-1 -> jump-2 -> 198.51.100.20

也可以写成配置:

Host my-target
    HostName 198.51.100.20
    User user3
    ProxyJump user1@jump-1:22022,user2@jump-2:22

每增加一层跳板,排查网络问题就多一层依赖。实际使用时,链路能短就不要再加中间节点。

连接失败时怎么排查

先确认跳板机本身可以登录:

ssh -p 22022 jump-user@192.0.2.10

再从跳板机检查它能否访问目标服务器的 SSH 端口:

nc -vz 198.51.100.20 22

如果跳板机没有 nc,也可以直接在跳板机上尝试:

ssh -v target-user@198.51.100.20

最后在本地打开详细日志:

ssh -vv \
    -J jump-user@192.0.2.10:22022 \
    target-user@198.51.100.20

日志通常能看出问题位于哪一段:

  • 跳板机认证失败:检查跳板机用户名、端口和密钥
  • 跳板机无法连接目标端口:检查目标服务器路由、防火墙和 sshd
  • 目标服务器认证失败:检查目标服务器的用户、密钥和 authorized_keys
  • 出现 host key 警告:确认目标 IP 没有被重新分配给另一台机器

目标 SSH 连接仍由当前电脑端到端建立。因此第一次连接目标服务器时,当前电脑仍然可能看到目标服务器的 host key 确认提示。

和端口转发有什么区别

-J 解决的是“登录目标服务器”:

本地电脑 -> 跳板机 -> 目标服务器

登录成功后,得到的是目标服务器上的 shell。

如果要访问目标服务器上的 HTTP、数据库或其他 TCP 服务,又不需要登录 shell,可以使用 -L 本地端口转发,把本地程序接到目标服务:

本地程序 -> 本地端口 -> SSH 隧道 -> 目标服务器上的服务

两者可以同时使用:-J 负责“怎么到达目标服务器”,-L 负责“到达目标服务器后再转发哪个服务端口”。

安全边界

ProxyJump 只改变到达目标服务器的网络路径,目标服务器仍然会独立校验用户和密钥。

部署时检查:

  • 跳板机必须允许连接目标服务器的 SSH 端口
  • 目标服务器的 host key 应该在当前电脑上验证并保存
  • 尽量使用密钥认证,目标服务器私钥不要放到跳板机
  • 只给跳板机开放必要的目标网段和端口
  • 把跳板机限制在明确的目标范围内,不要把它当成通用代理

将“必须经过某台机器才能到达”的路径写进 SSH 命令或配置文件后,sshscpsftp 都可以复用这套设置。