Nginx配合ngrok实现内网穿透

image

最近和微信公众号杠上了。和微信打过交道的叉子们肯定都见过微信这个配置。微信开发需要有一个公网IP,而我们绝大部分都用的公司的内网,所以每次都得自己在本地模拟微信请求(可以用[postman]),然后提交代码至服务器验证。一旦遇到错误需要重新发布,给调试代码带来极大不便,效率甚低。。。所以很多时候我的心情是这样的:

蓝瘦香菇

带着些许期待,遨游了一下google,于是就认识了它—[ngrok]。百度百科解释如下:

1
2
ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。
ngrok 可捕获和分析所有通道上的流量,便于后期分析和重放

顾名思义,「ngrok」这个东东就是一个反向代理,让本地运行的服务可以被外网访问。

一个令人悲伤的消息是 ngrok v2.x 官方已经闭源,那就意味着要收费了。不过,还好ngrok v1.x 是开源的,代码托管在github上。虽然「ngrok v1.x」已经不再开发和维护了,但是不影响其发挥作用。值得我们可以在自己服务器上折腾一番。

如果嫌自己搭建麻烦,可以使用国内的免费服务,如 http://ngrok.2bdata.com/, 感谢哪些无私贡献服务器的人~

但是~ 但是~ 作为一个爱折腾,爱捣鼓的码农,自给自足才是最大的乐趣。所以,在疯狂的折腾一天后,终于搞定了。。。不禁流下了幸福的汗水。。。

本次测试的「linux」版本是 Centos6

1
Linux 2.6.32-042stab113.21 #1 SMP Wed Mar 23 11:05:25 MSK 2016 x86_64 x86_64 x86_64 GNU/Linux

域名解析

我的域名DNS是在阿里云解析的,增加两条A记录只想服务器IP地址如下。DNS解析速度很快,一般修改后立马生效。

sudomain-seting

GO环境搭建

首页,我们要知道「ngrok」是用「go」语言写的,所以需要先安装「go」环境,安装「go」最新版本,采用源码安装,最新版本是「1.7.1」,注意:「1.4」以下版本会有编译问题,没有亲测,不敢尝试。

1
2
3
4
mkdir /usr/go # 创建go目录
cd /usr/go #切换目录
wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz
tar -xzvf go1.7.1.linux-amd64.tar.gz #解压文件

设置环境变量

1
2
3
4
5
#根据自己不同目录自行调整
echo 'export GOROOT=/usr/go/go' >> /etc/profile.d/go.sh
echo 'export PATH=$PATH:$GOROOT/bin' >> /etc/profile.d/go.sh
echo 'export GOPATH=$HOME/go' >> /etc/profile.d/go.sh
source /etc/profile #重现加载还将变量

注意,「GOPATH」如果设置不正确会出现找不到「package」错误。如果出现错误,可以将「GOPATH」值设置成「ngrok」根目录(具体原因没有仔究),更改后记得输入 source /etc/profile 命令更新环境变量。

检测是否安装成功

1
go version #go version go1.7.1 linux/amd64

git环境搭建

在编译「ngrok」的时候,出现了卡在下载某个东西的时候,找了好多资料才发现原来是「git」版本过低,版本必须 >=1.7.9.5方能正常编译。这个坑我踩了。唉~

1
gopkg.in/inconshreveable/go-update.v0 (download)

「git」安装及升级具体可参照官方文档:git 升级

以上依赖环境顺利安装好以后,就可以开始今天的主菜了。

下载「ngrok」源码

1
2
3
mkdir /usr/
git clone https://github.com/inconshreveable/ngrok.git ngrok #clone 源代码
cd ngrok

生成证书

ngrok.twindy.org 为列子

1
2
3
4
5
6
mkdir /usr/ngrok/cert #创建保存证书目录
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=ngrok.twindy.org" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=ngrok.twindy.org" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

执行完以上命令,就会在/usr/ngrok/cert目录下新生成6个文件

1
2
ls /usr/ngrok/cert # 文件列表
device.crt device.csr device.key rootCA.key rootCA.pem rootCA.srl

「ngrok」通过「bindata」将「ngrok」源码目录下的「assets」目录(资源文件)打包到可执行文件(「ngrokd」和「ngrok」)中去,assets/client/tls 和 assets/server/tls 下分别存放着用于「ngrok」和「ngrokd」的默认证书文件,我们需要将它们替换成我们自己生成的:(因此这一步务必放在编译可执行文件之前)

1
2
3
cp cert/rootCA.pem assets/client/tls/ngrokroot.crt
cp cert/device.crt assets/server/tls/snakeoil.crt
cp cert/device.key assets/server/tls/snakeoil.key

编译「ngrokd」和「ngrok」

注意,参数里可以设置操作系统位数、类型。傻傻的我,居然为了编译「window」版本,在「window」下又搭了一套环境重新编译。 无限笨~

1
2
3
4
make clean #clean
# 如果是32位系统,GOARCH=386; 如果是64为系统,GOARCH=amd64
# 如果要编译linux,GOOS=linux;如果要编译window,GOOS=windows
GOOS=linux GOARCH=amd64 make release-server release-client #liunx 、64bit

顺利的话,可以正常编译,在bin下面可以看到「ngrokd」和「ngrok」,其中「ngrokd」是服务端执行程序,「ngrok」是客户端执行程序。

ngrok-install

查看 「ngrokd」参数

1
2
3
4
5
6
7
8
9
ngrokd -h #查看参数说明
-domain string Domain where the tunnels are hosted (default "ngrok.com")
-httpAddr string Public address for HTTP connections, empty string to disable (default ":80")
-httpsAddr string Public address listening for HTTPS connections, emptry string to disable (default ":443")
-log string Write log messages to this file. 'stdout' and 'none' have special meanings (default "stdout")
-log-level string The level of messages to log. One of: DEBUG, INFO, WARNING, ERROR (default "DEBUG")
-tlsCrt string Path to a TLS certificate file
-tlsKey string Path to a TLS key file
-tunnelAddr string Public address listening for ngrok client (default ":4443")

启动「ngrokd」服务端

注意: http和https端口可以自己指定,这里不采用80端口,是因为其他程序已经占用了,端口转发在上面nginx已经配置完成

1
2
3
4
5
6
7
8
9
10
# domain填写刚才生成证书时填写的域名
# bin/ngrokd -domain="ngrok.twindy.org" -httpAddr=":8002" -httpsAddr=":8003"

# 如果想要后台启动,执行以下命令
nohup bin/ngrokd -domain="ngrok.twindy.org" -httpAddr=":8002" -httpsAddr=":8003" > /dev/null 2>&1 &

# 如果想要开机启动,执行以下命令
vim /etc/rc.d/rc.local
# 添加以下内容,具体内容请根据自己情况自行调整
{your-ngrok-dir}/bin/ngrokd -domain="ngrok.twindy.org" -httpAddr=":8002" -httpsAddr=":8003" > /var/log/ngrok.log &

####「ngrok」客户端连接

已下在「window」上测试「ngrok」客户端连接。

将生成的「ngrok.exe」下载至本地d:/ngrok/ngrok.exe,新建配置文件d:/ngrok/ngrok.cfg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server_addr: "ngrok.twindy.org:4443"
trust_host_root_certs: false
tunnels:
example:
subdomain: "example" #定义服务器分配域名前缀
proto:
http: 8000 #映射端口,不加ip默认本机
https: 80
web:
subdomain: "web" #定义服务器分配域名前缀
proto:
http: 192.168.1.100:80 #映射端口,可以通过加ip为内网任意一台映射
https: 192.168.1.100:80
web1:
hostname: "ngrok.your-domain.com"
proto:
http: 80
web2:
hostname: "your-domain.com"
proto:
http: 80
ssh:
remote_port: 50001 #服务器分配tcp转发端口,如果不填写此项则由服务器分配
proto:
tcp: 22 #映射本地的22端口
ssh1: #将由服务器分配端口
proto:
tcp: 21

启动「ngrok」客户端

1
2
3
4
5
6
7
8
cd d:\ngrok
ngrok -config=ngrok.cfg -subdomain weixin 8080

# 或者
ngrok -config ngrok.cfg start example

# 如果出现问题连接不上,想在本地查看日志,可加上log参数
ngrok -log ngrok.log -config ngrok.cfg start example

如果没有错误,则会出现以下结果:

1
2
3
4
5
6
7
Tunnel Status                 reconnecting
Version 1.7/1.7
Forwarding http://example.ngrok.twindy.org:8002 -> 127.0.0.1:8000
Forwarding https://example.ngrok2.twindy.org:8002 -> 127.0.0.1:8000
Web Interface 127.0.0.1:4040
# Conn 9
Avg Conn Time 114.23ms

到这里,内网已经成功穿透。但是,事情还还有结束!!!!

微信开发中的api地址,是不认80以外的端口的,也就是说虽然

1
http://example.ngrok.twindy.org:8002

外网已经可以访问本地服务,但是微信不认识。而我们又不能在「ngrokd」用80端口启动。因为80端口已经被「nginx」占用

1
bin/ngrokd -domain="ngrok.twindy.org" -httpAddr=":80"

怎么办?怎么办??

既然「nginx」占用了80端口,那么就在「nginx」上做文章。只要绑定example.ngrok.twindy.org 并将所有请求转发到8002端口即可。在服务器中新建/etc/nginx/conf.d/ngrock.twindy.org.conf文件,添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
upstream ngrok {
server 127.0.0.1:8002; # 此处端口要跟 启动服务端ngrok时指定的端口一致
keepalive 64;
}
server {
listen 80;
server_name *.ngrok.twindy.org;
#charset koi8-r;
access_log /var/log/nginx/log/ngrok.twindy.org.access.log main;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host:8002; #此处端口要跟 启动服务端ngrok 时指定的端口一致
proxy_set_header X-Nginx-Proxy true;
proxy_set_header Connection "";
proxy_pass http://ngrok;
}
}

重新加载「nginx」

1
nginx -s reload

此外,「ngrok」+ 「nginx」+「Docker」也可以完美实现,由于对Docker」不了解没有去折腾
参考地址:搭建自己的 Ngrok 服务器, 并与 Nginx 并存

至此,「ngrok」内网穿透成功实现。幸福的汗水。。。

总结:

  • 今天遇到坑太多,还好之前自学过「nginx」,虽然折腾了一天最终还是成功了,满满的成就感。
  • coding无止境。