代理与跳板主机 - 通过一个或多个网关传递

代理是一个中介,负责将客户端的请求转发到其他服务器。性能提升、负载均衡、安全性或访问控制是使用代理的一些原因。

跳板主机 -- 通过一个或多个网关连接

可以通过一个或多个中介主机连接到另一个主机,使客户端能够表现得像是直接连接。主要的方法是使用 SSH 连接,通过一个或多个跳板主机转发 SSH 协议,使用 ProxyJump 指令连接到目标主机上运行的 SSH 服务器。这是最安全的方法,因为加密是端到端的。除了其他加密方式外,链条的端点相互加密和解密流量。因此,经过中间主机的流量始终是加密的。但是,如果中间主机拒绝端口转发,则无法使用这种方法。

使用 ProxyCommand 选项通过 Netcat 作为链条中的最后一跳是一个变种,适用于非常旧的客户端。此方法通过 nc 而不是 ssh 转发 SSH 协议。需要注意的是,链中每个主机的用户名是否会发生变化。过时的 netcat 方法不支持更改用户名,而其他方法可以。

当端口转发可用时,最简单的方法是在配置文件中使用 ProxyJump 或通过运行时参数 -J。例如:

$ ssh -J firewall.example.org:22 server2.example.org

ProxyJump 指令(-J)非常有用,下面有专门的小节介绍。

在旧版本中,-J 是不可用的。在这种情况下,最安全且最直接的方法是使用 ssh(1) 的 stdio 转发模式(-W)通过中间主机“跳转”连接。

$ ssh -o ProxyCommand="ssh -W %h:%p firewall.example.org" server2.example.org

这种方法支持端口转发,而无需额外的技巧。

使用 %h, %u 和其他扩展标记的注意事项

关于使用来自不可信远程系统的 %h、%u 和其他扩展标记的注意事项:在旧版 OpenSSH 中,使用无效的用户名或主机名可以通过扩展标记传递 shell 元字符。问题描述在 CVE-2023-51385 中,并在 OpenSSH 9.6 及以后的版本中有所缓解。它涉及到不受信任的代理字符串,例如可能来自不可靠的版本控制服务等地方。缓解措施是部分的,因为很难过滤所有可能与用户提供的命令相关的 shell 元字符。

连接通过多个网关

从 OpenSSH 7.3 开始(2016年8月发布),通过 ProxyJump 指令是传递一个或多个跳板主机的最简单方法。

Host server2
        HostName 192.168.5.38
        ProxyJump user1@jumphost1.example.org:22
        User fred

可以指定多个跳板主机,使用逗号分隔。主机将按顺序访问。

Host server3
        HostName 192.168.5.38
        ProxyJump user1@jumphost1.example.org:22,user2@jumphost2.example.org:2222
        User fred

当以运行时参数的方式使用时,也可以使用 -J 快捷方式。

$ ssh -J user1@jumphost1.example.org:22 fred@192.168.5.38

多个跳板主机可以按同样的方式链式连接。

$ ssh -J user1@jumphost1.example.org:22,user2@jumphost2.example.org:2222 fred@192.168.5.38

在同一主机配置中,不能同时使用 ProxyJump 和 ProxyCommand 指令。找到的第一个指令会被使用,另一个会被阻止。

经过一个或多个网关的传输(有多个路由表/路由域)

当经过一个跳板主机时,该主机有多个与不同的路由表相关的接口时,必须手动操作路由表。具体来说,这意味着需要回到旧的传输方法,依赖于 netcat 并使用 ProxyCommand,而不是 ProxyJump,以便在中间添加 route(8)。下面是一个例子,使用运行时参数传递到 rdomain 1

$ ssh -o ProxyCommand="ssh user1@jumphost.example.org route -T 1 exec nc %h %p" fred@server.example.org

可以通过将其添加到客户端配置文件 ssh_config(5) 来使该配置持久化:

Host jump jumphost.example.org
        HostName jumphost.example.org
        User user1
        IdentitiesOnly yes
        IdentityFile ~/.ssh/jump_key

Host server server.example.com
        HostName 10.100.200.44
        User fred
        IdentitiesOnly yes
        IdentityFile ~/.ssh/inside_key
        ProxyCommand ssh jump route -T 1 exec nc %h %p

通过这些设置,可以通过跳板主机直接连接到局域网中的主机,使用 ssh server 即可,所有设置将生效。

否则,推荐的方式是使用 ProxyJump。有关使用旧版 ProxyCommand 指令的更多信息,请参见下文“ProxyCommand 与 Netcat”部分。

条件使用跳板主机

可以使用 Match exec 来选择复杂的模式,或者做出复杂的决策,例如根据客户端连接的网络类型或可用网络的连接状态来自动决定是否使用 ProxyJump 通过中介主机连接。以下是两个例子,说明如何在自动选择是否通过中介主机连接时使用 ProxyJump。第一个例子是当连接到只支持 IPv6 的远程机器时,如果没有本地的 IPv6 连接,就会使用中介主机。第二个例子则是在目标机器位于另一个网络时的情况。

示例 1:没有本地 IPv6 连接时连接至仅支持 IPv6 的远程机器

Match host ipv6only.example.org
        User fred

Match host ipv6only.example.org !exec "route -n get -inet6 %h"
        ProxyJump dualstack.example.org

使用这些设置,只有在无法直接通过 IPv6 连接到 ipv6only.example.org 时,才会通过跳板主机进行连接。

示例 2:目标机器位于另一个网络时

Match host ipv6only.example.org
        User fred

Match host ipv6only.example.org !exec "/sbin/ip route get $(host -t AAAA %h | sed 's/^.* //')"
        ProxyJump dualstack.example.org

这些脚本会使用 $SHELL 环境变量指定的 shell 来执行。请注意,配置指令会根据第一个匹配的规则应用,因此特定规则应放在文件的顶部,而更通用的规则放在底部。

使用 Netcat 检查连接

另一种方法是使用 nc(1) 工具直接检查目标主机的端口连接性。

Match !host jumphost.example.org !exec "nc -z -w 1 %h %p"
        ProxyJump jumphost.example.org

由于上述方法假设只使用一个跳板主机,因此可以结合其他 Match 条件来提高精度。

Match host 192.168.1.* !host jumphostone.example.org !exec "nc -z -w 1 %h %p"
        ProxyJump jumphostone.example.org

Match host 192.168.2.* !host jumphosttwo.example.org !exec "nc -z -w 1 %h %p"
        ProxyJump jumphosttwo.example.org

此配置将会捕获所有连接到 192.168.1.* 网络的流量,如果没有直接连接,将通过第一个跳板主机转发。同样,如果没有直接连接 192.168.2.* 网络的目标,也会通过第二个跳板主机转发。但如果存在直接连接,客户端将跳过跳板主机,直接连接。

使用 Canonical 主机名(位于跳板主机后)

一些局域网(LAN)使用自己的内部域名服务为其主机分配主机名。这些主机名对外部系统不可访问,因此无法直接使用 -JProxyJump 选项,因为客户端无法在跳板主机的另一侧查找 LAN 上的主机名。然而,跳板主机本身可以进行这些查找,因此可以使用 ProxyCommand 选项,在跳板主机上调用 SSH 客户端,利用其能力查找 LAN 上的主机名。

以下是一个例子,展示了内网主机缺少外部 DNS 记录的情况,然后通过跳板主机使用跳板主机的 SSH 客户端连接到内网主机,使用 -W 选项。

$ host fuloong04.localnet.lan
Host fuloong04.localnet.lan not found: 3(NXDOMAIN)

$ host fuloong04
Host fuloong04 not found: 2(SERVFAIL)

$ ssh -o ProxyCommand="ssh -W fuloong04.localnet.lan:22 jumphost.example.org" fred@fuloong04
fred@jumphost.example.org's password: 
fred@fuloong04's password: 
Last login: Sun May 24 04:06:21 2020 from 203.0.113.131
OpenBSD 6.7-current (GENERIC) #44: Sun May 24 04:06:31 MDT 2020

$ host fuloong04.localnet.lan
fuloong04.localnet.lan has address 10.10.10.193

-W 选项确保连接通过安全通道转发,并且仅通过跳板主机传递,而不会被解密。跳板主机必须能够进行 DNS 查找,并且需要有 SSH 客户端可用。

连接过程中的主机密钥管理

在初始连接过程中,客户端会收集主机密钥并进行询问:

$ ssh -o ProxyCommand="ssh -W fuloong04.localnet.lan:22 jumphost.example.org" fred@fuloong04
fred@jumphost.example.org's password: 
The authenticity of host 'fuloong04 (<no hostip for proxy command>)' can't be established.
ECDSA key fingerprint is SHA256:TGH6fbXRswaP7iR1OBrJuhJ+c1JiEvvc2GJ2krqaJy4.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'fuloong04' (ECDSA) to the list of known hosts.
fred@fuloong04's password: 
Last login: Thu May  7 21:06:18 2020 from 203.0.113.131
OpenBSD 6.7-current (GENERIC) #44: Sun May 24 04:06:21 MDT 2020

在初次连接之后,主机密钥将会保存到 known_hosts 文件中,并与目标主机名关联,而不是 ProxyCommand 中指定的主机名:

$ ssh-keygen -F fuloong04
# Host fuloong04 found: line 55 
fuloong04 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKwZOXY7wP1lCvlv5YC64QvWPxSrhCa0Dn+pK4b97jjeUqGy/wII5zCnuv6WZKCEjSQnKFP+8K9cmSGnIvUisPg= 

$ ssh-keygen -F fuloong04.localnet.lan

目标主机名仅用于识别要期望的主机密钥,与通过 ProxyCommand 选项传递的实际主机名无关,并且可以是随机字符串。

旧版跳板主机方法

关于旧方法的说明:这些旧方法已被弃用,因为 OpenSSH 的较新版本提供了更简单的方式来通过跳板主机进行连接。请参见上述“通过一个或多个网关传递使用 ProxyJump”部分。然而,某些长期支持版本的 GNU/Linux 发行版可能故意保留非常旧的 OpenSSH 版本。因此,尽管这些方法不常见,但仍有可能在未来几年内需要使用它们。

在旧版本的 OpenSSH(特别是版本 7.2 及更早版本)中,通过一个或多个网关进行连接更加复杂,需要使用标准输入输出转发(stdio forwarding)或在 5.4 之前使用 netcat(1) 工具。

旧方法:使用 stdio 转发(Netcat 模式)通过网关

在 OpenSSH 5.4 到 7.2 版本之间,可以使用“netcat 模式”将客户端的标准输入输出连接到服务器上转发的单一端口。这也可以用于使用 ssh(1) 连接,但它需要 ProxyCommand 选项,无论是作为运行时参数还是作为 ~/.ssh/config 文件的一部分。不过,旧版本中不再需要在中介主机上安装 netcat(1)。以下是使用它作为运行时参数的示例:

$ ssh -o ProxyCommand="ssh -W %h:%p jumphost.example.org" server.example.org

在这个示例中,认证将会发生两次,首先是在跳板主机上,然后是目标主机上,最终会启动一个 shell。

如果网关在配置文件中定义,语法是一样的。ssh(1) 会从配置文件中扩展出网关和目标的完整名称。以下配置允许通过输入 ssh server 来连接目标主机:

Host server
        Hostname server.example.org
        ProxyCommand ssh -W %h:%p jumphost.example.org

同样,这也可以应用于 SFTP。以下配置文件可以通过输入 sftp sftpserver 来连接目标 SFTP 服务器,并且配置文件会处理其他部分。如果目标主机密钥出现混乱,则需要通过 HostKeyAlias 明确指定用于识别目标系统的密钥:

Host sftpserver
        HostName sftpserver.example.org
        HostKeyAlias sftpserver.example.org
        ProxyCommand ssh -W %h:%p jumphost.example.org

你也可以将网关的密钥添加到正在运行的 ssh-agent 中,或者直接在配置文件中指定。选项 User 用于指定目标主机的用户名。如果目标主机和本地主机的用户名相同,则无需使用该选项。如果在跳板主机上使用不同的用户名,则可以在 ProxyCommand 选项中使用 -l 选项。例如,假设本地机器的用户名是 'fred',它通过用户名 'fred2' 登录跳板主机,然后通过 'fred3' 登录目标服务器:

Host server
        HostName server.example.org
        User fred3
        ProxyCommand ssh -l fred2 -i /home/fred/.ssh/rsa_key -W %h:%p jumphost.example.org

如果跳板主机和目标主机都使用密钥,则可以在配置中使用 IdentityFile 选项来指定网关的私钥,并在命令行中指定目标主机的私钥:

Host jump
        HostName server.example.org
        IdentityFile /home/fred/.ssh/rsa_key_2
        ProxyCommand ssh -i /home/fred/.ssh/rsa_key -W %h:%p jumphost.example.org

旧方法(OpenSSH 5.4 之前)使用的是 netcat,nc(1)

Host server
        Hostname server.example.org
        ProxyCommand ssh jumphost.example.org nc %h %p

但是,这种方法现在应该不再使用,应当改为使用 -W 提供的 netcat 模式。新的方法完全不需要在任何机器上安装 netcat。

旧方法:使用 stdio 转发递归链接网关

要通过多个跳板主机连接,最简单的方法是使用从 OpenSSH 7.3 开始提供的 ProxyJump。对于旧版本,如果路由始终使用相同的主机,并且顺序不变,那么可以在配置文件中创建一个简单的链。下面的示例展示了如何将三个主机链在一起,目标主机通过快捷方式 machine3 连接。

Host machine1
        Hostname server.example.org
        User fred
        IdentityFile /home/fred/.ssh/machine1_e25519
        Port 2222

Host machine2
        Hostname 192.168.15.21
        User fred
        IdentityFile /home/fred/.ssh/machine2_e25519
        Port 2222
        ProxyCommand ssh -W %h:%p machine1

Host machine3
        Hostname 10.42.0.144
        User fred
        IdentityFile /home/fred/.ssh/machine3_e25519
        Port 2222
        ProxyCommand ssh -W %h:%p machine2

通过这种方式,可以通过输入 ssh machine3 来访问最终的目标主机,所有设置将会被使用,包括端口转发和其他功能。

每个 Host 指令只需要目标主机名和(对于第二个及后续主机)ProxyCommand。如果没有使用密钥,则不需要 IdentityFile。如果所有主机使用相同的用户名,则可以省略 User 选项。如果端口是默认端口,则可以省略 Port。如果同时使用多个密钥,并且客户端遇到“too many authentication”错误,可能需要为每个主机的配置添加 IdentitiesOnly yes

旧方法:递归地链接任意数量的主机

再次强调,当 OpenSSH 7.3 可用时,推荐使用 ProxyJump。如果是旧版本,可以使配置更加抽象,允许通过任意数量的网关进行传递。可以使用 -l 选项设置用户名,但该用户名将应用于所有连接的主机。

Host */* 
        ProxyCommand ssh %r@$(dirname %h) -W $(basename %h):%p

在这种方式下,主机名通过斜杠(/)分隔,并且可以是任意数量的:

$ ssh host1/host2/host3/host4

使用斜杠作为分隔符会有一些限制,但它允许使用 dirname(1)basename(1) 来处理主机名。

使用 sed 配置不同的端口号和用户名

以下配置使用 sed(1) 允许通过使用加号(+)作为主机的分隔符,冒号(:)作为端口的分隔符,百分号(%)作为用户名的分隔符,来设置不同的端口号和用户名。基本结构是 ssh -W $() $(),其中 %h 被替换为目标主机名。

Host *+*
        ProxyCommand ssh -W $(echo %h | sed 's/^.*+//;s/^\([^:]*$\)/\1:22/') $(echo %h | sed 's/+[^+]*$//;s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/;s/:\([^:+]*\)$/ -p \1/')

端口可以省略,默认为 22,或者通过冒号(:)分隔符指定非标准端口。

$ ssh host1+host2:2022+host3:2224

如果使用的是标准端口,则上面的配置会在 SFTP 上正常工作。如果需要在非标准端口上使用 SFTP,则可以通过配置其他分隔符(例如下划线 _)。

对于给定主机,可以使用指定的分隔符(在此为百分号 %)指定除最后一个外的任何用户名。目标主机的用户名通过 -l 来指定,其他用户名可以与相应的主机名通过分隔符连接。

$ ssh -l user3 user1%host1+user2%host2+host3

如果指定了用户名,根据分隔符的不同,ssh(1) 可能无法将最终主机匹配到 IP 地址及其在 known_hosts 中的密钥指纹。在这种情况下,它将在每次建立连接时要求进行验证,但如果使用等号(=)或百分号(%)作为分隔符,通常不会有问题。

如果要使用密钥,则可以将它们加载到代理中,然后通过代理转发(使用 -A 选项)来自动识别密钥。不过,如果 ProxyJump 选项(-J)可用,则不需要代理转发。许多人认为代理转发实际上是一种安全漏洞和一般的误用。因此,如果 ProxyJump 可用,最好使用该选项。此外,由于服务器上 MaxAuthTries 的默认值为 6,正常使用代理中的密钥时,将限制密钥或跳转次数为 6,服务器端日志将在其中的一半时触发。

旧方法:使用 ProxyCommand 配合 Netcat

另一种旧方法,适用于 OpenSSH 5.3 及更早版本,是使用 ProxyCommand 配置指令和 netcat。nc(1) 工具用于直接读取和写入网络连接。它可以用于将连接转发到第二台机器。在这种情况下,目标是通过中介跳板主机到达另一台服务器。

$ ssh -o 'HostKeyAlias=destination.example.edu' \
        -o 'ProxyCommand ssh %h nc destination.example.edu 22' \
        jumphost.example.org

也可以使用密钥和不同的登录名。使用 ProxyCommand 时,ssh(1) 会首先连接到跳板主机,然后从那里连接到目标服务器 destination.example.eduHostKeyAlias 指令用于查找目标主机的正确密钥,因为如果没有它,会尝试使用跳板主机的密钥,显然,如果两个系统没有相同的密钥,这将导致失败。在这个示例中,跳板主机上有 user2 账户。

$ ssh -o 'HostKeyAlias=destination.example.edu' \
        -o 'ProxyCommand ssh -i jumphost_key-rsa -l user2 %h \
                nc destination.example.edu 22' \
        jumphost.example.org

也可以通过编辑 ssh_config 文件使此配置更持久并减少输入。当目标主机名被完全指定时,连接将通过跳板主机建立:

Host destination.example.org
        ProxyCommand ssh %r@jumphost.example.org nc %h %p

这里,连接通过跳板主机使用快捷方式 jump 来连接目标主机:

Host jump
        HostKeyAlias destination.example.org
        Hostname jumphost.example.org
        User fred
        ProxyCommand ssh %h nc destination.example.org 22

可以使此配置更通用。第一个片段确保跳板主机不会自己循环。第二个片段应用一般规则,将所有以 .example.edu 结尾的域名或快捷方式 my-private-host 通过跳板主机转发:

Host jumphost.example.org
        ProxyCommand none

Host *.example.org my-private-host
        ProxyCommand ssh -l %r jumphost.example.org nc %h %p

通过 ProxyCommand,也可以通过 SFTP 转发参数到 ssh(1)。这里有一个简单的例子,使用 jumphost 作为中介主机连接 machine2,用户名对于两个主机都是相同的:

$ sftp -o 'HostKeyAlias=machine2.example.edu' \
        -o 'ProxyCommand=ssh %h nc machine2.example.edu 22' \
        fred@jumphost.example.edu

下面是一个更复杂的例子,使用跳板主机的密钥,但对 SFTP 服务器使用常规的密码登录:

$ sftp -o 'HostKeyAlias=destination.example.edu' \
        -o 'ProxyCommand ssh -i /home/fred/.ssh/jumphost_rsa \
        -l user2 jumphost.example.edu nc destination.example.edu 22'   \
        jumphost.example.edu

如果两个机器的用户名不同,以上配置也可以工作。这里,'fred' 是目标机器的账户,'user2' 是跳板主机的账户。

$ ssh -i fred -i /home/fred/.ssh/destination_rsa \
       -o 'HostKeyAlias destination.example.org' \
       -o 'ProxyCommand ssh -i /home/fred/.ssh/jumphost_rsa \
              -l user2 %h nc destination.example.org 22'
       jumphost.example.org

如果可能的话,升级或回退到更新的 OpenSSH 版本,以便可以使用 ProxyJump-J 选项。

旧方法:通过 Bash 的 /dev/tcp/ 伪设备传递跳板主机连接

在缺少 nc(1) 的 GNU/Linux 跳板主机上,但具有 Bash shell 解释器时,可以使用伪设备 /dev/tcp[8]。这是利用 Bash 内置的功能,再加上 cat(1) 工具:

$ ssh -o HostKeyAlias=server.example.org \
	-o ProxyCommand="ssh -l fred %h 'exec 3<>/dev/tcp/server.example.com/22; cat <&3 & cat >&3;kill $!'" fred@jumphost.example.com

需要将 HostKeyAlias 设置为目标主机的主机名或 IP 地址,因为 SSH 连接只是经过跳板主机,需要预期正确的密钥。

该方法的主要前提是:在 GNU/Linux 跳板主机上使用 Bash 作为默认 shell。如果使用其他 shell(如 POSIX shell),则不会存在伪设备,且会发生错误:

sh: 1: cannot create /dev/tcp/server.example.com/22: Directory nonexistent

使用 ksh(1) 时,会发生类似的错误:

ksh: cannot create /dev/tcp/server.example.com/22: No such file or directory

zsh(1) shell 也会缺少 /dev/tcp 伪设备,但会产生不同的错误:

zsh:1: no such file or directory: /dev/tcp/server.example.com/22
zsh:1: 3: bad file descriptor
zsh:1: 3: bad file descriptor
zsh:kill:1: not enough arguments

因此,这个方法仅适用于 Bash shell,并且仅适用于 GNU/Linux 发行版。如果是 BSD 或 macOS 跳板主机,即使有 Bash,也不能使用此方法。

通过 -E 选项和增加调试信息,可以将客户端连接过程的详细信息捕获到日志文件:

$ ssh -vvv -E /tmp/ssh.dev.tcp.log \
	-o HostKeyAlias=server.example.org \
	-o ProxyCommand="ssh -l fred %h 'exec 3<>/dev/tcp/server.example.com/22; cat <&3 & cat >&3;kill $!'" fred@jumphost.example.com

此伪设备方法更多的是作为一种好奇心存在,但在某些极端情况下可以作为最后的手段使用。正如之前所提到的,所有最近部署的 OpenSSH 都提供了 -J 选项来代替 ProxyJump

通过一个或多个中介主机进行端口转发

隧道传输(也称为端口转发)是将一台机器上的端口映射到另一台机器上的端口。通过这种方式,远程服务可以像本地服务一样访问。或者在反向端口转发的情况下,反之亦然。端口转发可以直接从一台机器到另一台机器,或者通过中介主机进行转发。

ProxyJump 选项可用时,它是首选方法,并且可以轻松与端口转发一起使用。下面设置了一个从本地主机到机器2的隧道,机器2 位于跳板主机之后:

$ ssh -L 8900:localhost:80 -J jumphost.example.org machine2

因此,本地主机上的端口 8900 实际上是一个指向机器2上端口 80 的隧道。也可以简单到这个程度。与通常的 ProxyJump 选项一样,跳板主机可以通过逗号连接起来,形成链式跳转。

$ ssh -L 8900:localhost:80 -J jumphost1.example.org,jumphost2.localnet.lan machine2

同样,也可以指定替代帐户和端口,必要时。此方法最好与基于密钥或证书的身份验证一起使用。你可以像使用其他选项一样使用所有选项,例如使用 -X 进行 X11 转发,或使用 -D 创建一个 SOCKS 代理。

旧方法:通过单一中介主机进行端口转发,不使用 ProxyJump

下面设置了一个从本地主机到机器2的隧道,机器2 位于防火墙后,机器1 作为中介主机。隧道通过机器1 建立,机器1 是公开可访问的,且能够访问机器2。

$ ssh -L 2222:machine2.example.org:22 machine1.example.org

接下来,连接到这个隧道实际上会连接到第二台主机,机器2:

$ ssh -p 2222 remoteuser@localhost

下面是一个使用 rsync(1) 在两台主机之间运行的例子,使用机器1作为中介主机:

$ rsync -av -e "ssh -p 2222"  /path/to/some/dir/   localhost:/path/to/some/dir/

SOCKS 代理

基本的 SSH 动态转发

可以通过中介机器使用 SOCKS 代理进行连接。OpenSSH 目前支持 SOCKS4 和 SOCKS5 代理。SOCKS5 允许通过应用程序透明地穿越防火墙或其他障碍,并且可以通过 GSS-API 使用强身份验证。动态应用程序级端口转发允许动态分配外发端口,从而在 TCP 会话级别创建代理。

这里,网页浏览器可以连接到本地主机上 3555 端口的 SOCKS 代理:

$ ssh -D 3555 server.example.org

使用 ssh(1) 作为 SOCKS5 代理,或者在使用转发的任何其他情况下,可以一次性指定多个端口:

$ ssh -D 80 -D 8080 -f -C -q -N fred@server.example.org

你还需要让 DNS 请求通过你的代理进行转发。例如,在 Firefox 的最新版本中,可能有一个选项 "Proxy DNS when using SOCKS v5" 需要勾选。或者,在旧版本中,需要将 network.proxy.socks_remote_dns 设置为 true。然而,在 Chromium 中,你需要在启动浏览器时添加两个运行时选项 --proxy-server--host-resolver-rules 来指向代理,并告诉浏览器不要通过开放网络发送任何 DNS 请求。

对于支持 SOCKS 代理的其他程序也会有类似的设置。因此,你也可以通过 SSH 隧道传输 Samba。

通过跳板主机连接

通过使用 ProxyJump 选项可以轻松地通过跳板主机进行连接:

$ ssh -D 8899 -J jumphost.example.org machine2

同样,多个跳板主机可以这样链接。有关更多讨论和示例,请参见之前的部分。

旧方法:通过单个中介主机进行 SOCKS 代理

如果你希望通过一个中介主机开启一个 SOCKS 代理,这是可能的:

$ ssh -D 3555 -J user1@middle.example.org user2@server.example.org

对于旧版本的客户端,需要一个额外的步骤:

$ ssh -L 8001:localhost:8002 user1@middle.example.org -t ssh -D 8002 user2@server.example.org

在第二个示例中,客户端将看到本地主机上的端口 8001,实际上它连接到机器 1,流量最终通过机器 2 进出网络。 localhost 上的端口 8001 连接到机器 1 上的端口 8002,后者是指向机器 2 的 SOCKS 代理。端口号可以根据需要选择,但转发特权端口仍然需要 root 权限。

通过 Tor 使用 SSH

有两种方式可以通过 Tor 使用 SSH。一种是在客户端上使用 Tor,另一种是在服务器端托管 Onion 服务。通过 Tor 运行客户端可以隐藏其来源;通过 Tor 托管服务器可以隐藏其位置。这两种方法可以结合使用。

通过 Netcat 将 SSH 客户端通过 Tor 隧道传输

这些指令使用的是端口号 9050,这与 Tor 系统守护进程对应。对于 Tor 浏览器,请用端口 9150 并保持 Tor 浏览器打开。 除了将 ssh(1) 用作 SOCKS 代理外,还可以通过另一个 SOCKS 代理(如 Tor)将 SSH 协议本身进行隧道传输。Tor 是一种匿名软件和相应的网络,使用中继主机来隐瞒用户的位置和网络活动。它的架构旨在防止监视和流量分析。在需要隐匿 SSH 客户端来源的情况下可以使用 Tor,也可以用来连接到 Onion 服务。不幸的是,连接 Tor 通常会带来明显的延迟。

客户端看到的端点,Tor 就是一个常规的 SOCKS5 代理,可以像使用其他 SOCKS5 代理一样使用。所以这是通过 SOCKS 代理隧道传输 SSH。最简单的方法是使用 torsocks 工具,如果可用,只需在 SSH 命令前加上它:

$ torsocks ssh fred@server.example.org

这也可以通过 netcat 来完成:

$ ssh -o ProxyCommand="nc -X 5 -x localhost:9050 %h %p" fred@server.example.org

在尝试这样的连接时,非常重要的是它不会泄露信息。特别是,DNS 查找应该通过 Tor 进行,而不是由客户端自己执行。确保如果使用了 VerifyHostKeyDNS,则将其设置为 "no"。默认情况下是 "no",但请确保检查。可以将其作为运行时参数传递,以消除任何疑虑或不确定性。

$ ssh -o VerifyHostKeyDNS=no -o ProxyCommand="nc -X 5 -x localhost:9050 %h %p" server.example.org

使用 netcat-openbsd nc(1) 包时,这似乎不会泄露任何 DNS 信息。其他 netcat 包可能会有所不同。也不清楚此方法是否还有其他方式可能泄露信息。因而你的体验可能会有所不同。

由于这些方法通过 Tor 代理,因此它们也会启用连接到 Onion 服务。你可以配置 SSH 自动通过 Tor 连接到这些服务,而不会影响其他类型的连接。可以在 ssh_config~/.ssh/config 中添加类似以下的条目:

Host *.onion
        VerifyHostKeyDNS no
        ProxyCommand nc -x localhost:9050 -X 5 %h %p

你还可以在任何 Host 声明之前添加 CanonicalizeHostname yes,这样如果给 Onion 服务起了昵称,SSH 会在确定主机名是 .onion 地址后应用此配置。

作为 Onion 服务提供 SSH

可以从 .onion 地址提供 SSH 服务,为客户端提供匿名性和隐私,服务器也在一定程度上获得匿名性。两者都无法知道对方的位置,尽管必须采取很多额外的预防措施,才能接近对服务器本身进行匿名化,并且仍然需要一定程度的信任用户。

使 SSH 通过 Tor 可用的第一步是配置 sshd_config(5),使得 SSH 服务仅在本地主机地址上监听。

ListenAddress 127.0.0.1:22

如果需要提供多个端口,可以使用多个 ListenAddress 指令。但是,提供的任何 ListenAddress 指令应该仅绑定到 127.0.0.0/8 网络或 IPv6 等效地址。监听任何广域网 (WAN) 或局域网 (LAN) 地址会破坏 Tor 的匿名性,因为这会让 SSH 服务器通过其公钥被识别。

通过 Tor 提供 SSH 服务的下一步是设置 Tor 客户端并将其隐藏服务转发到本地 SSH 服务器。按照 Tor 项目网站上的安装说明操作,但如果不需要 Web 服务器则可以跳过相关部分。添加适当的 HiddenServicePort 指令以匹配 SSH 服务器正在使用的地址。

HiddenServicePort 22 127.0.0.1:22

如果需要,还可以为其他端口添加额外的指令。在 Tor 的新版本中,通过添加 HiddenServiceVersion 3,可以为版本 3 的 Onion 地址生成 56 个字符的地址。

确保 HiddenServiceDir 指向适合你系统的位置。新的 SSH 服务的 Onion 地址将保存在 HiddenServiceDir 指定目录中的 hostname 文件中,并且无论它在网络上的位置如何,或有多少层 NAT 将其封装,都可以访问该地址。要使用该 SSH 服务并验证它是否实际通过 Tor 可用,参见之前关于如何通过 Tor 使用 SSH 客户端与 Netcat 配合的部分。

仅通过 Tor 提供服务并不足以完全匿名化服务器。然而,这种方法的另一个优势是 NAT 穿透。如果 SSH 服务位于多个 NAT 层之后,那么作为 Onion 服务提供 SSH 可以无缝穿越这些层,而无需在每一层的路由器上进行配置。这消除了需要通过外部服务器建立反向隧道的需求,并且可以通过任意数量的 NAT 层工作,这在现在的手机调制解调器中非常常见。

使用临时 VPN 通过网关传递

可以通过 SSH 配置端点上的网络路由,使得两个子网之间的连接通过隧道进行,最终结果是一个 VPN。一个缺点是需要在两台主机上获得 root 权限,或者至少获得 sudo(8) 权限来操作 ifconfig(8)route(8)。这里展示了一个更有限、更古老的方法,虽然它不需要远程端的 root 权限,但通过 SSH 建立连接后再通过 PPP 和 slirp 创建网络。

需要注意的是,合法使用 VPN 的情况非常少,因为 OpenSSH 通常足够灵活,可以完成大多数常规的系统管理和操作任务。这个 SSH 临时 VPN 方法因此只在极少数情况下需要使用。

以下是两个网络的示例。一个网络的地址范围是 10.0.50.1 到 10.0.50.254,另一个网络的地址范围是 172.16.99.1 到 172.16.99.254。每个网络都有一台作为网关的主机,分别是 10.0.50.1 和 172.16.99.1。

最后修改: 2025年01月19日 星期日 21:47