使用SSH反向隧道进行内网穿透
# 前言
最近在摸索持续构建,正如之前的文章《DKIpaBuilder shell实现自动打包ipa并上传到fir.im》所言,“现在我们需要一个 mac 服务器,但是租赁的价格非常昂贵。网上有人用 mac mini 来当 macOS 服务器,但是并不在外网环境,Coding、GitHub 等平台的 webHook 接收不到。”
于是有个想法,利用 SSH 反向隧道穿透 NAT,连接内网的 mac mini 服务器。
# 目标
现在有位于公网的 VPS 一台,位于 NAT 之后的 Mac Mini 一台,本机 Macbook Pro 一台,分别取代号 A、B、C。
机器代号 | 机器位置 | 地址 | 账户 | ssh/sshd 端口 | 是否需要运行sshd ---|--- A | 位于公网 | bingo.ren | root | 22 | 是 B | 位于NAT之后 | localhost | dankal | 22 | 是 C | 位于NAT之后 | localhost | bingo | 22 | 否
A 上的 WebServer 为 Nginx,通过 location 配置规则,把 webhook 的请求反向代理到了 Nodejs 监听的端口 (eg. 7777)
目标是实现自动化部署需求:当代码提交并 Push 到 GitHub 或者 Coding 等平台时,平台往位于公网的 A 发送一个 Http Request 传递 Push Event,A 上的 nodejs 触发 shell 脚本,通过 ssh 通道连接并执行 B 上的 Shell 脚本,B 上的 Shell 脚本执行 fastlane 命令实现持续构建,自动实现单元测试、屏幕截图、打包、发布等操作。
所以,搞这么多,最基本的就是要穿透内网,这就是现在首先要做的事情。
# 稳定性维持
不幸的是 SSH 连接是会超时关闭的,如果连接关闭,隧道无法维持,那么 A 就无法利用反向隧道穿透 B 所在的 NAT 了,所以我们需要一种方案来提供一条稳定的 SSH 反向隧道。
一个最简单的方法就是 autossh
,这个软件会在超时之后自动重新建立 SSH 隧道,这样就解决了隧道的稳定性问题。
# 安装 autossh
$ brew install autossh
# 配置公网 VPS(A)
# 修改配置文件
首先在 A 上编辑 sshd 的配置文件 sshd_config,将 GatewayPorts 开关打开
A $ vim /etc/ssh/sshd_config
GatewayPorts yes
2
3
在 CentOS 下默认是
#GatewayPorts no
,把注释打开,然后把 no 改为 yes;在 Ubuntu 下默认没有 GatewayPorts,直接补一行
GatewayPorts yes
即可
# 重启 sshd 服务
A $ service sshd restart
# 防火墙开放端口
以 6766 和 6777 为例,可自由替换。
B $ sudo iptables -I INPUT -p tcp --dport 6766 -j ACCEPT
B $ sudo iptables -I INPUT -p tcp --dport 6777 -j ACCEPT
2
# 配置内网 Mac Mini (B)
# 新建用户
在 B 上新建一个用户 dankal,由于 macOS 是基于 Unix 系统,暂时还没有找到新建用户的命令,Linux 下可以用下面的命令新建用户。
B $ sudo useradd -m dankal
B $ sudo passwd dankal
2
而在 mini 上,暂时基于 GUI 操作,系统偏好设置 -> 用户与群组 -> 点按锁按钮以进行更改 -> 添加用户账户。
# 配置 SSH 密钥
# 创建密钥
B $ su dankal
B $ ssh-keygen -t 'rsa' -C 'dankal@mini'
2
注意该密钥不要设置密码,也就是运行 ssh-keygen 命令时尽管一路回车,不要输入额外的字符。
# 上传到 VPS(A)
B $ ssh-copy-id root@bingo.ren
以往我都是 scp 上传公钥到服务器上,然后再 cat >> 写入 authorized_keys 文件。
这里用 ssh-copy-id 可以不传公钥,一步实现上面的操作。
# 反向隧道
由 B 向 A 主动建立一个 SSH 隧道,将 A 的 6766 端口转发到 B 的 22 端口上,只要这条隧道不关闭,这个转发就是有效的。
通过上面的步骤,安装完的 autossh 默认在 /usr/local/Celler/autossh 目录下,而 autossh.sh 脚本的地址为 /usr/local/Celler/autossh/1.4e/bin/autossh.sh。
B $ cd /usr/local/Celler/autossh/1.4e/bin/
B $ ./autossh -p 22 -M 6777 -NR '*:6766:localhost:22' root@bingo.ren
2
-M 参数指定的端口用来监听隧道的状态,与端口转发无关。
有了这个端口转发,只需要访问 A 的 6766 端口反向连接 B 即可。
# 连接
现在可以在 A 上使用这条反向隧道穿透 B 所在的 NAT,SSH 连接到 B:
A $ ssh -p 6766 dankal@localhost
或者是在 C 上直接穿透两层 NAT,SSH 连接到 B:
C $ ssh -p 6766 dankal@bingo.ren
# TODO
如果 B 重启隧道就会消失。那么需要有一种手段在 B 每次启动时使用 autossh 来建立 SSH 隧道。在 linux 下可以使用 systemd 做成服务来解决,在 macOS 下暂时还没有找到解决方案。
动态端口转发,在 VPS(A)安装 ShadowsocksX,SOCKS v5 代理实现访问 Mini(B)上的网页。
# 后话
实现内网穿透并不难,但对于 Linux/Unix 操作系统的使用的熟练度有一定的要求,我在 ssh、sudoers、iptables 等上面花了不少时间,也踩了挺多的坑。