以前曾經寫過一篇〈ngrok 讓本機發佈出可被訪問的網址〉,這次要介紹的是類似的工具 Cloudflare Tunnel,他們有著共同的功能:

  1. 我們有個本機的服務跑在 http://localhost:5000,想讓外部訪問
  2. Cloudflare Tunnel 產生公共網址以及 Cloudflare 節點與本機間的「tunnel」
  3. 訪客訪問網址,Cloudflare 節點把請求透過 tunnel 轉送到我們本機的服務,並且把服務的回覆也透過 tunnel 反向的回傳

Cloudflare Tunnel(也稱為 Argo Tunnel)除了名字太長這個問題外,有著這些優勢:

  • Cloudflare Tunnel 與 ngrok 一樣是免費增值型服務,上述基本的 Cloudflare Tunnel 功能完全免費,進階的 Argo 則是收費的
  • Cloudflare Tunnel 可以搭配 Cloudflare 自家的 DNS 服務使用,也就是可以用自有的網址,而這在 ngrok 是要收費的
  • Cloudflare Tunnel 的服務會自動就近分配節點位置,而 ngrok 需要手動指定

安裝 Cloudflare Tunnel

使用 Cloudflare Tunnel 的前提當然是要有 Cloudflare 帳號,以及以 Cloudflare DNS 託管的網域,除這兩點外,我們得在主機上安裝 Cloudflare Tunnel 的代理程式 cloudflared,安裝檔參見 cloudflared 的下載頁

登入

初次使用時,須執行一次登入,讓 cloudflared 取得我們帳號的授權:

$ cloudflared tunnel login

執行後會開啟授權網頁,選擇我們要授權 cloudflared 操作的網域,授權成功後,會獲得一個代表我們帳號的憑證檔在 ~/.cloudflared/cert.pem,後續的操作 cloudflared 會自動以此為證與 Cloudflare 服務互動。

為了方便後續說明,在此我們假設授權的網域是 cccc.ws。

如果 cccc.ws 上面已經有既有的 DNS 紀錄,並不會因為此動作而有被改掉的危機,不用擔心。

建立 Tunnel 與連接服務

假設我們的機台上有個服務跑在 http://localhost:5000,想要利用 Cloudflare Tunnel 讓外部人士用 https://local.cccc.ws 訪問,最簡單的一行指令如下:

$ cloudflared tunnel --hostname local.cccc.ws --url http://localhost:5000

執行後下面會跑出一堆嘰哩呱啦的訊息,只要沒有紅字應該就是正常:

2021-12-05T20:04:44Z INF Connection established connIndex=0 location=TPE
2021-12-05T20:04:46Z INF Connection established connIndex=1 location=NRT
2021-12-05T20:04:47Z INF Connection established connIndex=2 location=TPE
2021-12-05T20:04:48Z INF Connection established connIndex=3 location=NRT

照紀錄看起來,我們分配到的節點分別位於台北(TPE)與東京(NRT),如同最開始所說的,Cloudflare 會自動就近分配 tunnel 節點位置。

跑起來後在 Cloudflare DNS 頁面的確有多了一筆 local.cccc.ws 的紀錄:

Cloudflare DNS

雖然此處登錄的是 IPv6,但這並不影響 IPv4 的設備訪問,實際去查詢可以看到 local.cccc.ws 是有對映到 IPv4 位址的:

Non-authoritative answer:
Name:   local.cccc.ws
Address: 172.67.175.136
Name:   local.cccc.ws
Address: 104.21.91.163
Name:   local.cccc.ws
Address: 2606:4700:3035::ac43:af88
Name:   local.cccc.ws
Address: 2606:4700:3032::6815:5ba3

不論是 A 紀錄的 IPv4 或 AAAA 紀錄的 IPv6,都不是我們自家機台的 IP,而是 Cloudflare 節點的 IP。

仔細看上面實際向 DNS 查詢與 Cloudflare DNS 的設定,會發現位址對不起來,這也是 Cloudflare 節點的基本功能之一,訪客會查詢到離他所在位置較近的 Cloudflare 節點,藉此加快訪問速度,而 Cloudflare 節點與我們 tunnel 節點間的流量,則由 Cloudflare 自行導引,就像 Cloudflare 自我宣傳的那樣:

Cloudflare

來源:Cloudflare

(寫這麼多 Cloudflare 會給我業配嗎?🤔)


如果我們手賤,原本 DNS 就有登錄一筆 server.cccc.ws,卻又想用它建立 tunnel:

$ cloudflared tunnel --hostname server.cccc.ws --url http://localhost:5000

那 Cloudflare 會阻止我們這麼做:

2021-12-06T03:06:24Z ERR Initiating shutdown error="Your Cloudflare account already have DNS records, [type: CNAME, content: dry-melon-kb9phqzfmn.herokudns.com], for the subdomain server.cccc.ws you are attempting to create. Please delete the records in the Cloudflare dashboard before starting Argo Tunnel"

關閉 Tunnel

對剛剛在跑著 cloudflared 的 terminal(或 console,不論怎麼叫)按下 CTRL-C 即可中斷 tunnel,並秀出類似下面的訊息:

2021-12-06T02:10:32Z INF Initiating graceful shutdown due to signal interrupt ...
2021-12-06T02:10:32Z INF Unregistered tunnel connection connIndex=0
2021-12-06T02:10:32Z INF Unregistered tunnel connection connIndex=2
2021-12-06T02:10:32Z INF Unregistered tunnel connection connIndex=3
2021-12-06T02:10:32Z INF Unregistered tunnel connection connIndex=1
2021-12-06T02:10:32Z INF Tunnel server stopped
2021-12-06T02:10:32Z INF Metrics server stopped

此時若去看 Cloudflare DNS 會發現那筆 local.cccc.ws 與 IPv6 的紀錄還是在,但是確實已經連不到了,此時需要手動砍掉這筆遺留的紀錄。

Clouflare Tunnel 的安全優勢

對於展示、測試開發中專案來說,搭建的 tunnel 往往是臨時性的,可能不用太在意安全性的部份,但 Cloudflare Tunnel 的野心不僅於此,他們希望可以將此技術應用在真實的服務上,帶給服務更多的安全保護,因為覺得滿好的所以也順帶寫一下。

下面的示意圖是未使用 Cloudflare Tunnel 前的一個典型的 DNS A record 訪問模型:

Cloudflare Tunnel

來源:〈Argo Tunnels that live forever

正常的訪客透過 example.com 訪問我們的服務,並且透過 Cloudflare 節點對我們後端的 1.2.3.4 做到基本的防護。

但我們服務的 IP 1.2.3.4 仍然是暴露在公網的,所以壞人大猩猩有可能透過一些手段擷取到我們的真實 IP 1.2.3.4,進而展開攻擊。

而在 Cloudflare Tunnel 的連線模型中,一切的傳輸只透過 tunnel,我們的服務始終都是封閉的,不曾暴露於公網,壞人大猩猩最多只能攻擊到 Cloudflare 節點,碰不到我們機台的真身,這可以為我們帶來額外的安全優勢:

Cloudflare Tunnel

來源:Cloudflare

其他花式玩法

前文所提的「一行指令」用法較適合臨時性的應用,例如開發中的專案需要給手機測試、對外展示等,Cloudflare Tunnel 也可以另外配置持久性的 tunnel,這點從 cloudflared 的名字可以推測的出來,字尾的「d」是 daemon 的意思,對 Cloudflare Tunnel 的其他花式玩法有興趣的朋友可參閱〈Argo Tunnels that live forever〉一文。