前端開發 ngrok 指北

Mitscherlich發表於2019-04-21

近日實習的公司要做企業微信開發,但是還沒有購買固定公網 IP,為了除錯方便我決定在內網搭建一個 ngrok 內網穿透服務。經過兩天左右的踩坑,下面大致記錄一下流程和解決的問題。

0x00 開始之前

首先當然是要做的準備工作,你需要準備:

  1. 一個有固定公網 IP 的伺服器 (或者能通過 CNAME 解析到的伺服器也行);
  2. 一個域名 (國內使用者請參考是否需要備案等要求選購);
  3. SSL 證照 (可選,可以稍後再申請);
  4. docker

Q&A: 為什麼需要 docker

ngrok 代理的埠最後會體現到 url 上,如果你的請求中包含相對路徑,那麼請求將會被重定向到 <your.domain>:<port> 上,而微信的服務只會向 443 埠發請求;而如果 ngrok 直接代理 80443 埠,這意味著你其他的 web 服務如 nginx 或 apache 服務將無法使用,這顯然是不允許的。

因此,將 ngrok 放置在 docker 容器中監聽 80443 埠,並對外暴露不同的埠號,使用 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 2048
  ssl_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 <mitscherlich36@gmail.com>" \
      provider="hteen <i@hteen.cn>"

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
複製程式碼

ngrok

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="https://user-gold-cdn.xitu.io/2019/4/21/16a3eff6610a9d5d?imageView2/2/w/480/h/480/q/85/interlace/1">
</header>`);
});

app.listen(3000);
console.log('Listen on port 3000!');
複製程式碼

開啟瀏覽器,訪問 https://test.tunnel.you.domain:

ngrok

是不是很酸爽? ?

0xff 寫在最後

ngrok 作為一項知名開源內網穿透專案而為人所熟知,然而人家也是要恰飯的嘛,所以 ngrok@2 就閉源了,成為了一個商業付費專案,不過好在 ngrok@1.x 的原始碼還在 github 上可以找到,所以早 fork 保平安咯~

相關文章