以前曾經寫過一篇〈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

幫 tunnel 取個名字,這裡叫 local:

$ cloudflared tunnel create local

這只是在 Cloudflare 建一個空的 tunnel 紀錄在案,還沒有真正可以連線的 tunnel。

要檢視自身旗下所有 tunnel,使用命令 list

$ cloudflared tunnel list

會看到如下的輸出:

You can obtain more detailed information for each tunnel with `cloudflared tunnel info <name/uuid>`
ID                                   NAME   CREATED              CONNECTIONS
e6b1ea74-e6ad-4e36-b8b5-0f8f81ccafec local  2023-02-05T16:29:33Z
9ea66384-6ec7-4b6c-9a05-e57c08a32526 soda   2022-05-26T01:46:42Z

綁定 Tunnel 與網址

目前 tunnel 還沒有網址,給它一個:

$ cloudflared tunnel route dns local local.cccc.ws

當然一般而言會把兩者取相同的名字,這步顯得有點多此一舉。

如果我們手賤,原本 DNS 就有登錄一筆 existed-local.cccc.ws,卻又想用它綁定 tunnel:

$ cloudflared tunnel route dns local existed-local.cccc.ws

那 Cloudflare 會阻止我們這麼做:

Failed to add route: code: 1003, reason: An A, AAAA, or CNAME record with that host already exists.

連接本地服務

有了 tunnel、有了網址,只剩把服務連上 tunnel 了。

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

$ cloudflared tunnel run --url http://localhost:1111 local

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

2023-02-05T16:50:52Z INF Connection e4fe5a22-1e6e-4a05-b24c-95fc3ec62501 registered with protocol: quic connIndex=0 ip=198.41.200.23 location=TPE
2023-02-05T16:50:52Z INF Connection 57570b20-0c60-472b-bfac-eeebe45912ff registered with protocol: quic connIndex=1 ip=198.41.192.227 location=HKG
2023-02-05T16:50:52Z INF Connection 02f8e0ad-dfca-499b-af48-21a8bbfa1b47 registered with protocol: quic connIndex=2 ip=198.41.200.43 location=TPE
2023-02-05T16:50:52Z INF Connection 39c656ce-e08d-49a7-9cfb-1d05d92228fc registered with protocol: quic connIndex=3 ip=198.41.192.27 location=NRT

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

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

關閉 Tunnel

對剛剛在跑著 cloudflared 的 terminal(或 console,不論怎麼叫)按下 CTRL-C 即可中斷 tunnel。

此時若去看 Cloudflare DNS 會發現那筆 local.cccc.ws 還是在,但是確實已經連不到了,此時需要手動砍掉這筆遺留的紀錄(或者射後不理也無妨)。

其他花式玩法

前文所提的命令列用法較適合臨時性的應用,例如開發中的專案需要給手機測試、對外展示等,從 cloudflared 的名字可以推測的出來,字尾的「d」是 daemon 的意思,因此它也可以是服務,配合 tunnel 配置文件,讓開機完就把 tunnel 跑起來,除此之外,除了最典型的轉送給本地 HTTP 服務外,Cloudflare Tunnel 也可以轉送給本地 Unix socket 或者 IP,對 Cloudflare Tunnel 的其他花式玩法有興趣的朋友可參閱〈Argo Tunnels that live forever〉一文。

Clouflare Tunnel 的安全優勢

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

下面的示意圖是未使用 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 有著全球廣佈的快取節點,訪客會查詢到離他所在位置較近的 Cloudflare 節點,藉此加快訪問速度,而 Cloudflare 節點與我們 tunnel 節點間的流量,則由 Cloudflare 自行導引,就像 Cloudflare 自我宣傳的那樣:

Cloudflare

來源:Cloudflare