作弊条:SSH 的 ProxyJump 跳板服务
问题
有些环境中,SSH 服务器可能无法从 Internet 直接访问(例如,SSH 服务器可能使用的是一个私有 IP 地址,或是 Internet 服务提供商没有提供 IPv6 服务,而 SSH 服务器只提供 IPv6 服务)。
考虑到 SSH 已经进行了相互认证(连接时客户端会验证服务器的公钥是否与已知公钥,例如 ~/.ssh/known_hosts
,
或是通过 DNSsec 发布的 SSHFP
RR 匹配;服务器端则会验证用户是否能证明自己拥有与授权公钥对应的私钥),
因此比较常见的解决方法便是使用 VPN、在防火墙上穿孔,或是使用代理服务器。
由于 SSH 自身也提供了许多转发功能,因此如果中间的跳板服务器也提供 SSH 服务, 便可以使用这些跳转服务器直接作为代理服务器来用。与前面那些传统方法相比, 这样做的优点是避免了安装额外的软件,也不需要特别指定端口。
ssh -W (“netcat mode”)
OpenSSH 提供了一个 -W
选项。该选项的作用是让 SSH 服务器打开到某一特定 IP/端口 的 TCP 连接,
并将标准输入输出与该连接绑定,这与 nc(1) 类似。
此功能最早在 2010 年由 OpenSSH 5.4 引入。
传统上,OpenSSH 支持一个名为 ProxyCommand
的选项,该选项可以让系统在连接时在本地运行一个命令。
故而,我们可以把 ProxyCommand
写作 ssh -W %h:%p username@<跳板服务器>
来告诉 ssh(1)
在连接服务器时,首先连接 username@<跳板服务器>
并用其作为代理服务器来提供服务,如下图所示。
+-------+ +-------+ +--------------------+
| 客户机 |------>| 跳板机 |------>| 另一网络上的SSH服务器 |
+-------+ +-------+ +--------------------+
不过,对于比较复杂的网络来说,有可能需要使用多层跳板。ProxyCommand
仍然可以满足此需求,
但写起来会复杂不少。考虑到使用 ssh -W
是一种常见情形,在 OpenSSH 7.5 中引入了一个新的选项
ProxyJump
,它可以直接指定多个跳板机,并在每一跳使用不同的用户名/端口来进行连接。
由于该功能大致上时 ProxyCommand
的子集,因此它和 ProxyCommand
同时在配置中出现时,
只有后出现的那一个生效。
根据条件选择是否使用跳板
使用跳板机可能并非在所有情况下都适合,例如如果跳板机完成的功能是接受 IPv4 的 SSH 连接请求, 并提供到 IPv6 网络的连接服务,那么在一个有 IPv6 服务的地方可能就无所谓是否使用跳板机了。
OpenSSH 的 Host
配置只支持做模式匹配(例如: *.example.com
)而无法灵活地判断当前网络环境。
自然,我们可以使用脚本去让系统使用不同的 ~/.ssh/config
文件,但这种外科手术式的做法并不美观。
OpenSSH 还提供了一个 Match
配置,除了模式匹配之外,
它还支持使用一个可执行文件来判断当前主机是否匹配,这样就可以做更加灵活的判断了。
下面是一个简单的此类脚本,其原理是通过调用某个支持 GET
方法的 API 来判断是否可以直接连通内网并据此返回是否使用跳板的结果:
#!/bin/sh
HASH=$(fetch -T 1 -qo - http://api.internal.example.com/reachable 2>/dev/null | sha512)
if [ ${HASH} = "fa706818d7cb88e278eed38b528269eeffce175ccb118fc9665b547e4742758b6d1da65786da56a5cc755b1f86789d89e2e7ff31b228b111420d9424252a74c1" ]; then
# 签名匹配:网络可直达,返回 1 表示不应使用跳板
exit 1
else
# 其他情况:网络不可达,或者 API 在 1 秒内无响应,使用跳板
exit 0
fi
或者,下面的脚本可以判断是否能够连上 IPv6 网络:
#!/bin/sh
# 解析即将连接的域名的AAAA记录
AAAA_COUNT=$(dig "$1" AAAA +short | wc -l)
# 如果没有IPv6地址,则无需使用跳板
[ 0 = "${AAAA_COUNT}" ] && exit 1
# 如果访问 Google 的 generate_204 超时或出错,则表示很可能 IPv6 不通,需要使用代理
# 注意此做法假定使用的是正常的互联网,如果不是请换成其他可用的 API。
curl -6 --connect-timeout 0.5 https://www.gstatic.com/generate_204 > /dev/null 2>&1 || exit 0
# 反之,直接连接
exit 1
与之对应的 ssh_config
或 ~/.ssh/config
配置如下:
Match host 10.*,*.internal.example.com exec "/usr/local/bin/need_proxy %h"
ProxyJump trampoline.example.com
其中 trampoline.example.com
是一台在 Internet 上可达的 SSH 服务器作为跳板。
第一个脚本不需要 %h
参数,其他可以使用的参数请参见 ssh_config(5)。