Ngrok实现内网穿透

在一些内部环境,我们经常需要在外网访问到某些web服务。比如在自己家里的宽带网络里,想让智能家居硬件能够让手机流量网络访问到;比如参加黑客编程马拉松,手头上笔记本运行的程序想要接入外网演示;比如想要在家里开发第三方支付等等,就需要借助内网穿透来给外网访问到这些服务。

常见的商业应用有花生壳,处于安全性考虑,希望自己也能够搭建一个内网穿透的服务。Ngrok则是一个不错的内网穿透的开源软件,可以方便地自由搭建内网穿透服务。本文详细介绍了Ngrok的安装,泛解析域名的配置,以及将ngrok添加为系统服务,并使用nginx配置反向代理。

一、基本原理

ngrok分为服务端和客户端:

  • ngrok客户端:在需要开启内网穿透的PC上,安装内网电脑上的终端软件,ngrok客户端来把本地端口提供给外网。
  • ngrok服务端:ngrok客户端启动后默认会使用ngrok官网的域名代理,由于ngrok官网的域名代理服务器在美国,很多时候需要自建ngrok服务端。
  • 用户客户端可以通过访问绑定在ngrok服务端的域名,从而访问到内网的服务。

本文章中,可能会提到一些例子,其中具体数值可以自己替换:

项目 说明
xxx.com 顶级域名
xxx.xxx.com 二级域名
*.xxx.xxx.com 二级泛解析域名

二、Ngrok安装方法

1、安装依赖环境

yum install -y gcc git openssl golang

2、检查环境

git --version
go version

git 版本 >= 1.7 go 版本 >= 1.8

3、下载ngrok源码

git clone https://github.com/inconshreveable/ngrok.git /usr/local/ngrok

4、生成ngrok证书

这里生成ngrok证书,与绑定ngrok服务端域名有关,所以务必认真检查域名。

方法一:自己构造SSL证书

为了方便可以配置一个系统变量

vim /etc/profile

在文件末尾添加一行

export NGROK_DOMAIN="xxx.com"

保存后,执行source使变量生效

source /etc/profile
echo $NGROK_DOMAIN

开始生成证书文件

cd /usr/local/ngrok
# 生成证书
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days
5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$NGROK_DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

# 替换新的证书 系统会提示是否覆盖,按"y"回车确定即可
cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key

方法二:购买/生成第三方机构颁发的SSL证书

进入网站https://www.sslforfree.com/申请CA证书,这里会验证DNS,可以申请泛域名证书,如*.xxx.com的证书。

申请步骤可以参考:https://cloud.tencent.com/developer/news/151294

最后会下载相应的证书文件,这里可以按下面更改下文件名:

ca_bundle.crt => assets/client/tls/ngrokroot.crt
certificate.crt => assets/server/tls/snakeoil.crt
private.key => assets/server/tls/snakeoil.key

这里可以理解一下以下路径

路径 说明
assets/client/tls/ngrokroot.crt 这是客户端ssl连接的CA证书文件(包含根证书和中间证书)
assets/server/tls/snakeoil.crt 这是服务端ssl连接的Certificate证书文件
assets/server/tls/snakeoil.key 这是服务端ssl私钥文件

5、编译ngrok服务端

这一步服务端编译,则会默认去加载上面的证书文件,生成对应的服务端。后面可以使用第三方机构颁发的CA证书,需要根据以下

GOOS=linux GOARCH=amd64 make release-server

编译成功后,放在当前目录下的bin目录中,如:bin/ngrokd

6、编译ngrok客户端

生成了服务端,客户端也需要使用服务端的证书配置,所以客户端务必在这里编译生成。 不同操作系统编译命令如下: 32位linux客户端:

GOOS=linux GOARCH=386 make release-client

64位linux客户端:

GOOS=linux GOARCH=amd64 make release-client

32位windows客户端:

GOOS=windows GOARCH=386 make release-client

64位windows客户端:

GOOS=windows GOARCH=amd64 make release-client

32位mac平台客户端:

GOOS=darwin GOARCH=386 make release-client

64位mac平台客户端:

GOOS=darwin GOARCH=amd64 make release-client

ARM平台linux客户端:

GOOS=linux GOARCH=arm make release-client

客户端生成成功后文件将放在bin目录对应的文件夹内,例如64位mac平台客户端,则放在bin/darwin_amd64/目录下

7、启动ngrok服务端

使用默认的ssl证书启动ngrok服务

/usr/local/ngrok/bin/ngrokd -domain="$NGROK_DOMAIN" -httpAddr=":8800" -httpsAddr=":8443" -tunnelAddr=":4443"

注意这里$NGROK_DOMAIN为之前配置的环境变量。如果手动配置,注意和第4步生成证书域名一致

  • domain参数:绑定的ngrok域名,需要和之前生成证书的域名保持一致,如果是CA证书是泛域名,则可以直接写域名即可,如证书是*.xxx.com,这里参数则可以写xxx.com
  • httpAddr参数:为ngrok代理的http端口
  • httpsAddr参数:为ngrok代理的https端口
  • tunnelAddr参数:为ngrok通道的端口,用于客户端和服务端通信的端口,所以这个端口服务端配置和客户端配置需要保持一致才能正常连接。

使用其他证书启动ngrok服务

/usr/local/ngrok/bin/ngrokd -domain="xxx.com" -httpAddr=":8800" -httpsAddr=":8443" -tunnelAddr=":4443" -tlsKey="/usr/local/ngrok/bin/snakeoil.key" -tlsCrt="/usr/local/ngrok/bin/snakeoil.crt"
  • tlsKey参数:指定服务端私钥
  • tlsCrt参数:指定服务端SSL证书
  • domain参数:绑定的ngrok域名,需要和生成证书的域名保持一致,如果是CA证书是泛域名,则可以直接写域名即可,如证书是*.xxx.com,这里参数则可以写xxx.com

温馨提示

如果有提示端口被占用,则可以用以下命令查出占用端口的进程号,然后kill

ps -A |grep ngrokd

控制台找到对应的进程号,杀掉进程。然后再重新尝试启动服务端

kill -9 20746

8、ngrok服务端配置为系统服务

启动ngrok后,关闭控制台,ngrok也会被关闭,我们需要把ngrok设为系统服务。

方案一:使用shell脚本

(1)创建一个start.sh 在/usr/local/ngrok目录下创建一个start.sh

vim /usr/local/ngrok/start.sh

编辑以下内容:

/usr/local/ngrok/bin/ngrokd -domain="xxx.xxx.com" -httpAddr=":8800" -httpsAddr=":8443" -tunnelAddr=":4443"

给文件可执行权限

chmod 755 /usr/local/ngrok/start.sh

(2)创建启动脚本文件

sudo vim /etc/init.d/ngrok
!/bin/sh
# ngrok Startup script By Lizhijun
# chkconfig: 345 85 15
BEGIN INIT INFO
Provides:          ngrok
Required-Start:
Required-Stop:
Default-Start:    2 3 4 5
Default-Stop:      0 1 6
Short-Description: Start or stop the ngrok Proxy.
END INIT INFO
ngrok_path=/usr/local/ngrok
case "$1" in
    start)
            echo "start ngrok service.."
            sh ${ngrok_path}/start.sh
            ;;
  *)
    exit 1
    ;;
esac

给文件可执行权限

chmod 755 /etc/init.d/ngrok

(3)添加启动服务 ngrok

chkconfig --add ngrok

(4) 测试启动

service ngrok start

方案二:添加service

/etc/systemd/system/目录下创建ngrok.service

vim /etc/systemd/system/ngrok.service

编辑以下内容:

[Unit]
Description=ngrok service
After=network.target

[Service]
PrivateTmp=true
Type=simple
Restart=always
RestartSec=1min
StandardOutput=null
StandardError=null
ExecStart=/usr/local/ngrok/bin/ngrokd -tlsKey=/usr/local/ngrok/assets/server/tls/snakeoil.key -tlsCrt=/usr/local/ngrok/assets/server/tls/snakeoil.crt -domain=xxx.xxx.com -httpAddr=:8800 -httpsAddr=:8443 -tunnelAddr=:4443 &
ExecStop=/usr/bin/killall ngrokd

[Install]
WantedBy=multi-user.target

注意,这里启动命令可以改为之前使用过的启动ngrok命令。注意需要指定SSL证书的绝对路径。

一些基本操作

# 查看运行状态
systemctl status ngrok.service
# 启动ngrok
systemctl start ngrok.service
# 停止ngrok
systemctl stop ngrok.service
#显示启动失败的服务
systemctl --failed 
#重启服务
systemctl restart ngrok.service
#重新加载服务配置文件
systemctl reload ngrok.service

#查询服务是否开机启动
systemctl is-enabled ngrok.service
# 设置开机启动
systemctl enable ngrok.service
# 取消开机
systemctl disable ngrok.service

9、启动ngrok客户端

在第6步(#6、编译ngrok客户端)生成了客户端后,可以下载到本地电脑,然后通过终端软件启动: 假设ngrok下载到了本地的~/ngrok/路径下,目录结构如下:

~/ngrok/
  |-ngrok         ngrok客户端
  |-ngrok.cfg     ngrok配置文本文件

创建配置文件ngrok.cfg,编辑以下内容:

server_addr: "xxx.com:4443"
trust_host_root_certs: false

说明:server_addr为代理的域名,可以配置之前搭建ngrok服务端的域名地址,4443为服务端启动时配置的tunnelAddr参数 注意:server_addr配置的域名需要去域名服务商解析域名 如果是一级域名,需要添加解析 @.xxx.com*.xxx.com到你的服务端ip 如果是二级域名,需要添加解析 xxx.xxx.com*.xxx.xxx.com 到你的服务端ip

1)直接启动ngrok客户端

cd ~/ngrok/
./ngrok -config=ngrok.cfg -log=ngrok.txt -subdomain wechat 8010
  • config参数:为我们刚创建的配置文件路径
  • log参数:为ngrok写日志的文件名
  • subdomain参数:为ngrok启动后我们像服务端注册的子域名的名称,如wechat,服务端将为我们把wechat.xxx.com转发到我们这台主机
  • 最后一个参数8010:是指要映射本机哪个端口给服务端

客户端启动后,会显示如下信息:

Tunnel Status                 online
Version                       1.7/1.7
Forwarding                    http://wechat.xxx.xxx.com:8800 -> 127.0.0.1:8010
Forwarding                    https://wechat.xxx.xxx.com:8800 -> 127.0.0.1:8010
Web Interface                 127.0.0.1:4040
# Conn                        0
Avg Conn Time                 0.00ms

这时候我们就可以访问http://wechat.xxx.xxx.com:8800,来打开内网127.0.0.1:8010的服务了。 如果需要配置https客户端,请参考:https://www.jianshu.com/p/4b03fb532145

2)配置tunnels启动ngrok客户端

除了第一种直接启动ngrok的方式,还可以在ngrok.cfg配置文件配置好tunnels方便启动。 比如我们有2个服务:

Web服务 本机端口 需要绑定的二级域名 tunnels
官网项目 8001 www www
微信项目 8002 wechat wechat
server_addr: "*.ngrok.xxx.com:4443"
trust_host_root_certs: false
tunnels:
  www:
    proto:
      http: 8001
    subdomain: www
  wechat:
    proto:
      http: 8002
    subdomain: wechat

注意:绑定的如果是泛解析二级域名,服务端配置的域名证书应该是*.xxx.xxx.com。这里server_addr可以配置成x.xxx.xxx.com:4443,实际上x.xxx.xxx.com仍然会被当做*.xxx.xxx.com,具体绑定的三级域名会根据启动的subdomain决定绑定三级域名。

启动命令:

./ngrok -config ngrok.cfg start www wechat

启动成功后,外网可以通过绑定的域名访问内网的web服务了。

域名 本地的Web服务
www.ngrok.xxx.com 127.0.0.1:8001
wechat.ngrok.xxx.com 127.0.0.1:8002

或者可以使用start-all启动全部的tunnels

./ngrok -config ngrok.cfg start-all

三、配置nginx反代理

配置nginx反代理,实现域名80端口访问ngrok。 在微信公众号开发服务端配置域名,微信只支持80端口和443端口,这时候我们就需要用Nginx来转发80或443端口的数据到ngrok上。安装Nginx方法请参考其他文章,本篇文章不细讲。 添加配置nginx:

server {
  listen 80;
  listen 443 ssl;
  server_name *.xxx.com;
  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:8800;
    proxy_set_header X-Nginx-Proxy true;
    proxy_set_header Connection "";
    proxy_pass http://127.0.0.1:8800;
  }
  ssl_certificate /usr/local/ngrok/bin/snakeoil.crt;
  ssl_certificate_key /usr/local/ngrok/bin/snakeoil.key;
  ssl_session_timeout  5m;
}

重启nginx生效。这样就可以在用户客户端直接使用80或者443端口直接访问到对应的web服务了。