跳到內容

在 Cloudflare Tunnel 後方執行

Cloudflare Tunnel 由源站上的 cloudflared 主動連線至 Cloudflare,再透過該連線接收請求。源站不需要開放公開連接埠;TLS 在 Cloudflare 邊緣終結,源站透過 loopback 接收明文 HTTP。本頁說明 Tunnel 設定,以及如何記錄實際用戶端位址。

📌 本頁描述 v0.2.2。

  • 網域已在 Cloudflare 帳號中,且可以使用 Zero Trust。
  • cloudflared 與 Pingclair 在同一台主機上,且 Pingclair 正在提供網站(提供靜態網站)。
  • 使用儀表板(Zero Trust → Networks → Tunnels),或一個對該 zone 具有 Cloudflare Tunnel: Write 與 DNS: Edit 權限的 API token。這裡的範例使用 API,並把 $CF_TOKEN、$ACCOUNT 與 $ZONE 分別設為 token、帳號 ID 與 zone ID。
終端機視窗
curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H 'Content-Type: application/json' \
--data '{"name":"docs-origin","config_src":"cloudflare"}' \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel"
{"success":true,"result":{"id":"bc6869fa-19cf-4780-b95b-f11be77eb329","name":"docs-origin", …}}

config_src: cloudflare 代表這個 tunnel 是 遠端管理 的:它的 ingress 規則存放在 Cloudflare,透過 API 推送,所以 connector 旁邊不需要寫任何檔案。

connector 的憑據要另外呼叫取得:

終端機視窗
curl -s -H "Authorization: Bearer $CF_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL_ID/token"

這個 token 是機密:任何主機拿到它都能加入這個 tunnel。請把它當成密碼看待,外洩時要輪替。

終端機視窗
curl -fsSL -o /tmp/cloudflared.deb \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i /tmp/cloudflared.deb
sudo cloudflared service install "$TUNNEL_TOKEN"
INF Linux service for cloudflared installed successfully

connector 會向附近的 Cloudflare 據點開啟四條連線,預設透過 QUIC:

INF Registered tunnel connection connIndex=2 … location=pdx02 protocol=quic
INF Registered tunnel connection connIndex=3 … location=sea10 protocol=quic

ingress 規則決定哪個主機名稱會連到哪個源站服務:

終端機視窗
curl -s -X PUT -H "Authorization: Bearer $CF_TOKEN" -H 'Content-Type: application/json' \
--data '{"config":{"ingress":[
{"hostname":"tunnel-test.pingclair.com","service":"http://127.0.0.1:80"},
{"service":"http_status:404"}]}}' \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL_ID/configurations"

最後一條是全部接住的規則:任何其他主機名稱的請求都會得到 404,而不會抵達源站。

接著把名稱指向 tunnel,並開啟代理:

終端機視窗
curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H 'Content-Type: application/json' \
--data '{"type":"CNAME","name":"tunnel-test.pingclair.com","content":"'$TUNNEL_ID'.cfargotunnel.com","proxied":true,"ttl":60}' \
"https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records"

從任何地方:

終端機視窗
curl -I https://tunnel-test.pingclair.com/
HTTP/2 200
content-type: text/html; charset=utf-8
accept-ranges: bytes
server: cloudflare

server: cloudflare 表示回應的是邊緣。源站是透過 tunnel 連到的,沒有為它開放任何對內連接埠。

每個請求都是從 loopback 上的 connector 抵達,所以存取日誌預設記錄的是 connector,而不是用戶端:

📝 Access … host="tunnel-test.pingclair.com" status=200 remote_ip=127.0.0.1 user_agent="curl/8.7.1"

trusted_proxies 列出可以在轉送標頭中聲明用戶端位址的對端。connector 跑在同一台主機上,所以清單只需要 loopback:

{
admin 127.0.0.1:2019
servers {
trusted_proxies static 127.0.0.1/32
client_ip_headers CF-Connecting-IP
}
}
http://:80 {
root * /srv/site
file_server
}

以同一個請求在加上這個選項前後實測:

remote_ip=127.0.0.1 # before
remote_ip=16.162.199.171 # after: the client that started the request

依用戶端的速率限制與 client_ip 匹配器,也是靠這個設定才能在 tunnel 後方看見真正的用戶端。它在啟動時讀取,所以變更後需要重啟,而不是重載(重載意味著什麼)。

client_ip 與 {client_ip} 使用所列標頭中的用戶端;remote_ip 與 {remote_host} 仍是 connector 的位址。只有受信任的對端能提供這些標頭。CF-Connecting-IP 必須明確列入 client_ip_headers。

  • HTTP/2 530 並帶有 error code: 1033。 這個 tunnel 沒有 connector。在源站上執行 systemctl is-active cloudflared 可以知道它是否在執行;connector 註冊後幾秒內,請求就會恢復回應 200。
  • 請求連到了別的網站,或得到 404。 ingress 規則依序匹配,最後是全部接住的規則;怪罪 DNS 之前,先檢查規則中的主機名稱拼寫。
  • 邊緣回傳 502。 connector 正常,但源站服務拒絕了連線:Pingclair 沒有在規則指定的連接埠上監聽。
  • 存取日誌永遠顯示 127.0.0.1。 缺少 trusted_proxies,如上所述。
  • 主機名稱無法解析。 這筆記錄必須是指向 <tunnel-id>.cfargotunnel.com 且開啟代理的 CNAME;灰色雲朵的記錄會完全繞過 tunnel。
  • connector token 外洩了。 輪替 tunnel 的 token,並用新的 token 重新安裝服務。