OpenSSH/Cookbook/代理和跳跃主机
代理是转发来自客户端到其他服务器的请求的中间人。性能改进、负载均衡、安全性或访问控制是使用代理的一些原因。
可以通过一个或多个中间人连接到另一个主机,以便客户端可以表现得好像连接是直接的。
主要方法是使用 SSH 连接通过一个或多个跳跃主机转发 SSH 协议,使用ProxyJump指令,到目标目标主机上运行的 SSH 服务器。这是最安全的方法,因为加密是端到端的。除了任何其他进行的加密之外,链的端点相互加密和解密彼此的流量。因此,通过中间主机传递的流量始终被加密。但是,如果中间主机拒绝端口转发,则不能使用此方法。
使用ProxyCommand选项将 Netcat 作为链中的最后一个调用是针对非常旧的客户端的此方法的变体。SSH 协议由nc
而不是ssh
转发。还必须注意在 SSH 连接链中用户名是否从主机到主机发生变化。过时的netcat方法不允许更改用户名。其他方法可以。
当端口转发可用时,最简单的方法是在配置文件中使用ProxyJump或在运行时参数中使用-J。-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和其他扩展标记的说明:在旧版本的 OpenSSH 中,可以使用无效的用户名或主机名通过扩展标记传递 shell 元字符。该问题在CVE-2023-51385中进行了描述,并在OpenSSH 9.6及更高版本中得到了一定程度的缓解。它指的是不可信的代理字符串,例如可能来自不可靠的版本控制服务和其他地方。缓解措施是部分的,因为过滤所有可能与用户提供的命令相关的 shell 元字符是不可行的。 |
更旧的客户端不支持-W选项。在这种情况下,可以使用ssh -tt
。这强制分配 TTY 并按原样传递 SSH 流量,尽管这不太安全。要通过跳跃主机firewall连接到server2
$ ssh -tt firewall.example.com ssh -tt server2.example.org
该示例打开到远程机器的 SSH 会话。您还可以传递命令。例如,要使用screen重新附加到远程屏幕会话,您可以执行以下操作
$ ssh -tt firewall.example.com ssh -tt server2.example.org screen -dR
链可以任意长,并不局限于只有两个主机。这种方法相对于使用-W或-J在网络层进行 stdio 转发的缺点是,您的会话、任何转发的代理、X11 服务器和套接字都暴露给了中间主机。
从 2016 年 8 月发布的 OpenSSH 7.3[1]开始,通过一个或多个跳跃主机最简单的方法是在ssh_config(5)中使用ProxyJump指令。
Host server2
HostName 192.168.5.38
ProxyJump [email protected]:22
User fred
可以将多个跳跃主机指定为逗号分隔的列表。主机将按照列出的顺序访问。
Host server3
HostName 192.168.5.38
ProxyJump [email protected]:22,[email protected]:2222
User fred
它在用作运行时参数时也有-J的快捷方式。
$ ssh -J [email protected]:22 [email protected]
多个跳跃主机可以以相同的方式链接。
$ ssh -J [email protected]:22,[email protected]:2222 [email protected]
在同一主机配置中,无法同时使用ProxyJump和ProxyCommand指令。找到第一个指令,然后阻塞另一个指令。
当穿过一个跳跃主机时,它的相关接口每个都在不同的rdomain(4)上,则需要手动操作路由表。具体来说,这意味着回到一种旧的传输方法,该方法依赖于netcat并使用ProxyCommand而不是ProxyJump,以便可以在中间添加route(8)。以下是用 run time 参数通过 rdomain 1 进行传输的示例
$ ssh -o ProxyCommand="ssh [email protected] route -T 1 exec nc %h %p" [email protected]
可以通过将该配置添加到客户端配置文件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
行就可以通过跳跃主机连接到 LAN 上的主机,所有设置都将被使用。
否则,建议的方式是使用ProxyJump。有关使用旧的ProxyCommand指令的更多信息,请参见下面的部分带有 Netcat 的 ProxyCommand。
可以使用Match exec来选择困难的模式或进行其他复杂的决策,例如客户端连接的网络或可用网络的网络连接。下面是两种情况,说明如何在自动选择使用ProxyJump通过中间主机连接时使用它。第一个示例适用于当连接到仅有 IPv6[2] [3]的远程机器时没有本地 IPv6 连接的情况。第二种情况[4]适用于目标机器在另一个网络上的情况。
Match host ipv6only.example.org
User fred
Match host ipv6only.example.org !exec "route -n get -inet6 %h"
ProxyJump dualstack.example.org
使用这些设置,到ipv6only.example.org机器的连接将仅在无法通过 IPv6 直接访问时通过跳跃主机进行。
在 GNU/Linux 系统上,该功能将更加复杂。
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 调用。请记住,配置指令是根据第一个匹配的指令应用的。因此,特定规则放在顶部附近,更通用的规则放在末尾。
另一种方法是使用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.* 的连接通过第二个跳跃主机发送。但是,如果存在直接连接,客户端将无需跳跃主机即可继续。
当然,如果客户端机器在具有相同编号的多个不同网络之间移动,则测试将更加复杂。
使用位于跳跃主机后面的规范主机名
[edit | edit source]一些局域网 (LAN) 拥有自己的内部域名服务,以分配自己的主机名。这些名称无法从 LAN 外部访问。因此,无法从外部使用-J或ProxyJump选项,因为客户端将无法在跳跃主机另一侧的 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
[email protected]'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选项确保连接通过安全通道转发,并且只是通过跳跃主机而不会被解密。跳跃主机必须既能够为 LAN 名称执行 DNS 查找,又能够使用 SSH 客户端。
以下是客户端收集主机密钥并询问其信息的初始连接的外观。
$ ssh -o ProxyCommand="ssh -W fuloong04.localnet.lan:22 jumphost.example.org" fred@fuloong04
[email protected]'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中为作为目标给出的名称fuloong04(而不是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选项中传递的实际主机名或地址无关,甚至可以是随机字符串。上面fuloong04是单独使用的,但同样,它可以是任何随机字符串,甚至可以通过使用HostKeyAlias选项来覆盖。
通常,这些 SSH 选项可以记录在ssh_config(5)中的永久快捷方式中,以减少输入工作量并避免错误。
通过跳跃主机的旧方法
[edit | edit source]在 OpenSSH 的旧版本(特别是 7.2 及更早版本)中,通过一个或多个网关更加复杂,需要使用 stdio 转发或在 5.4 之前使用netcat(1)实用程序。
旧方法:使用 stdio 转发(Netcat 模式)通过网关
[edit | edit source]在 OpenSSH 5.4[5]到 7.2(含)之间,'netcat 模式' 可以将客户端上的 stdio 连接到服务器上转发的单个端口。这也可以用于使用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 代理,或者在配置文件中指定它。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选项用于指向网关的私钥,命令行上指定的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 转发递归链接网关
[edit | edit source]执行此操作的简单方法是使用ProxyJump,该方法从 OpenSSH 7.3 开始可用,如上所述。对于旧版本,如果路由始终以相同的顺序包含相同的主机,则可以在配置文件中添加一个简单的链。这里,三个主机与目标链接,目标被赋予快捷方式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指令只需要hostname,以及对于第二台及后续主机,ProxyCommand。如果未使用密钥,则不需要IdentityFile。如果所有主机的用户都相同,则可以跳过它。如果端口是默认端口,则可以跳过Port。如果在代理中同时使用多个密钥,并且在客户端端出现“身份验证过多”的错误,则可能需要将IdentitiesOnly yes添加到每个主机的配置中。
旧方法:递归链接任意数量的主机
[edit | edit source]同样,执行此操作的简单方法是使用ProxyJump,该方法从 OpenSSH 7.3 开始可用。对于旧版本,可以使配置更抽象,并允许通过任意数量的网关。您可以使用-l设置用户名,但该用户名将用于您连接或通过的所有主机。
Host */*
ProxyCommand ssh %r@$(dirname %h) -W $(basename %h):%p
这样,主机之间用斜线 (/) 分隔,数量可以是任意的[6]。
$ ssh host1/host2/host3/host4
使用斜线作为分隔符会有一些限制,就像使用其他符号一样。但是,它允许使用 dirname(1) 和 basename(1) 处理主机名。
以下配置使用 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,也可以用冒号 (:) 分隔非标准端口[7]。
$ ssh host1+host2:2022+host3:2224
原样,冒号会干扰 sftp(1),因此上述配置只能在使用标准端口的情况下才能与它一起使用。如果需要在非标准端口上使用 sftp(1),那么可以使用其他分隔符,比如下划线 (_) 来进行配置。
可以使用指定的定界符为给定主机指定除最后一个以外的任何用户名,在上面的例子中,它是一个百分号 (%)。目标主机的用户名使用 -l 指定,其他所有用户名都可以用分隔符与它们对应的主机名连接起来。
$ ssh -l user3 user1%host1+user2%host2+host3
如果指定了用户名,根据分隔符的不同,ssh(1) 可能无法将最终主机与 IP 号码和 known_hosts 中的密钥指纹匹配。在这种情况下,它将在每次建立连接时要求验证,但如果使用等号 (=) 或百分号 (%),则这不会有问题。
如果要使用密钥,那么将它们加载到代理中,然后如果使用 -A 选项进行代理转发,客户端会自动识别它们。但是,如果可以使用 ProxyJump 选项 (-J),则不需要代理转发。许多人认为这实际上是一个安全漏洞,是一个普遍的错误特征。因此,如果可以使用 ProxyJump 选项,请使用它。此外,由于服务器上 MaxAuthTries 的默认值为 6,因此通常在代理中使用密钥会将密钥或跳跃的次数限制为 6 次,服务器端日志将在超过一半的时间后被触发。
旧:带有 Netcat 的 ProxyCommand
[edit | edit source]另一种方法,适用于 OpenSSH 5.3 及更早版本,是使用 ProxyCommand 配置指令和 netcat。工具 nc(1) 用于直接读取和写入网络连接。它可以用来将连接传递到第二台机器。在这种情况下,destination 是通过中间机器 jumphost 访问的另一台服务器。
$ ssh -o 'HostKeyAlias=destination.example.edu' \
-o 'ProxyCommand ssh %h nc destination.example.edu 22' \
jumphost.example.org
密钥和不同的登录名也可以使用。使用 ProxyCommand,ssh(1) 将首先连接到 jumphost,然后从那里连接到 destination.example.edu。需要使用 HostKeyAlias 指令查找 destination.example.edu 的正确密钥,因为如果没有它,将会尝试使用 jumphost 的密钥,这当然会失败,除非两个系统具有相同的主机密钥。在这个例子中,帐户 user2 存在于 jumphost 上。
$ 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 来使这种安排更加持久,并减少输入。这样一来,只要在目标主机名完整的情况下使用它,就会通过 jumphost 连接到 destination
Host destination.example.org
ProxyCommand ssh %[email protected] nc %h %p
这里通过 jumphost 连接到 destination,使用快捷名称 '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
对 sftp(1) 也可以这样做,通过将参数传递给 ssh(1)。以下是一个简单的例子,使用 sftp(1),其中 jumphost 是用于连接到 machine2 的中间主机。用户名对两个主机都相同。
$ sftp -o 'HostKeyAlias=machine2.example.edu' \
-o 'ProxyCommand=ssh %h nc machine2.example.edu 22' \
[email protected]
以下是一个更复杂的例子,使用 jumphost 的密钥,但使用基于密码的常规登录方式登录 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 选项。
旧:没有 Netcat 的 ProxyCommand,使用 Bash 的 /dev/tcp/ 伪设备
[edit | edit source]在没有 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 $!'" [email protected]
需要将 HostKeyAlias 设置为目标主机名或 IP 地址,因为 SSH 连接只是通过跳跃主机进行,需要期望正确的密钥。
此方法的主要前提是在具有 Bash 作为默认 shell 的 GNU/Linux 跳跃主机上拥有一个登录 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(1) shell 的一个过程。此外,此方法特定于 GNU/Linux 发行版上的 Bash,即使 bash(1) 存在,也不会在任何 BSD 或 Mac OS 上作为跳跃主机提供。
可以使用 '-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 $!'" [email protected]
此伪设备方法主要作为一种奇特的方式包含在此处,但在某些极端情况下可以作为最后的手段。如前所述,所有最近部署的 OpenSSH 都将具有 -J 选项,用于 ProxyJump。
通过一个或多个中间主机进行端口转发
[edit | edit source]隧道,也称为端口转发,是指将一台机器上的端口映射到另一台机器上的端口连接。这样一来,就可以像访问本地服务一样访问远程服务。或者,在反向端口转发的情况下,反过来。转发可以从一台机器直接转发到另一台机器,也可以通过中间机器进行转发。
如果可以使用,ProxyJump 选项是更优的选择。它与端口转发一样容易使用。以下是在 localhost 到 machine2 之间建立的隧道,该隧道位于跳跃主机后面。
$ ssh -L 8900:localhost:80 -J jumphost.example.org machine2
因此,localhost 上的端口 8900 将实际上是到 machine2 上端口 80 的隧道。它可以像这样简单。与通常的 ProxyJump 选项一样,跳跃主机可以通过用逗号连接起来进行链式连接。
$ ssh -L 8900:localhost:80 -J jumphost1.example.org,jumphost2.localnet.lan machine2
如果需要,也可以指定备用帐户和端口。这种方法在使用基于密钥或证书的身份验证时效果最好。可以以这种方式使用所有选项,例如 -X 用于 X11 转发。-D 也是如此,用于创建 SOCKS 代理。
旧:通过单个中间主机进行端口转发,不使用 ProxyJump
[edit | edit source]下面,我们将在 localhost 到 machine2 之间建立一条隧道,machine2 位于防火墙 machine1 后面。隧道将通过 machine1,它可以公开访问,并且也可以访问 machine2。
$ ssh -L 2222:machine2.example.org:22 machine1.example.org
接下来,连接到隧道将实际上连接到第二台主机,machine2。
$ ssh -p 2222 remoteuser@localhost
以下是在使用 machine1 作为中间机器,使用上述设置,在两台主机之间运行 rsync(1) 的示例。
$ rsync -av -e "ssh -p 2222" /path/to/some/dir/ localhost:/path/to/some/dir/
SOCKS 代理
[edit | edit source]可以使用 SOCKS 代理通过中间机器进行连接。OpenSSH 目前支持 SOCKS4 和 SOCKS5 代理。SOCKS5[9] 允许应用程序透明地穿越防火墙或其他障碍,并且可以在 GSS-API 的帮助下使用强身份验证。动态应用程序级端口转发允许在运行时分配传出端口,从而在 TCP 会话级别创建代理。
这里,Web 浏览器可以连接到本地主机上端口 3555 的 SOCKS 代理
$ ssh -D 3555 server.example.org
使用 ssh(1) 作为 SOCKS5 代理,或在任何其他使用转发的场合,可以在一个操作中指定多个端口
$ ssh -D 80 -D 8080 -f -C -q -N [email protected]
您还希望 DNS 请求通过您的代理。例如,在最新版本的 Firefox 中,可能有一个选项“使用 SOCKS v5 时代理 DNS”可供选中。或者在旧版本中,about:config 需要将 network.proxy.socks_remote_dns 设置为 true。但是,在 Chromium[10] 中,您需要在添加两个运行时选项 --proxy-server 和 --host-resolver-rules 时启动浏览器,以指向代理并告诉浏览器不要通过开放网络发送任何 DNS 请求。
对于支持 SOCKS 代理的其他程序,情况类似。因此,您也可以通过 ssh(1) 对 Samba 进行隧道。
通过跳跃主机只是使用 ProxyJump 选项的问题。
$ ssh -D 8899 -J jumphost.example.org machine2
也可以通过这种方式将多个跳跃主机链接在一起。有关讨论和示例,请参见有关此主题的先前部分。
如果您想通过中间主机打开一个 SOCKS 代理,这是可能的。
$ ssh -D 3555 -J [email protected] [email protected]
在旧的客户端上,需要一个额外的步骤。
$ ssh -L 8001:localhost:8002 [email protected] -t ssh -D 8002 [email protected]
在第二个示例中,客户端将在本地主机的端口 8001 上看到一个 SOCKS 代理,这实际上是与 machine1 的连接,流量最终将通过 machine2 进入和离开网络。本地主机上的端口 8001 连接到 machine1 上的端口 8002,这是一个到 machine2 的 SOCKS 代理。端口号可以根据需要选择,但转发特权端口仍然需要 root 权限。
有两种方法可以使用 SSH 通过 Tor。一种是通过 Tor 使用客户端,另一种是将服务器托管为洋葱服务。通过 Tor 运行客户端可以隐藏其来源。在 Tor 后面托管服务器可以隐藏其位置。这两种方法可以结合使用。
这些说明使用端口号 9050,该端口号对应于 Tor 系统守护进程。对于 Tor 浏览器,请使用端口 9150 并保持 Tor 浏览器打开。 |
除了使用 ssh(1) 作为 SOCKS 代理外,还可以通过另一个 SOCKS 代理(如 Tor)对 SSH 协议本身进行隧道。它是一种匿名软件和相应的网络,使用中继主机来隐藏用户的定位和网络活动。其体系结构旨在防止监控和流量分析。Tor 可用于在必须隐藏 SSH 客户端的来源的情况下使用。它还可用于连接到洋葱服务。不幸的是,通过 Tor 连接通常会以明显的延迟为代价。
在客户端看到的端点上,Tor 是一个常规的 SOCKS5 代理,可以像任何其他 SOCKS5 代理一样使用。所以这是通过 SOCKS 代理对 SSH 进行隧道。如果可用,使用 torsocks
实用程序是执行此操作的最简单方法,只需在 SSH 命令前面加上它即可:[11]
$ torsocks ssh [email protected]
但是,这也可以通过使用 netcat 来完成。
$ ssh -o ProxyCommand="nc -X 5 -x localhost:9050 %h %p" [email protected]
在尝试这种连接时,非常重要的是它不会泄露信息。尤其是,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 软件包可能相同也可能不同。目前也不清楚此方法是否还有其他可能泄露信息的方式。YMMV。
由于这些方法通过 Tor 代理,因此它们都将允许连接到洋葱服务。您可以配置 SSH 自动通过 Tor 连接到这些服务,而不会影响其他类型的连接。可以在 ssh_config 或 ~/.ssh/config 中添加类似以下内容的条目。
Host *.onion
VerifyHostKeyDNS no
ProxyCommand nc -x localhost:9050 -X 5 %h %p
您可以在任何 Host 声明之前进一步添加 CanonicalizeHostname yes,以便如果您为洋葱服务提供了一个昵称,SSH 将在确定主机名是 .onion 地址后应用此配置。
SSH 可以从 .onion 地址提供服务,为客户端和在一定程度上为服务器提供匿名性和隐私。两者都不会知道对方的位置,尽管必须采取很多额外的预防措施才能接近匿名化服务器本身,并且至少需要信任用户。
通过 Tor 使 SSH 可用的第一步是设置 sshd_config(5),以便 SSH 服务仅监听 localhost 地址上的 localhost 地址。
ListenAddress 127.0.0.1:22
如果要提供多个端口,可以使用多个 ListenAddress 指令。但是,提供的任何 ListenAddress 指令都应该只绑定到 127.0.0.0/8 网络中的地址或 IPv6 等效地址。监听任何 WAN 或 LAN 地址将通过允许从 SSH 服务器的公钥识别它来破坏 Tor 的匿名性。
通过 Tor 提供 SSH 的下一步是设置一个 Tor 客户端,并将隐藏服务转发到本地 SSH 服务器。按照 Tor 项目网站上提供的安装 Tor 客户端的说明[12],但如果不需要,则跳过有关 Web 服务器的部分。添加相应的 HiddenServicePort 指令以匹配地址和 sshd(8) 正在使用的地址。
HiddenServicePort 22 127.0.0.1:22
如果需要,请为其他端口添加其他指令。在较新的 Tor 版本中,可以通过在支持它的版本中将 HiddenServiceVersion 3
添加到 Tor 配置中,来创建 56 个字符的版本 3 洋葱地址[13]。
确保 HiddenServiceDir 指向适合您系统的某个位置。新 SSH 服务的洋葱地址将位于 HiddenServiceDir 指令指示的目录内的 hostname 文件中,并且无论它在网络上的什么位置或有多少层 NAT 掩盖它,它都将可访问。要使用 SSH 服务并验证它是否确实可以通过 Tor 使用,请参见前面有关使用 Netcat 通过 Tor 使用 SSH 客户端的部分。
仅仅通过 Tor 使它可用不足以匿名化服务器。但是,此方法的另一个优势是 NAT 穿透。如果 SSH 服务位于多层 NAT 后面,那么将其作为洋葱服务提供允许无缝地穿过这些层,而无需配置每一层的每个路由器。这样就无需将反向隧道连接到外部服务器,并且可以穿过任意数量的 NAT 层,例如现在手机调制解调器中发现的 NAT 层。
通过在端点上配置网络路由以使用隧道,可以将两个子网连接到 SSH。结果是 VPN。缺点是需要在两个主机上都有 root 访问权限,或者至少需要对 sudo(8) 访问 ifconfig(8) 和 route(8)。相关但更有限且更古老的方法(这里没有介绍,但不需要在远程端使用 root)是使用 ssh
建立连接,然后使用 PPP 和 slirp
建立网络。[14]
请注意,真正需要使用 VPN 的情况很少,这并非因为 VPN 非法(事实上,恰恰相反,许多国家的数据保护法规定必须使用 VPN 来保护传输中的内容),而是因为 OpenSSH 通常足够灵活,能够使用正常的 SSH 方法按需完成大多数常规系统管理员和操作任务。因此,这种 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),它们将充当网关。本地机器编号从 3 开始,因为 2 将用于每个局域网上的隧道接口。
+----10.0.50.1 172.16.99.1----+
+ 10.0.50.2 ===== 172.16.99.2 +
| |
10.0.50.3-----+ +---172.16.99.3
| |
10.0.50.4-----+ +---172.16.99.4
| |
10.0.50.5-----+ +---172.16.99.5
| |
10.0.50.etc---+ +---172.16.99.etc
| |
10.0.50.254---+ +---172.16.99.254
首先,在每台机器上创建一个 tun 设备,这是一个用于点对点 IP 隧道的虚拟网络设备。然后,这两个网关上的 tun 接口通过 SSH 隧道连接。每个 tun 接口都被分配一个 IP 地址。
隧道将机器 10.0.50.1 和 172.16.99.1 连接在一起,并且它们都已连接到各自的局域网 (LAN)。这是一个 VPN,其中客户端为 10.0.50.0/24,远程为 172.16.99.0/24。首先,在客户端上设置
$ ssh -f -w 0:1 192.0.2.15 true
$ ifconfig tun0 10.1.1.1 10.1.1.2 netmask 255.255.255.252
$ route add 172.16.99.0/24 10.1.1.2
在服务器上
$ ifconfig tun1 10.1.1.2 10.1.1.1 netmask 255.255.255.252
$ route add 10.0.50.0/24 10.1.1.1
以下类型的错误消息可能有多种原因
channel 0: open failed: connect failed: open failed Tunnel forwarding failed
一种可能是服务器上尚未启用隧道功能。SSH 服务器的默认配置已关闭隧道功能,因此必须使用 PermitTunnel 配置指令在尝试使用临时 VPN 之前明确启用它。如果未启用隧道功能,则在客户端使用 -w 选项连接时会发生类似上述的错误。在这种情况下,解决方案是在服务器上将 PermitTunnel 设置为 'yes'。
另一种可能是远程系统或本地系统(或两者)缺少必要的网络接口伪设备。之所以会出现这种情况,是因为一个或两个帐户都缺乏在运行时创建 tun(4) 设备的必要权限。存在多种解决方法,包括使用特权帐户预先在每个系统上创建 tun(4) 设备。换句话说,解决方案是手动创建用于隧道的必要网络接口伪设备。
第三种可能是,一个或两个帐户都没有访问网络接口伪设备的适当权限。检查并(如果需要)更正组成员身份。
参考文献
- ↑ "OpenSSH 7.3 发布说明". OpenSSH. 2016-08-01. 检索自 2016-08-01.
- ↑ Peter Hessler (2018-12-04). "phessler". Mastodon. 检索自 2018-12-05.
- ↑ Mike Lee Williams (2017-07-13). "使用 Match 在必要时隧道化 SSH 连接". 检索自 2019-09-05.
- ↑ Andrew Hewus Fresh (2019-08-25). "afresh1". Mastodon. 检索自 2019-09-05.
- ↑ "OpenSSH 5.4 发布说明". OpenSSH. 2010-04-07. 检索自 2013-04-19.
- ↑ Josh Hoblitt (2011-09-03). "递归地链接 SSH ProxyCommand". [阅读本精细材料] 来自 Joshua Hoblitt. 检索自 2014-02-14.
- ↑ Mike Hommey (2016-02-08). "SSH 通过跳跃主机,重温". glandium. 检索自 2016-02-09.
- ↑ "关于 ssh ProxyCommand 的一切". Stack Exchange. 2011.
- ↑ "SOCKS 协议版本 5". IETF. 检索自 2011-02-17.
- ↑ "在 Chrome 中配置 SOCKS 代理服务器". Chromium 项目. 检索自 2016-12-10.
- ↑ "通过 Tor 进行 SSH". Tor 项目. 2012-08-28. 检索自 2013-05-04.
- ↑ "为 Tor 配置隐藏服务". Tor 项目,Inc. 检索自 2016-12-09.
- ↑ "Tor 交会规范 - 版本 3". Tor 项目,Inc. 2015-05-26. 检索自 2018-12-14.
- ↑ Jeremy Impson (2008-09-16). "pppsshslirp: 通过 SSH 创建一个 PPP 会话到您没有 root 权限的远程机器". 检索自 2016-12-10.
OpenSSH 概述 • 为什么要加密 • 协议 • 实现 • 客户端 • 客户端配置 • 服务器 • 模式 • 工具 • 第三方 • 日志记录和故障排除 • 开发 |