1 需求描述

1.1 机器状况

机器号 IP 用户名 备注
A 192.168.0.A usr_a 目标服务器,在局域网中,可以访问A
B B.B.B.B usr_b 代理服务器,在外网中,无法访问A
C - - 可以直接访问B,无法直接访问A

1.2 目标

从C机器使用 SSH 访问 A

2 解决方案

在A机器上配置到B机器上的远程端口转发;在B机器上做本地端口转发。

上述方案,使用了ssh 2个技术:远程端口转发和本地端口转发。

2.1 远程端口转发

远程端口转发:ssh client跟服务client不在一个网络,其命令为ssh -R <local port>:<remote host>:<remote port> <SSH hostname>

远程端口转发实例:如下图,LDAP client要访问防火墙后的LDAP server,但但防火墙设置只允许ssh服务通过,或者LDAP server只允许本地访问,因此LDAP client无法直接访问到LDAP server。此时可以通过SSH本地端口转发,即在SSH client建立SSH连接后,由与LDAP server同一网络内的ssh client,将报文转发到同处于防火墙内部的LDAP server。

remote port

将上述实例的LDAP server替换为ssh server,也就能通过远程端口转发,实现从B机器到A机器的ssh访问:当在B机器上访问127.0.0.1:<local port>时,通过远程端口转发,实际访问的是A机器的服务<remote host>:<remote port>

但是由于远程端口转发只能在B机器上配置本地监听(127.0.0.1:<local port>),C机器无法向B机器的local port发起连接,因此还需要在B机器上做一次本地端口转发,从而实现C机器通过访问B.B.B.B:loca port2来实现访问A机器上的ssh服务。

2.2 本地端口转发

本地端口转发:ssh client跟实际client在一个网络,其命令为ssh -L [local addr]<local port>:<remote host>:<remote port> <SSH hostname>

本地端口转发实例:如下图,LDAP client想要访问防火墙后的LDAP server,但防火墙设置只允许ssh服务通过,或者LDAP server只允许本地访问,因此LDAP client无法直接访问到LDAP server。此时可以通过SSH本地端口转发,即从防火墙外部网络建立到防火墙内SSH server的SSH连接,由防火墙内部SSH server本地转发到同处于防火墙内部的LDAP server。

由于ssh报文总是加密的,因此通过本地端口转发,可以保证不会被抓包窃取到信息。

local

在本方案中,我们想解决的跟上述实例是同一个问题:服务提供者只允许本地访问,因此需要做SSH 本地端口转发,从而在B机器上建立监听一个新的端口(port 2),该监听服务可以接受任意客户端的请求,并将请求转发给(在远程端口转发时配置的)本地监听端口(port 2)。

2.3 多主机转发

通过远程端口转发和本地端口转发时,能够实现开始时提出的从C机器直接访问A机器的需求:在A机器上配置远程端口转发,从而允许从B机器SSH连接到A机器;在B机器上配置本地端口转发,从而允许从C机器SSH连接到B机器;最终实现从C机器直接SSH连接到A机器。

此处应该有图

3 实施配置

3.1 环境需求

每台机器上都需要 SSH 客户端。A、B 两台机器上需要 SSH 服务器端,通常是 openssh-server。在 Ubuntu 上安装过程为

sudo apt-get install openssl-server

3.2 实施步骤

(1) B机器在公网上开启ssh监听;A机器在内网上开启监听。

tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      872/sshd     (B机器) 
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      2233 (A机器)

(2) 建立A机器到B机器的远程端口转发【A 机器上操作】。A机器将以ssh client身份建立到B机器的连接。需要注意由于A机器在内网,连接到B机器时会做一次SNAT替换源地址和源端口号。

命令:

ssh -fCNR <port_b1>:localhost:22 usr_b@B.B.B.B

我选取的 port_b1 为 8087,该步骤建立的 tcp 连接:

tcp        0      0 [A.A.A.A]:47102   [B.B.B.B]:22            ESTABLISHED 4851/ssh   (A机器)
tcp        0      0 [B.B.B.B]:22    [NAT(A.A.A.A)]:[NAT PORT] ESTABLISHED 819/sshd: root   (B机器)
tcp        0      0 127.0.0.1:8087          0.0.0.0:*         LISTEN      819/sshd: root (B机器)

<port_b1> 为B机器上端口,用来与A机器上的22端口绑定。可以看到,该步骤会建立 AB 之间的ssh连接,会在B机器上建立针对<port_b1>的监听。之后,在B机器上所有到127.0.0.1:8087的报文,会被ssh封装以后,通过AB之间的ssh连接发送给A机器(注意到后2个连接的pid都是819了吗?想一想为什么)。

(3) 建立B机器上的本地端口转发。做这一步是因为第二步配置后的8087端口只支持本地访问【B 机器上操作】

ssh -fCNL "*:<port_b2>:localhost:<port_b1>' localhost

<port_b2> 为本地转发端口(我设置了8086),用以和外网通信,并将数据转发到 <port_b1> (即上面选定的8087),实现可以从其他机器访问。

其中的*表示接受来自任意机器的访问。

该步骤会建立对 <port_b2> 即8086监听的TCP连接。

tcp        0      0 0.0.0.0:8086            0.0.0.0:*               LISTEN      7885/ssh    (B机器)

(4) 现在在C机器上可以通过B机器 ssh 到A机器

ssh -p <port_b2> usra@B.B.B.B

此时在B机器上会看到C机器发起的连接

tcp        0      0 [B.B.B.B]:8086      [C.C.C.C]:7742       ESTABLISHED 7885/ssh            

B 机器的8086监听的ssh进程(pid 7877)收到C的请求后,转给sshd:root(pid 819)

tcp        0      0 127.0.0.1:51728         127.0.0.1:8087          ESTABLISHED 7877/sshd: root   
tcp        0      0 127.0.0.1:8087          127.0.0.1:51728         ESTABLISHED 819/sshd: root

B 机器的sshd:root(pid 819)将报文通过ssh隧道发给A机器 client,报文通过8087端口号标记;A机器 ssh client(pid 4851)收到后,将报文解包后,向真正的server(ssh, pid 2233)建立连接,并将报文发给该A 机器的ssh server

tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      2233
tcp        0      0 ::1:42286                   ::1:22                      ESTABLISHED 4851/ssh   
tcp        0      0 ::1:22                      ::1:42286                   ESTABLISHED 2233

4 扩展阅读

4.1 非ssh服务

上述方案的目的是将内部的ssh服务开放出来,如果是非ssh服务,比如8080端口的web服务呢?实际也是类似的,只要第二步将localhost:22改为localhost:8080即可,这样C机器在web访问 B.B.B.B:[PORT B]时,实际访问的是A机器上配置的web服务。

A 机器并不需要理解C机器client发起的是http请求还是ssh请求,它看到的只是TCP层面,只要跟后端服务建立连接后,将C的报文往 后端服务丢就可以了。

4.2 SSH动态端口转发

有时内部网络提供的服务有多种,端口很多,如果一一配置会比较繁琐,此时可以使用SSH动态端口转发。

ssh -D <local port> <SSH Server>

dynamic

这里SSH创建是在内部SSH server上启动了一个socks代理服务。由于前面已经配置了从B机器到A机器的SSH链路,因此只要B机器上配置动态端口转发,并且在C机器上配置socks代理为B机器,就能实现在C机器上访问所有A机器(及其所在网络)的服务。

想想动态端口转发还能用来做什么?嘿嘿。

4.3 SSH参数解释

  • -f 后台运行
  • -C 允许压缩数据
  • -N 不执行任何命令
  • -R 将端口绑定到远程服务器,反向代理
  • -L 将端口绑定到本地客户端,正向代理

ref: