如果在家中或在任何內網中佈建了一些服務,需要從外部連入,Reverse SSH tunnel (Remote Port Forwarding),是一個很好的選擇。但是如果要讓在 Internet 上的人可以存取這個內部的服務,就必須要有外部網站再轉 tunnel 的服務,這也就是 ngrok 所提供的服務之一。ngrok 雖然有提供免費的服務,但是限制頗多。如果並不常用,訂閱起來也蠻浪費的。

如下圖,本文要說明的是如何實作自製的 ngrok 服務,其中會使用下面幾種技術:

  • Reverse SSH tunnel (Remote Port Forwarding)
  • NGINX reverse proxy
  • (optional) Let’s Encrypt

要做到本文的成果,您必須要有:

  • domain name
  • Internet 上的 Linux server (自建或 Cloud 服務),以下使用 GCP
在 Mac 中設定開機自動連接 SSH tunnel

參考此篇:https://blog.kylemanna.com/osx/ssh-reverse-tunnel-on-mac-os-x/

建立持續連接的 SSH reverse tunnel

在內部機器使用以下指令,連接建立 SSH tunnel,將內部實際提供服的 port 3000,連接到外部 GCP VM 的 port 13000,並使其持續連接,不會因為閒置沒有流量而斷掉:

ssh -o TCPKeepAlive=yes -o ServerAliveCountMax=20 -o ServerAliveInterval=15 -NR 13000:localhost:3000 [email protected]
參數說明:

-o: 覆蓋 ssh_config 的選項
-f: 背景執行
-N: 不執行遠端指令,適用於 port forwarding (reverse tunnel)
-R: 執行 remote port forwarding (reverse tunnel)

13000: 遠端 server 的 port
localhost: 本機
3000: 本機的 port

TCPKeepAlive: 持續送出 KeepAlive 封包,以保持連線
ServerAliveInterval: 每送出一次 KeepAlive,會等幾秒回應
ServerAliveCountMax: 最多等幾次沒有回應的 KeepAlive,以上面指令為例,如果 20x15=300 秒,沒有收到回應,才會斷開。

/

安裝 NGINX

sudo apt update
sudo apt upgrade
sudo apt install nginx

設定 NGINX: 設定 Virtual Host、使用 Reverse Proxy

先完成支援 HTTP 的設定

[email protected]:~$ cd /etc/nginx/sites-available/
[email protected]:~$ sudo vi tunnel
server {
  server_name tunnel.example.com; # Virtual Host 的名稱
  listen 443 ssl;
  location /gf/ {  # 將 /gf 導引到建立好的 SSH tunnel
      proxy_pass http://localhost:13000/;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      proxy_redirect off;
  }

  location /lgtn/ {
      proxy_pass http://localhost:18086/;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      proxy_redirect off;
  }
}

連結至 sites-enabled 目錄、重新啟動 NGINX

[email protected]:~$ cd ../sites-enabled
[email protected]:~$ sudo ln -s ../sites-available/tunnel .
[email protected]:~$ sudo service nginx restart

安裝支援 NGINX 的 Let’s Encrypt Certbot

[email protected]:~$ sudo apt install certbot python3-certbot-nginx

使用 Certbot 申請與驗證憑證,並自動更新 NGINX 設定檔

注意:使用時,必須確保 HTTP 在 Port 80 有提供服務,否則無法完成驗證。

[email protected]:~$ sudo certbot --nginx -d tunnel.example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): [email protected]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for tunnel.example.com
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/tunnel

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
No matching insecure server blocks listening on port 80 found.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://tunnel.example.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=tunnel.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/tunnel.example.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/tunnel.example.com/privkey.pem
   Your cert will expire on 2020-11-29. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. To non-interactively renew *all* of
   your certificates, run "certbot renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

確認最後的 NGIX Virtual Host 設定檔

[email protected]:~$ cd /etc/nginx/sites-available/
[email protected]:/etc/nginx/sites-available$ more tunnel
server {
  server_name tunnel.example.com;
  listen 443 ssl;
  location /gf/ {
      proxy_pass http://localhost:13000/;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      proxy_redirect off;
  }

  location /lgtn/ {
      proxy_pass http://localhost:18086/;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
      proxy_redirect off;
  }

    #listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/tunnel.example.com/fullchain.pem; # ma
naged by Certbot
    ssl_certificate_key /etc/letsencrypt/live/tunnel.example.com/privkey.pem; #
managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料