通过 ssh 的端口转发或者隧道 (tunneling) 功能,可以实现: A) 加密 SSH Client 端至 SSH Server 端之间的通讯数据;B) 突破防火墙的限制完成一些之前无法建立的 TCP 连接。
简介
首先看下,对于转发有些比较有用的参数:
-N
告诉 SSH 客户端,这个连接不需要执行任何命令,也就是说不需要打开远程 shell,仅仅做端口转发;-T
不为这个连接分配 TTY,通过-N
-T
两个参数表示这个 SSH 连接只用来传数据,不执行远程操作;-f
告诉SSH客户端在后台运行,要关闭这个后台连接,就只有用 kill 命令去杀掉进程;-L
做本地端口转发,需要注意被冒号分割的三个部分含义,下面做详细介绍;-R
做远程端口转发,与本地端口转发类似,同样需要注意被冒号分割的三个部分含义;-g
(GatewayPorts) 默认只转发本地发送的数据,如果要转发其它服务器的客户端请求,则需要添加该参数。-C
压缩数据传输;
注意,使用时需要修改服务端的如下配置。
AllowTcpForwarding yes
AllowAgentForwarding no
GatewayPorts yes
接下来,看看具体的使用场景,以及配置方式。
本地转发
在本地启动一个监听端口,将访问该端口的请求通过 SSH 转发到服务端可以访问的服务,命令参数如下。
----- 本地转发方式的参数设置
$ ssh -N -f -L [local port]:[remote service host]:[remote service port] [user@remote ssh host]
相当于利用了 SSH 加密通道,将请求从客户端发送到了服务端的服务上。
转发到本地服务
假设在服务器上部署了 MySQL 服务 (也包括 SSH 服务),为了防止被攻击,通常在进行防火墙配置时,会尽可能减小打开的端口,例如只开启 80/443
服务端口,因此如果需要访问 mysql(3306)
服务只能从本地访问,此时就可以使用本地转发功能。
可以执行如下命令,然后访问本地的端口 7000 即可。
----- 本地执行如下命令,然后访问localhost:7000即可
$ ssh -N -f -L 7000:localhost:3306 10.12.34.100
----- 访问远程的MySQL服务
$ mysql -P7000 -hlocalhost
注意,localhost
表示的是部署 SSH 所在机器访问 MySQL 时的地址;另外,因为非管理员只能使用 1024~65535
的端口,在此选择 7000
端口。
数据在传输时将会通过如下的四步:A) 将数据发送到本地的 7000 端口;B) 本地的 SSH Client 将 7000 收到的数据加密后发送到 SSHD 端,也就是 MySQL Server 所部署的机器;C) SSHD 端收到数据后解密,并发送给本地的 3306 端口;D) 从 MySQL Server 上返回的数据按照原路返回。
转发到其它服务器
另外一种场景如下,A 尝试访问 D 提供的 MySQL 服务,但是由于防火墙导致不能直接访问,也就是说 ABD 链路不通。但是 ABC 的 SSH 链路是通的,此时就可以利用隧道技术建立 ABCD 链路。
也就是说,中间服务器 C 部署着 sshd 服务,用于建立 ssh 连接;D 的 3306 端口提供 MySQL 服务,然后就可以通过如下方式建立隧道。
----- 本地执行如下命令,然后访问localhost:7000即可
$ ssh -N -f -L 7000:10.12.34.136:3306 10.12.34.100
----- 访问远程的MySQL服务
$ mysql -P7000 -hlocalhost
可以看到,上述两种唯一的区别在于中间的地址,只要 C 机器能够访问的服务,都可以通过这种方式转发。
总结
本地转发就是在客户端监听本地端口,然后将请求通过 SSH 发送到 SSHD 所能链接的服务端,转发过程是透明的,SSH 不会关注具体的协议内容。
如果本地开启了 GatewayPorts
选项,那么只要可以链接到 10.35.0.125
的机器都可以访问 7000/2121
端口的服务,也就是远端的 MySQL/FTP
服务了。
远程转发
如上述的第二个场景,CD 通常是在一个内网,但是此时 C 没有公网 IP,但是 A 是有的,那么就意味着,通过 CBA 可以建立链接,但是 ABC 将无法建立链接,如果要在 A 访问 D 提供的 MySQL 服务,那么就需要远程转发了。
也就是利用一条已建立好的 CBA 方向的连接,来完成 ABCD 方向的访问,也就是先从 C 建立到 A 的链接,然后在 A 启动一个监听端口,这样只要能链接到 A 的机器就可以访问 D 提供的服务。
----- 参数列表,需要在提供服务的机器上执行ssh登录
$ ssh -R [remote port]:[local host]:[local port] [SSH hostname]
----- 在C上执行如下命令,与A建立SSH转发服务
$ ssh -N -f -R 7000:10.12.34.136:3306 10.35.0.125
----- 在A上通过7000端口访问D的MySQL服务
$ ssh -p 7000 localhost
这里是在 C 建立与 A 的 SSH 链接,并在 A 监听 7000 端口,对于 A 来说,C 是一台远程主机,所以这种情况就被称为 “远程端口绑定” 。
两者比较容易混淆,可以理解为,如果 SSH 服务器和应用都在同一端,则是本地转发,否则是远程转发。
动态转发
也就是通过 ssh 在本地建立一个 socks 代理服务,所有的本地网络访问都会通过该端口,然后转发到服务器,而应用程序决定使用那个端口。
可以用作隧道代理,其大致的工作如下:
整个流程如下:
- 墙内的客户机跟墙外的代理服务器,建立好 SSH 连接,并设定动态绑定;
- 此时墙内客户机上的 SSH 会监听本地的一个端口
7001
; - 访问
www.google.com
会将请求发送到7001
端口,SSH 将此请求通过 SSH 加密连接发送到墙外服务器的 SSH 上; - 由于建立的时候是动态绑定,服务器会将相应的请求发送到
www.google.com
的80
端口,并在收到回复后,原路返回给客户机的 SSH,客户机的 SSH 返回给应用程序。
所以在上述的模型中,客户机的 SSH 实际上就是实现了一个 SOCKS 代理的角色,这个 SOCKS 代理侦听了 7001 端口,并将所有的请求都代理给服务器的 SSH,并利用 SSH 动态绑定,让服务器进一步转发请求。
当然,使用时,需要在浏览器或者其他应用程序上设置 SOCKS 代理,实际设置 SOCKS-v4 就可以,而 SOCKS-v5 仅仅增加了鉴权功能,代理指向 127.1:7001 即可。
$ ssh -D [local port] [SSH Server]
$ ssh -N -f -D 1080 foobar@123.123.123.123 # 将端口绑定在127.0.0.1上
$ ssh -N -f -D 0.0.0.0:1080 foobar@123.123.123.123 # 将端口绑定在0.0.0.0上
上述的命令,建立一个通过 SOCK5 协议的 123.123.123.123
的 SOCKS 服务器。