0x01 前言

因为我博客的后端服务器有一台在我家中,为了提升服务的可靠性,经过权衡后我最终选用SSH隧道承载nginx反向代理的流量。

前些天我在知乎发布了一篇专栏文章:

其中详细描述了我博客的服务架构,目前的版本为3.0,简图如下:

从上图可以看到,在我家中有一台后端服务器,这样的架构可以大大地节省云服务器的费用支出。但受限于家庭宽带,绝大部分端口屏蔽了主动传入的流量或者没有公网IP,这时候我有2种解决方案,一种是采用VPN,另一种就是本文要说的SSH端口转发。

因为我的服务器都是centos,为减少工作量与维护难度,我选择了SSH端口转发。

0x02 分析

SSH端口转发分两种,一种是在本地监听端口,将数据转发到远端;另一种是在远端监听端口,将数据转发到本地。

例子1:我在腾讯云 CN 2服务器上部署了mysql服务,但希望在本地的home server建立一个网关,以便让本地的服务都可以通过这个网关连接到腾讯云 CN 2的mysql服务。这就需要将远端的3306端口映射到本地,这时候可以通过SSH在本地监听一个端口,所有通过该端口的请求都会被转发到腾讯云CN 2。

例子2:反过来,我在本地的home server部署了mysql服务,但希望腾讯云 CN 2服务器上的服务也可以连接该服务,这就需要将本地的3306端口映射到腾讯云 CN 2上,因此需要在腾讯云 CN 2上监听一个端口,以便让该服务器上的服务访问home server的mysql服务。

注意!以上两个例子都是以home server为SSH client,腾讯云 CN 2为SSH server。

图片来源:https://unix.stackexchange.com/questions/46235/how-does-reverse-ssh-tunneling-work

以上图为例,左侧的图形适用于例子2,右侧的图形适用于例子1。

其实SSH隧道只有本地和远端的概念,如果是将远端的端口映射到本地,在本地监听并转发到远端,则需要使用“-L”参数,类似正向代理;如果将本地端口映射到远端,在远端监听并转发到本地,则需要使用“-R”参数,类似反向代理。

0x03 准备

因为SSH代理需要监听端口,因此会用到root用户;另外家庭宽带每48小时自动断开,为提供可靠性,需要实现自动连接SSH,这就需要配置SSH免密登录。

注意!因为需要实现SSH免密登入的功能,为了确保数据与系统的独立性,请选择安全性较好且可靠地服务器作为client。

例如我需要确保home server的数据安全,且确认home server比腾讯云 CN 2服务器更可靠,这时候可以在home server上执行SSH命令。

另外还有一个因素,因为家庭宽带一般不允许从外部主动连入,因此建议通过家庭宽带内部的服务器执行SSH命令。

确定好client与server端后,需要分别为两者建立SSH私钥与公钥。先通过root用户登入系统,然后执行以下命令:

[root@home-server ~]# ssh-keygen -t rsa -b 4096

在交互中直接回车即可,请勿为私钥设置密码,否则在后续的操作中将要求输入密码:

而后重命名公钥:

[root@home-server ~]# mv .ssh/id_rsa.pub .ssh/authorized_keys

为了能使home server通过免密的方式登入tc-cloud,需要将home server的公钥添加至tc-cloud的authorized_keys中:

# 打印出home-server的公钥
[root@home-server ~]# cat .ssh/authorized_keys | grep home-server
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCjLf83WX/kqrv6bUORMDtFK5RP/pP5Tr8Iep2HIA5l+rI6mqDQ8VvaZyogDBw9pasdWWIRjj9TxaT1ktMff5rrubDHg1ZC8MGMl3K1/ycDJtugopztAz+1YqecMj3buCd59WoVk6V0jVODXRBTnSwGaUXR+9gtMxn/Xcgpvimuh4ivR0ZJe5cPoiQQQq1sOooZ0Dg/T8hPvRwUtmWCIg2eukwEjGsHbO19Zy4mAyam4D4IqqEGVna6MGQyhTdoxi8JQRGpre/QBD5tkBxoK9qNZ/2wpXbO8IOY6oPdKN0ID0QEf5YvMWcUNF0fJ48jQobCGpVHXDhMSojEuPs4SaELqZSGbfIEDW2c7+oVZ9n+F1O7VQkSMpkkjix3ya9qy7W2Muwcj7a0boqESOEfeT+KhoK25jVUHMPsZpfVVUDlkBzRu1b1xOzPGgFffQyORwJ83SyQyE8DOv0S9iXwuGfoOebou3S/WPoWJ2oyL8gJo6zcjBIYEb1HCtOQ7YZvafGoQE3G8kGbcbEvv2OFSuY4vz/GCuV32+YnOYx+znODR5WBo5KEkH+jr0hDRTBpSNrmi6DUrbUK07tRuMnv5O397Q4B8xrayhZUS0lJ1ibunbZM9oGWIhH1AyD/wa+iDYyxJ+dlWvVtZrjMoip80PwAvR29TayC/eiW6qH9SLE+MQ== root@home-server

# 将home-server公钥添加至tc-cloud的authorized_keys文件最后一行
[root@tc-cloud ~]# vim .ssh/authorized_keys

为了安全起见,请在tc-cloud(远端服务器)禁止通过密码登入SSH,然后允许root登入:

# 打开SSH配置文件
[root@tc-cloud ~]# vim /etc/ssh/sshd_config 

# 删除PermitRootLogin配置的注释符号并设为yes
PermitRootLogin yes

# 删除PasswordAuthentication配置的注释符号并设为no
PasswordAuthentication no

而后分别在两台服务器上重新启动SSH服务:

[root@home-server ~]# systemctl restart sshd

最后在home-server上测试SSH免密登入是否成功:

[root@home-server ~]# ssh [email protected]
The authenticity of host 'tc-cloud.t.com (10.1.1.115)' can't be established.
ECDSA key fingerprint is SHA256:FQi7NN+ckssUdFR8/BZKoL7yYzvIrEGUFZPGpPrfBOU.
ECDSA key fingerprint is MD5:bc:e1:a2:83:d4:81:db:d1:73:41:80:1d:ee:b4:54:f6.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'tc-cloud.t.com,10.1.1.115' (ECDSA) to the list of known hosts.
Last login: Sun Aug 26 15:43:45 2018 from terence-pc.t.com
[root@tc-cloud ~]# 

确认功能正常后即可开始配置端口转发功能。

0x04 使用

再继续之前,请先安装autossh,它可以实现SSH自动重连的功能:

[root@tc-cloud ~]# yum install autossh -y

0x04.1 本地监听/正向代理

基本的命令如下:

autossh -M [心跳包端口] -fCNL [本地IP,可省略]:[本地端口]:[远端IP]:[远端端口] [ssh用户名@远端域名或IP]

而后即可撰写SSH端口转发的命令:

autossh -M 10001 -fCNL 10.1.1.115:10002:127.0.0.1:3306 [email protected]
  • -M:autossh的参数,用于监控SSH连接状态,autossh会向该端口发送测试数据,若无返回则认为SSH连接中断,此时会自动重连直至成功,类似心跳包;
  • -f:SSH的参数,将SSH放置在后台运行;
  • -C:SSH的参数,用于数据压缩;
  • -N:SSH的参数,不执行远程指令,请务必配置该参数;
  • -L:SSH的参数,将本地的数据转发到远端。

以上命令的意义为:在本地监听TCP 10001端口,用于心跳包的发送与接收;在后台运行SSH端口转发服务,在本地IP 10.1.1.115上监听10002这个端口,并将该端口的数据转发到tc-cloud.t.com的3306端口。

如果使用本地监听,建议把本地IP添加在命令中,以提高安全性。

0x04.2 远端监听/反向代理

基本的命令如下:

autossh -M [心跳包端口] -fCNR [远端IP,可省略]:[远端端口]:[本地IP]:[本地端口] [ssh用户名@远端域名或IP]

在我服务架构中用得比较多的是远端监听端口,命令如下:

autossh -M 10003 -fCNR 127.0.0.1:10004:127.0.0.1:8081 [email protected]
  • -R:SSH的参数,将远端的数据转发到本地。

以上命令的意义为:在本地监听TCP 10003端口,用于心跳包的发送与接收;在后台运行SSH端口转发服务,在tc-cloud.t.com的IP 127.0.0.1上监听10004这个端口,并将该端口的数据转发到本地127.0.0.1的80端口。

0x04.3 检查

在home-server中执行以上命令后,通过以下命令即可查看相关进程:

[root@home-server ~]# ps -aux | grep ssh | grep 100
root      1616  0.0  0.0   6512   472 ?        Ss   17:02   0:00 autossh -M 10001 -CNL  10.1.1.115:10002:127.0.0.1:3306 [email protected]
root      1617  0.2  0.2 181068  4976 ?        S    17:02   0:00 /usr/bin/ssh -L 10001:127.0.0.1:10001 -R 10001:127.0.0.1:10002 -CNL 10.1.1.115:10002:127.0.0.1:3306 [email protected]
root      1623  0.0  0.0   6512   472 ?        Ss   17:02   0:00 autossh -M 10003 -CNR  127.0.0.1:10004:127.0.0.1:8081 [email protected]
root      1624  0.1  0.2 181064  4976 ?        S    17:02   0:00 /usr/bin/ssh -L 10003:127.0.0.1:10003 -R 10003:127.0.0.1:10004 -CNR 127.0.0.1:10004:127.0.0.1:8081 [email protected]

当SSH成功连接后,可以看到有“/usr/bin/ssh”相关的进程;若无,则表示SSH尚未连接,此时需要检查message日志。

通过以下命令查看监听的端口:

[root@home-server ~]# netstat -anp | grep ssh | grep LIST
tcp        0      0 127.0.0.1:10001         0.0.0.0:*               LISTEN      1617/ssh            
tcp        0      0 127.0.0.1:10002         0.0.0.0:*               LISTEN      1616/autossh        
tcp        0      0 127.0.0.1:10003         0.0.0.0:*               LISTEN      1624/ssh            
tcp        0      0 127.0.0.1:10004         0.0.0.0:*               LISTEN      1623/autossh        
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1257/sshd

在tc-cloud中执行相关命令,也可以看到监听的端口。

再测试业务,若可以接受与发送数据,则说明端口转发正常。

0x05 结语

在我的业务场景需要该服务在服务器启动时启动,此时可以将其加入rc.local文件中。

在实际应用中,端口数量较少的情况,用SSH端口转发会较为方便。在端口较多且相互依存的情况下,SSH端口转发的配置会非常繁琐,此时建议使用VPN承载数据。

另外,在同一台服务器中,autossh命令的心跳包端口不可复用,各个监听的端口也不可复用。