基于Docker构建ngrok+nginx内网穿透(80,443可用).md
ngrok介绍
ngrok
是一个反向代理软件,最早是因为微信调试需要而使用。后来发现用途不少,我常用的主要是以下方面:
- 微信调试;
-
webhook
需要,比如第三方登陆或者支付的webhook
; - 对隧道中的
HTTP
请求抓包分析; - 将局域网的
SSH
端口映射出来(比如你的朋友请你远程技术支持linux
,通过此方式可以直接ssh
到对方电脑,不要用慢吞吞的远程桌面了)
为什么不使用现成的呢?
- 国外的不能用,主要原因是微信调试要求域名是备案的。
- 国内的免费,不过大部分端口都不是
80
和443
端口,另外一个是不太稳定,经常要换客户端。 - 国内收费服务。我自己没使用过,另外一个我有服务器可以有更高灵活性,于是打算自己也搭一个。重点就是解决端口问题和稳定性问题。
准备工作
两台电脑:
- 一台装有
docker
的公网电脑当服务端(笔者使用的电脑操作系统是Centos 7.2
) - 一台
PC
电脑当客户端
知识要求:
-
nginx
的基础知识,特别是反向代理的配置; -
docker
的基础知识;
特别强调:在nginx
+ngrok
配合的情况下,不要在同一台电脑上同时起服务端和客户端,否则可能出现反复连接断开的现象
快速搭建
注意点
- 关于
ngrok
首先要强调一下,ngrok
的服务端与客户端是配套的,所以随便网上找客户端连接自己编译的服务端是不能用的。 -
DNS
的记录记得修改; -
ngrok 2.x
版本是不开源的,因此我们使用的是ngrok 1.x
的版本;
操作步骤:
所有的示例都在 https://github.com/pheye/dockers.git的web
目录下,通过以下命令获取源码:
$git clone https://github.com/pheye/dockers.git
$cd dockers/web/
$cp env.example .env
然后按如下操作:
1.将.env
的NGROKD_DOMAIN
也改成你的域名。
2.修改身份认证的文件,即客户端必须传递正确的auth_token
参数才能正常连接,防止网址公开后任意人都可以使用
$cp ngrok/ngrok-secrets{.example,}
该文件的格式为:
# username password
example-user example-password
3.编译映像
$docer-compose build #耗时很长
编译映像时需要注意,一定要通过上面的命令,而不要进入
ngrok
命令然后通过docker build
手动编译映像,会提示NGROK_BASE_DOMAIN
环境变量不存在,如需手动编译,请自行做相应修改。
4.修改nginx
配置
将ngrok.conf
的server_name
改成自己的域名。
5.启动容器
$docker-compose run -d
6.下载客户端
正如前面所说,客户端与服务端是配套的。这时客户端在容器中,需要将其复制出来.
通过docker ps
找到ngrokd
容器的名称,比如web_ngrokd_1
,将客户端复制到宿主机中:
$docker cp web_ngrokd_1:/ngrok bin
其中bin
目录下的内容:
ngrok # linux下的客户端
darwin_amd64/ngrok # mac下的客户端
windows_amd64/ngrok # windows下的客户端
ngrok.cfg # ngrok客户端配置文件,将其移到客户端机器的~/.ngrok,或者在启动时加-config=ngrok.cfg参数
ngrok.cfg
的内容如下:
server_addr: tunnel.papamk.com:4443 #应改成自己的域名
trust_host_root_certs: false #必须为false
auth_token: abc:17605033425abc #前面ngrok-secrets配置的<user>:<password>的组合
tunnels: #预先配置好通道,主要是为了演示,不是必须的
ssh:
proto:
tcp: "22"
remote_port: 2222
web:
subdomain: abc
proto:
http: 80
https: 80
7.修改DNS记录
,添加2
条A
记录到你主机的IP
,以tunnel.papamk.com
为例:
tunnel.papamk.com <你的IP>
*.tunnel.papamk.com <你的IP>
8.客户端启动ngrok
:
首先确保ngrok -v
的版本是在1.x
,以及~/.ngrok
已经配置好,然后启动客户端,看到Status为online
就是成功了,同时可以看到映射端口都是标准端口:
$ngrok start web
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding https://abc.tunnel.papamk.com -> 127.0.0.1:80
Forwarding http://abc.tunnel.papamk.com -> 127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
关于ngrok
的更多用法,推荐:https://imlonghao.com/27.html。
搭建过程关键点分析
我们推荐先根据此教程在宿主机上熟悉下ngrok
的搭建过程:http://zpblog.cn/linux/run-ngrok-on-your-own-server.html,然后再看下面的说明。
ngrok
服务端与客户端的构建由Dockerfile
完成,完整内容见:https://github.com/pheye/dockers/blob/master/web/ngrok/Dockerfile
nginx
下面的ngrok.conf
实现到ngrok
的反向代理,完整内容见:https://github.com/pheye/dockers/blob/master/web/nginx/conf.d/ngrok.conf
关键点:
-
ngrok/Dockerfile
是构建ngrokd
映像的描述文件,其中,ngrok
的源使用的是https://github.com/prikevs/ngrok.git
,而不是官方ngrok
的源码。官方源码不需要任何认证就可以连接上,太容易被人盗用,具体见《客户端安全问题》的讨论。 -
ngrok/Dockerfile
映射了2222
和4443
端口,而没有映射80
和443
端口。2222
映射的目的见《tcp端口的转发问题?》的讨论,4443
是ngrok
的控制端口,所以必须映射;而80
和443
通过nginx
反向代理,所以没必要开放。 - 编译后的所有文件(证书与二进制文件)都在容器的
/ngrok
目录下,宿主机需要从容器中复制,并根据自己的客户端操作系统选择对应的客户端,见“操作步骤-5”,如果客户端不能满足你的需求,修改Dockerfile
重新build
即可。 - 为了确保
nginx
和ngrok
共存,并且都使用的是80
和443
端口,应该将nginx
的80
和443
映射到主机,而ngrok
不映射。然后所有到ngrok
的HTTP
请求都通过nginx
反向代理实现,
几个问题
客户端安全问题?
默认的ngrokd
服务器,只要知道地址就能使用,是非常不安全的。毕竟是私人服务器,最好加个身份认证。具体可参考本文章。
给ngrok加上身份认证
如果要公开使用,仅仅是验证用户名和密码其实是不够的,应该与域名绑定才能控制得住。能通过改ngrokd
的源码当然最好。次点的方法,通过nginx
做限制,只转发允许的域名,其他域名都不转发。
找到ngrok.conf
的server_name
:
server_name tunnel.papamk.com *.tunnel.papamk.com;
应该将通配域名改为允许访问的域名,比如a.tunnel.papamk.com
,b.tunnel.papamk.com
,在该文件中一一枚举出来。量大时显然不太方便,特别是要同时改80
和443
两处的server_name
。因此我做了个脚本简化此过程(https://github.com/pheye/dockers/blob/master/web/scripts/setdomain.sh):
先修改ngrok/ngrok-domains.example
示例文件,里面为允许的域名,文件格式为每行一个域名:
tunnel.papamk.com
abc.tunnel.papamk.com
然后通过scripts/setdomain.sh
脚本完成替换:
scripts/setdomain.sh ngrok/ngrok-domains.example nginx/conf.d/ngrok.conf
该方法带来另外一个问题,没在白名单的其他域名可能匹配到nginx
的其他server
,给用户造成困惑。为解决此问题,没在白名单的域名应该提示出错才是比较好的做法,解决方法是添加一个nginx/conf.d/ngrok-forbid.conf
(示例工程有提示nginx/conf.d/ngrok-forbid.conf.example
)
server {
listen 80;
listen 443;
server_name *.tunnel.papamk.com;
ssl off;
location / {
default_type text/html;
add_header Content-Type 'text/html; charset=utf-8';
return 200 "您未被授权访问,请联系管理员xxx@163.com";
}
}
tcp端口的转发问题?
当本地的22
端口开放出去,如下:
./ngrok -proto=tcp 22
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding tcp://tunnel.papamk.com:43343 -> 127.0.0.1:22
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
然而43343
在宿主机上并没有映射,所以实际上没办法连接。需要通过iptables
添加一条转发记录,对于自建服务器的同学这样显然是不方便的。
解决方案是:
1.客户端,编辑~/.ngrok
,在tunnels
下面添加ssh
的通道,手动指明远程端口:
server_addr: tunnel.papamk.com:4443
trust_host_root_certs: false
auth_token: abc:17605033425abc
tunnels:
ssh:
proto:
tcp: "22"
remote_port: 2222
web:
subdomain: abc
proto:
http: 80
https: 80
然后通过以下命令启动服务,远程端口2222
就映射到了本地的22
端口:
ngrok start ssh
2.服务端,ngrokd
的docker
在启动时就映射2222
端口。(docker
实际上也是借助iptables
完成的)
https的证书问题?
访问https
时,Chrome
会提示证书不安全。ngrok
需要的是wildcard
的证书,没找到比较好用的并且免费的。之前生成证书,一直都是使用Let's encrypt
,不过不支持wildcard
。好消息是,根据官方消息,2月份将会提供支持:
First, we’re planning to introduce an ACME v2 protocol API endpoint and support for wildcard certificates along with it. Wildcard certificates will be free and available globally just like our other certificates. We are planning to have a public test API endpoint up by January 4, and we’ve set a date for the full launch: Tuesday, February 27.
因此,要么等到那时看看。要么买一个支持wildcard
的收费证书。