近日实习的公司要做企业微信开发,但是还没有购买固定公网 IP,为了调试方便我决定在内网搭建一个 ngrok 内网穿透服务。经过两天左右的踩坑,下面大致记录一下流程和解决的问题。
0x00 开始之前
首先当然是要做的准备工作,你需要准备:
- 一个有固定公网 IP 的服务器 (或者能通过 CNAME 解析到的服务器也行);
- 一个域名 (国内用户请参考是否需要备案等要求选购);
- SSL 证书 (可选,可以稍后再申请);
- docker
Q&A: 为什么需要 docker
ngrok 代理的端口最后会体现到 url 上,如果你的请求中包含相对路径,那么请求将会被重定向到
<your.domain>:<port>
上,而微信的服务只会向 443
端口发请求;而如果 ngrok 直接代理 80
或 443
端口,这意味着你其他的 web 服务如 nginx 或 apache 服务将无法使用,这显然是不允许的。因此,将 ngrok 放置在 docker 容器中监听
80
和 443
端口,并对外暴露不同的端口号,使用 nginx 进行代理转发,这样就能避免上面的问题了。0x01 安装 lnmp
lnmp 是一个一键 nginx + php + mysql 安装工具,包含了许多有用的工具包 (例如 vhost 管理和域名证书申请),下载安装非常方便:
$ wget http://soft.vpser.net/lnmp/lnmp1.5.tar.gz -cO lnmp1.5.tar.gz
$ tar zxf lnmp1.5.tar.gz && cd lnmp1.5
$ [sudo] ./install.sh lnmp
由于我的服务器内存较小,我选择跳过了 mysql 安装并安装了低版本的 php 环境,您应该根据需要自行选择;或者您要是熟悉 nginx 的 ssl 配置的话,可以选择跳过本章节,手动申请域名并配置代理转发。
0x02 配置泛域名证书
完成 lnmp 安装后,可以使用它提供的命令来方便的申请泛域名证书,使用下面的命令,按照屏幕上的提示完成配置即可:
$ [sudo] lnmp dnsssl
注意保存生成的证书路径信息,下面会用到。
如果您是手动在 Let's Encrypt 申请的泛域名证书,那还请注意它的有效期只有三个月,到期还需要手动更新。
完成后,lnmp 会帮助您创建一个 nginx vhost 配置文件,位置在
/usr/local/nginx/conf/vhost/<example.your.domain>.conf
,参考下面的配置替换原有内容:map $scheme $ngrok_proxy_port {
"http" "5442";
"https" "5443";
default "5442";
}
server {
listen 80;
listen 443 ssl http2;
#listen [::]:443 ssl http2;
server_name tunnel.your.domain *.tunnel.your.domain;
ssl on;
ssl_certificate /usr/local/nginx/conf/ssl/tunnel.your.domain/fullchain.cer;
ssl_certificate_key /usr/local/nginx/conf/ssl/tunnel.your.domain/tunnel.your.domain.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
ssl_session_cache builtin:1000 shared:SSL:10m;
# openssl dhparam -out /usr/local/nginx/conf/ssl/dhparam.pem 2048ssl_dhparam /usr/local/nginx/conf/ssl/dhparam.pem;
location / {
proxy_pass $scheme://127.0.0.1:$ngrok_proxy_port;
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
access_log off;
}
0x03 安装 docker 与配置镜像
使用 Docker's install script 可以方便的下载安装 docker,使用方法:
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ chmod +x ./get-docker.sh
$ ./get-docker.sh
你可能需要输入 sudo 密码来获取管理员权限。国内用户可以在
./get-docker.sh
后面加上 --mirror Aliyun
来使用阿里云 docker 镜像。0x04 编写 Dockerfile
这里我参考了 hteen/docker-ngrok 的写法,不过我不需要在编译前自签名 ssl 证书,所以我简化了他的写法并优化了镜像构建过程 (最后生成的镜像只有
18.7M
😎):FROM golang:alpine as builder
LABEL maintainer="Mitscherlich <[email protected]>" \
provider="hteen <[email protected]>"COPY . /tmp
# install ngrok dependencies
RUN apk add --no-cache git make
# clone ngrok source code and build binary
RUN git clone https://github.com/inconshreveable/ngrok.git /tmp/ngrok && \
cd /tmp/ngrok && make release-server
FROM alpine
EXPOSE 4443 80 443
ENV NGROK_DOMAIN=${NGROK_DOMAIN} \
NGROK_DIR=/usr/local/ngrok \
TUNNEL_ADDR=":4443" \
HTTP_ADDR=":80" \
HTTPS_ADDR=":443" \
TLS_KEY=${NGROK_DIR}/certs/device.key \
TLS_CRT=${NGROK_DIR}/certs/device.crt
COPY --from=builder /tmp/ngrok/bin ${NGROK_DIR}/bin
VOLUME [ "/usr/local/ngrok/certs" ]
ENTRYPOINT /usr/local/ngrok/bin/ngrokd \
-domain=${NGROK_DOMAIN} \
-tlsKey=${TLS_KEY} \
-tlsCrt=${TLS_CRT} \
-httpAddr=${HTTP_ADDR} \
-httpsAddr=${HTTPS_ADDR} \
-tunnelAddr=${TUNNEL_ADDR}
完成后,使用下面的命令构建镜像:
$ docker build -t ngrok .
使用也非常简单,不过您需要将上面生成的 ssl 证书路径作为共享卷挂载到 docker 容器中,ngrok 需要能访问到这些证书:
# 例如我的证书放在 /usr/local/nginx/conf/ssl/tunnel.your.domain 下:
$ docker run -d --name ngrok-srv \
> -p 5442:80 -p 5443:443 -p 4443:4443 \
> -v /usr/local/nginx/conf/ssl/tunnel.your.domain/certs:/usr/local/ngrok/certs \
> -e NGROK_DOMAIN='tunnel.your.domain' \
> -e TLS_KEY=/usr/local/ngrok/certs/tunnel.your.domain.key \
> -e TLS_CRT=/usr/local/ngrok/certs/fullchain.cer ngrok
本地 ngrok 客户端连接时,你还需要这些配置:
server_addr: tunnel.your.domain:4443
trust_host_root_certs: true
将上面的内容保存为
ngrok.yml
,你可以这样使用它:$ ngrok -config ngrok.yml -subdomain test 3000
OK 👌,就是这样,你可以写个 express 服务快速测试一下:
const app = require('express')();
app.get('/', (req, res) => {
res.send(`<header style="padding: 40px>
<h3>Welcome to ngrok</h3>
<small>Introspected tunnels to localhost</small>
<img src="/images/ngrok.png" />
</header>`);
});
app.listen(3000);
console.log('Listen on port 3000!');
打开浏览器,访问
https://test.tunnel.you.domain
:是不是很酸爽? 🥳
0xff 写在最后
ngrok 作为一项知名开源内网穿透项目而为人所熟知,然而人家也是要恰饭的嘛,所以 ngrok@2 就闭源了,成为了一个商业付费项目,不过好在 [email protected] 的源码还在 github 上可以找到,所以早 fork 保平安咯~