SSH Tunnel (Port Forwarding)

Table of Contents

1 SSH Tunnel

SSH往往工作在TCP之上,SSH Connection中的数据是加密后传递的。SSH常常用于登录远程服务器,不过本文将介绍 SSH的另外一个强大功能——端口转发,使用这个功能可以对其它应用程序的TCP连接进行加密。 比如,Telnet, SMTP, NNTP, IMAP等协议是不安全的,利用SSH的端口转发功能可以使它们变得安全。由于SSH Connection提供了一个安全的隧道,以使其它应用程序的TCP连接可以通过, 端口转发又被称为隧道(tunnel)。

端口转发有“Local Port Forwarding”和“Remote Port Forwarding”之分,后面将一一介绍它们。

参考:
SSH, The Secure Shell: The Definitive Guide, 9.2 Port Forwarding

2 Local port forwarding (-L)

假设Host B上有一个服务程序(提供服务的TCP端口为W),Host A的客户端直接连接它时的示意图如 1 图所示。

ssh_direct_conn.gif

Figure 1: 直接连接

注意,在这种方式下,Host A和Host B之间数据传送是不安全的(未加密的)。 我们利用SSH的端口转发功能,可以使Host A和Host B之间数据传送变得安全 ,如图 2 所示,当隧道建立好后Host A的客户端连接“Host A上的端口P”就相当于连接上了“Host B上的端口W”。注:应用服务器启用TLS也可以为TCP提示安全的连接,这里不讨论它。

ssh_port_forward.gif

Figure 2: 通过SSH隧道进行连接

如何建立图 2 所示的SSH隧道呢?这可以通过ssh的 -L 选项来实现端口转发功能。

下面假设端口W为80,而端口P为8080。首先你需要有一个可以登录Host B的ssh账号(假设用户名为user1),然后在Host A上执行下面命令即可:

$ ssh -L 8080:HostB:80 user1@HostB
$ ssh -L 8080:localhost:80 user1@HostB   # 当ssh服务器和HostB是同一台机器时,这样写也行

执行上面命令时可能(如果你没有配置私钥登录方式的话)提示你输入user1在HostB上的密码。当ssh连接建立后,客户端连接“Host A上的端口8080”就相当于连接“Host B上的端口80”了,而且Host A和Host B之间的数据传送是安全的。

执行上面命令有两个功能:一是获得了Host B上一个交互式shell(可执行远程命令),二是开启了端口转发功能。有时,我们不需要执行远程命令,仅仅想使用其端口转发功能。通过 -N 可以放弃执行远程命令:

$ ssh -N -L 8080:localhost:80 user1@HostB       # -N 表示不执行远程命令,不会返回交互式shell了

上面命令会在前端执行,使用 & 或者ssh的选项 -g 可以把它放入后台执行:

$ ssh -N -L 8080:localhost:80 user1@HostB &    # 命令最后加上 & 可把它放入后台
$ ssh -f -N -L 8080:localhost:80 user1@HostB   # -f 也可以把ssh放入后台执行,它比 & 更可靠

2.1 Forwarding Off-Host(可能涉及4台机器)

前面描述的端口转发中,应用程序服务器和ssh服务器在一个机器上,而应用程序客户端和ssh客户端在一个机器上,所以只涉及2台机器。事实上,一次转发可能涉及4台机器,如图 3 所示。

ssh_off_host_forward.gif

Figure 3: Off-host port forwarding

为安全起见默认只有位于Host A上的客户端才能连接“Host A上的端口8080”(和前一样,假设端口P为8080,端口W为80),如果你想在另外一台机器(比如Host C)上连接“Host A上的端口8080”,则需要设置 GatewayPortsyes (参考 man ssh_config ),也可以通过 -g 选项(和设置 GatewayPortsyes 等效)来实现监听在Host A上的所有地址(不仅仅是本地地址):

$ ssh -f -N -L 8080:localhost:80 user1@HostB         # 默认仅监听在本地,即 127.0.0.1:8080
$ ssh -g -f -N -L 8080:localhost:80 user1@HostB      # -g 使其监听在所有地址,即 *:8080

应用程序服务器(Host S)和ssh服务器(Host B)可以是不同的机器,通过 -L 的参数的第二个字段指定即可:

$ ssh -g -f -N -L 8080:localhost:80 user1@HostB   # ssh服务器会连接localhost:80
$ ssh -g -f -N -L 8080:HostS:80 user1@HostB       # ssh服务器会连接HostS:80

2.2 Troubleshooting: administratively prohibited: open failed

如果启用Local port forwarding后(如在Host A上执行下面命令):

$ ssh -L 8080:localhost:80 user1@HostB

连接Host A上8080端口时,出现了错误“administratively prohibited: open failed”。

你需要检测Host B上的配置文件“/etc/ssh/sshd_config”,确保:

  1. AllowTCPForwarding 被配置为 yes ,或者被注释掉(它的默认值为 yes ,所以可以不配置);
  2. PermitOpen 被配置为 any ,或者被注释掉(它的默认值为 any ,所以可以不配置)。

参考:
man sshd_config
https://unix.stackexchange.com/questions/14160/ssh-tunneling-error-channel-1-open-failed-administratively-prohibited-open

3 Remote port forwarding (-R)

上一节介绍的是Local port forwarding,这一节介绍Remote port forwarding。

假设你在自己电脑上开发了一个网站,想分享给朋友看一下。但你的ISP(Internet service provider)一般都不会分配固定的公网IP给你,从而你的朋友是无法通过Internet访问你的网站。

这时,可以通过Remote port forwarding来解决。注:你需要有一台在公网上可访问的服务器。

假设你电脑为Host A,且你的网站服务器端口为8000。公网上可访问的服务器为Host B。则在Host A上执行下面命令即可:

$ ssh -R 9000:localhost:8000 user1@HostB

这时,你朋友访问HostB:9000时,就相当于访问你本地搭建的网站。

特别说明,默认Host B上的9000端口仅监听本地地址(只能从Host B上访问,没什么意义),要使其监听在任意地址,你需要把 GatewayPorts yes 加入到Host B中ssh服务器配置文件“/etc/ssh/sshd_config”中。为使配置生效,你需要重启ssh服务器: sudo service sshd restart

如果你不需要交互式shell,则可以指定参数 -N ,如果你想把它放入后台执行,则可以指定参数 -f 。如:

$ ssh -N -R 9000:localhost:8000 user1@HostB       # 没有交互式shell,在前台执行
$ ssh -N -R 9000:localhost:8000 user1@HostB &     # 没有交互式shell,且后台执行
$ ssh -f -N -R 9000:localhost:8000 user1@HostB    # 没有交互式shell,且后台执行

4 Local VS. Remote port forwarding

Local port forwarding和Remote port forwarding很相似,它们的区别如图 45 所示。

ssh_local_port_forwarding.gif

Figure 4: SSH local port forwarding

ssh_remote_port_forwarding.gif

Figure 5: SSH remote port forwarding


Author: cig01

Created: <2018-04-28 Sat 00:00>

Last updated: <2018-07-19 Thu 13:28>

Creator: Emacs 25.3.1 (Org mode 9.1.4)