跳到主要内容

SSH内网穿透

· 阅读需 12 分钟

对于内网服务器,如果我们想从外网访问,可以借助一台拥有外网IP的云服务器,通过建立SSH反向隧道来实现访问内网服务器。

SSH隧道

首先,修改云服务器的/etc/ssh/sshd_config,在该文件的最后添加:

GatewayPorts yes
ClientAliveInterval 60
ClientAliveCountMax 3

然后重启云服务器的sshd服务使上述配置生效:

$ sudo systemctl restart sshd

如果不进行上述配置,将只有登录云服务器才可以外网访问内网服务器。

然后,内网服务器向云服务器主动建立SSH连接,并将内网服务器的22号端口转发到转发端口(任一空闲端口即可):

$ ssh -NR 转发端口:localhost:22 云服务器用户名@云服务器IP -p 云服务器SSH端口

注意,需要在云服务器的控制台配置允许转发端口的访问

云服务器SSH端口默认是22号端口,如果你修改了SSH连接的默认端口号,需要使用-p参数指明端口号,否则可以省略-p 云服务器SSH端口

其中,-N表示禁止执行远程命令,-R进行远程端口转发。如果要后台运行,可以用-f选项。但是,如果没有配置密钥登录,将需要输入密码,使用后台运行将运行失败。

最后,就可以从外网访问内网服务器了:

$ ssh 内网服务器用户名@云服务器IP -p 转发端口

【例子】

假设云服务器IP为123.123.123.123,用户名为demo,使用默认的22号端口进行SSH登录(所以可以省略-p参数),使用转发端口为6666。首先需要在云服务器的控制台配置允许6666端口的访问,然后在内网服务器运行:

$ ssh -NR 6666:localhost:22 demo@123.123.123.123

输入完密码后,如果连接成功,会保持,不是卡住了。

假设内网服务器有个用户是jlice,那么在外网可以通过下面的命令登录内网服务器:

$ ssh jlice@123.123.123.123 -p 6666

如果只是临时进行内网穿透,比如使用SSH进行远程协助,那么像这样就已经可以满足需求了。不过,如果需要长时间使用,由于SSH的连接并不稳定,可以使用autossh来保持SSH的连接。为此,需要配置SSH密钥登录(也就是SSH登录时不需要输入密码),然后运行autossh,后文还介绍了如何使autossh开机启动。

云服务器端口配置

在上一步里,需要开放云服务器的端口,这里介绍下阿里云和腾讯云的配置方法。

以阿里云ECS为例,登录阿里云的ECS控制台,配置安全组。如果没有安全组,创建一个安全组。点击管理实例,可以把云服务器添加到安全组,重启云服务器生效。

安全组应该已经配置了允许SSH使用的22号端口。阿里云的安全组出方向默认允许所有访问,即从安全组内ECS访问外部都是放行的。因此只要配置入方向即可。

点击配置规则,这里需要开放一些端口以供上一步提到的转发端口和后面提到的监听端口来使用,协议类型为TCP。例如,开放6666~8888端口:

授权对象填0.0.0.0/0表示允许所有IP访问。因为我们一般使用的不是固定IP地址上网,所以这样配置。如果使用固定IP地址(外网IP)上网,这里也可以配置成只允许自己的IP连接。

腾讯云的安全组配置是类似的:

密钥登录

在上述过程中,我们可以通过密钥来从内网服务器SSH登录到云服务器,只需要把内网服务器的~/.ssh/id_rsa.pub的内容加入到云服务器的~/.ssh/authorized_keys即可。

注意:authorized_keys应该是内网服务器SSH登录云服务器时使用的用户的home目录下的,而不是其它用户home目录下的。假设如之前的例子,内网服务器使用demo用户SSH登录到云服务器,默认demo用户的home目录是/home/demo,那么,authorized_keys应该位于/home/demo/.ssh/authorized_keys,而不是/home/other/.ssh/authorized_keys之类的。

如果内网服务器没有~/.ssh/id_rsa.pub,可以在内网服务器上通过ssh-keygen命令来生成,过程中直接按回车即可。

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/jlice/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/jlice/.ssh/id_rsa.
Your public key has been saved in /home/jlice/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:ESR3+MCGxlrJCdy6pSD4PpGQok3+cyD6NVFIFqPBqF8 jlice@vbox
The key's randomart image is:
+---[RSA 2048]----+
| o..**.*+.. |
|. .=.oXo=o |
|o.. .=...o |
|B o E.. .. |
|oO +.+ S |
|. O +. |
| o +o. |
|. o.o.. |
| ... o |
+----[SHA256]-----+

例如,还是使用之前的例子,那么,内网服务器SSH连接云服务可以使用:

$ ssh -NR 6666:localhost:22 -i ~/.ssh/id_rsa demo@123.123.123.123

注意,这里用id_rsa而不是id_rsa.pub。如果不需要输入密码即成功连接,那么密钥登录配置成功,就可以在外网通过ssh访问内网服务器了。

autossh

由于SSH的连接并不稳定,为了维持连接,可以使用autossh。它需要一个额外的监听端口,用-M来指定,用来监视SSH的连接状态。

首先,安装autossh,直接安装即可:

$ sudo apt install autossh

然后,内网服务器使用autossh向云服务器主动建立SSH连接,并将内网服务器的22号端口转发到转发端口(监听端口和转发端口选择空闲端口即可,必须使用两个不同的端口号):

$ autossh -M 监听端口 -NR 转发端口:localhost:22 -i ~/.ssh/id_rsa 云服务器用户名@云服务器IP -p 云服务器SSH端口

注意,需要在云服务器的控制台配置允许监听端口和转发端口的访问

如果配置了密钥登录,这里将不需要输入云服务器的密码。如果要后台运行,可以用-f选项。但是,如果需要输入密码,启动SSH会失败且无提示。其它原因导致连接SSH失败也无提示。

最后,就可以从外网访问内网服务器了:

$ ssh 内网服务器用户名@云服务器IP -p 转发端口

【例子】

例如在前面“云服务器端口配置”中,我们配置了6666~8888端口可访问,然后使用6666作为转发端口,这里以7777作为监听端口为例(注意,必须使用不同的端口),运行autossh

$ autossh -M 7777 -NR 6666:localhost:22 -i ~/.ssh/id_rsa demo@123.123.123.123

如果运行成功,autossh将会开启ssh,为了验证,运行

$ ps aux | grep ssh

应该可以看到启动了如下的进程:

/usr/bin/ssh -L 7777:127.0.0.1:7777 -R 7777:127.0.0.1:7778 -NR 6666:localhost:22 -i /home/jlice/.ssh/id_rsa -p 22 demo@123.123.123.123 &> /dev/null

此时,就可以在外网登录内网服务器了:

$ ssh jlice@123.123.123.123 -p 6666

另外,并不需要为内网每个用户都进行类似上面的配置。只要有一个内网用户像上面这样SSH连接了云服务器,其它内网用户就都可以在外网连接内网服务器了。

开机启动

SSH 连接远程主机时,会检查主机的公钥。如果是第一次该主机,会显示该主机的公钥摘要,提示用户是否信任该主机,这样会导致自动连接失败。因此,需要先在内网服务器上SSH登录云服务器,然后确认信任云服务器,这样会将云服务器的摘要添加到~/.ssh/known_hosts,之后就不会有是否信任的提示了,也就可以自动连接了。

TIP:如果你按本文的步骤,手动在内网SSH连接云服务器,那么,应该已经遇到过是否信任远程主机的提示,并选择了yes,那么,后面就没有是否信任的提示了。如果没有手动在内网SSH连接云服务器,那么,可以手动连接一下,然后在提示是否信任主机时选择yes。

目前systemd已代替init接管了开机启动的配置,需要配置文件来配置开机启动的服务项。

为了让autossh开机启动,创建文件/lib/systemd/system/autossh.service并编辑:

$ sudo vi /lib/systemd/system/autossh.service

内容为(替换相应内容,如内网服务器用户名、转发端口等):

[Unit]
Description=Auto SSH Tunnel
After=network-online.target
[Service]
User=内网服务器用户名
Type=simple
ExecStart=/usr/lib/autossh/autossh -M 监听端口 -NR 转发端口:localhost:22 -i /home/内网服务器用户名/.ssh/id_rsa 云服务器用户名@云服务器IP -p 云服务器SSH端口 &> /dev/null
Restart=always
RestartSec=60
[Install]
WantedBy=multi-user.target

建议先手动运行下ExecStart里配置的命令,以排查可能出现的问题。

然后启动autossh服务:

$ sudo systemctl start autossh

autossh开机启动:

$ sudo systemctl enable autossh

查看autossh的状态:

$ systemctl status autossh

如果启动失败,可能是云服务器的端口已被占用,或者内网服务器尚未添加云服务器到已知主机等原因。