FurCDN 文檔

回源鑑別與源站防護

如何保護源站(自定義回源 Header、回源 IP 白名單),以及多源站時的源站擇優(每節點探測延遲後自動選路)設定。

接入 CDN 後,所有訪客流量都先打到 CDN 節點,節點再「回源」到你的源站取資料。若源站仍對全網開放,攻擊者只要拿到源站真實 IP,就能繞過 CDN(與 WAF / 緩存 / 防護)直連源站。

本文說明兩種讓源站「只信任 FurCDN 回源」的方式:

方式原理防偽造推薦度
自定義回源 Header節點回源時注入一個只有你和 CDN 知道的密鑰 header,源站校驗✅ 強(密鑰不公開)⭐ 推薦
回源 IP 白名單源站防火牆只放行 CDN 節點 IP⚠️ 較弱(IP 公開可查,且需定期同步)補充手段

兩種可疊加

最穩的做法是兩者一起用:防火牆先用 IP 白名單擋掉絕大多數掃描流量,再用自定義 Header 做最終身份校驗。但若只能選一個,優先用自定義 Header —— IP 會變、會被別人查到,Header 密鑰不會。


方式一:自定義回源 Header(推薦)

原理

你在域名設定裡加一條只有你知道的密鑰 header(例如 X-Origin-Secret: 一串隨機字串)。CDN 節點每次回源都會自動帶上它,源站只要檢查這個 header 是否存在且值正確,就能確認「這個請求確實來自 FurCDN,不是有人直連」。

因為密鑰不公開、不出現在任何對外 API,攻擊者無從偽造,所以比 IP 白名單可靠得多。

設定步驟

  1. 自己生成一串夠長的隨機密鑰,例如:

    openssl rand -hex 32
    # 例:b3f1c9a7e2d84f06a1c5e9b7d3f0a2c8...
  2. 進入 儀表板 > 域名管理 > 編輯域名 > 回源設定,找到「回源自定義 Header」區塊,點「新增」:

    • Name:X-Origin-Secret(名稱自取,但避開 Host / Authorization / Date / X-Amz-Date 等保留名稱)
    • Value:上一步生成的隨機密鑰
  3. 儲存。節點會在後續所有回源請求注入此 header(最多可加 5 條)。

源站校驗範例

只放行帶正確密鑰的請求,其餘一律拒絕。

Nginx:

server {
    listen 80;
    server_name origin.example.com;

    if ($http_x_origin_secret != "你的密鑰") {
        return 403;
    }

    # ... 你的正常配置
}

Apache(.htaccess / vhost):

SetEnvIf X-Origin-Secret "^你的密鑰$" from_cdn
Require env from_cdn

PHP:

<?php
if (($_SERVER['HTTP_X_ORIGIN_SECRET'] ?? '') !== '你的密鑰') {
    http_response_code(403);
    exit('Forbidden');
}

Caddy:

origin.example.com {
    @noSecret not header X-Origin-Secret "你的密鑰"
    respond @noSecret 403
    reverse_proxy 127.0.0.1:8080
}

密鑰保密

這個密鑰等同於源站的「門禁卡」。不要寫進公開 repo、不要洩漏到日誌。懷疑外洩時,在域名設定改一個新值即可(節點下次回源立即生效)。


方式二:回源 IP 白名單

如果你的源站防火牆不方便校驗 header,可以改用 IP 白名單:只放行 FurCDN 節點的回源 IP。

回源 IP 公開 API

平台提供一個公開、免鑑權的端點,列出當前全部節點的回源 IP:

GET https://cdn.taipei/api/public/origin-ips

預設回傳 text/plain,一行一個 IP,方便直接灌進防火牆腳本:

curl -s https://cdn.taipei/api/public/origin-ips
1.2.3.4
5.6.7.8
9.10.11.12

也支援 JSON:

curl -s "https://cdn.taipei/api/public/origin-ips?format=json"
{ "ips": ["1.2.3.4", "5.6.7.8", "9.10.11.12"], "count": 3 }

這份清單不是固定的

節點會隨時新增、下線、更換 IP,沒有固定的回源 IP 或 IP 段。請不要把它寫死到防火牆,而要定期(建議每 5~10 分鐘)重新拉取並同步規則。否則節點換 IP 後,你的源站會把新節點的回源當成攻擊擋掉,導致網站打不開。

清單已涵蓋 Anycast 節點的 unicast 回源出口 IP。

Anycast 出口可能有多個、且未必即時出現在 API

同一個 Anycast 節點在底層可能有多條 unicast 回源出口(跨 upstream / 跨 PoP 的備援線路),新出口啟用後不保證馬上同步到這支公開 API,可能有數分鐘~數十分鐘的落差。

這代表:單靠 IP 白名單一定會誤報(把合法回源當攻擊擋掉、觸發 403/連線重置,用戶側看起來就是「網站間歇性打不開」)。

強烈建議**務必配合方式一的自定義回源 Header**做身份校驗,IP 白名單只當成第一層粗篩,不要當成唯一鑑別依據。

範例:定期同步到防火牆

ipset + iptables(Linux)—— 放進 cron 每 5 分鐘跑一次:

#!/bin/bash
set -euo pipefail

# 建立 / 確保 ipset 存在
ipset create furcdn hash:ip -exist
ipset create furcdn_new hash:ip -exist
ipset flush furcdn_new

# 拉取最新回源 IP 填入暫存集合
while read -r ip; do
  [ -n "$ip" ] && ipset add furcdn_new "$ip" -exist
done < <(curl -fsS https://cdn.taipei/api/public/origin-ips)

# 原子替換,避免同步空窗期把節點擋在外面
ipset swap furcdn_new furcdn

# 首次需掛一條規則(只放行 furcdn 集合 + 本機,其餘拒絕 80/443)
# iptables -A INPUT -p tcp -m multiport --dports 80,443 -m set --match-set furcdn src -j ACCEPT
# iptables -A INPUT -p tcp -m multiport --dports 80,443 -j DROP

Nginx allow/deny(生成片段後 reload):

#!/bin/bash
set -euo pipefail
OUT=/etc/nginx/furcdn-allow.conf
{
  curl -fsS https://cdn.taipei/api/public/origin-ips | sed 's/^/allow /; s/$/;/'
  echo "deny all;"
} > "$OUT.tmp" && mv "$OUT.tmp" "$OUT"
nginx -t && nginx -s reload

在 server {} 內 include /etc/nginx/furcdn-allow.conf; 即可。

IP 白名單的局限

回源 IP 是公開可查的,任何人都能拿到這份清單。所以 IP 白名單只能擋「不知道你源站 IP 的隨機掃描」,擋不住刻意偽造來源的攻擊者(雖然偽造 TCP 來源 IP 並不容易)。要真正鑑別身份,請搭配方式一的自定義 Header。


該選哪個?

  • 大多數人:用自定義 Header 就夠了,設定一次、源站校驗一次,密鑰不變就一勞永逸。
  • 有防火牆運維能力、想多一層:自定義 Header 疊加 IP 白名單(定期同步腳本)。
  • 不要只靠 IP 白名單且把 IP 寫死 —— 節點換 IP 時網站會掛。

源站擇優(多源站自動選路)

一個網域可以設定 1 個主源站 + 多個備援源站,預設的 failover 策略是「照 origins 陣列順序打,失敗才換下一個」。問題是:對每個節點而言,陣列裡的第一個源站未必最快 —— 香港節點打香港源站可能只要 10ms,打美西源站卻要 150ms。

源站擇優讓每個節點從自己的網路位置對各源站做延遲探測,各自挑出對自己最快的源站。這跟「master 集中決定一個最佳源站」不同:節點分散在全球,只有節點自己才知道哪條路徑最快。

設定存在網域的 originPolicy(一個 JSON 物件):

  • failover(預設)=維持現行行為,不啟用擇優。
  • latency=依探測分數擇優,這是最常用的設定。
  • 其餘策略(round_robin / weighted / random / ip_hash)不需要探測也能運作;probeAlways 可讓它們「只觀測、不影響選路」。

為什麼用 ICMP

ICMP echo(ping)是最輕量的 RTT 量測方式:不需要源站跑任何應用層邏輯、不佔用 HTTP 連線、每輪只送幾個封包。節點每輪對每個源站送 probeCount 個封包,取平均 RTT 當作該輪分數,丟包率則用來懲罰。

若環境不允許 ICMP,也可以改用 TCP connect 時間(probeMethod: tcp)或 HTTP HEAD(probeMethod: http)。

選路策略

strategy行為
failover預設。依 origins 陣列順序嘗試,失敗才換下一個;backup: true 的源站一律排到最後。
latency依最近 scoreWindow 輪的探測分數擇優(分數越低越好)。啟用探測。
round_robin輪流使用所有非備用源站,weight 影響分配比例。
weighted依每個源站的 weight 加權隨機挑選。
random隨機挑一個非備用源站。
ip_hash依 hashKey(client_ip 或 uri)雜湊,把同一個訪客(或同一個 URI)固定導到同一個源站,藉此提高源站快取命中。

備用源站(backup)

不論哪一種策略,backup: true 的源站都只在「所有非備用源站都不可用」時才會被使用,適合放只在災難時才啟用的冷備援。

完整選項(originPolicy)

所有欄位都有預設值,只需要帶要改的欄位:未帶的欄位沿用目前設定(伺服器淺合併後整體驗證),全新網域則套預設。

欄位型別 / 範圍預設說明
strategyfailover / latency / round_robin / weighted / random / ip_hashfailover選路策略,見上表
probeMethodicmp / tcp / httpicmp探測方式
probeFallbackbooltrueICMP 不可用(無 raw socket 權限,或目標徹底擋掉 ICMP)時,退回用 TCP connect 時間
probeIntervalSec整數 5~360015每輪探測間隔秒數
probeTimeoutMs整數 200~100002000單一探測封包的逾時毫秒
probeCount整數 1~103每輪送幾個封包;RTT 取該輪平均,丟包率為該輪遺失比例
probePort整數 1~65535 或 nullnullTCP/HTTP 探測埠;null 表示依 URL scheme(http→80、https→443)或 URL 內指定的埠
probePath以 / 開頭的字串,≤256 字元/HTTP 探測路徑(送 HEAD,回應 <500 視為成功)
probeAlwaysboolfalse非 latency 策略時也持續探測並上報(只用於觀測,不影響選路)
scoreWindow整數 1~305分數=最近 N 輪 RTT 的平均
lossPenaltyMs整數 0~50001000分數公式:avgRtt + 丟包率(0~1) × lossPenaltyMs + origin.latencyBiasMs
maxLatencyMs整數 0~300000大於 0 時,分數高於此值的源站會被排到最後(仍保留在 failover 鏈中,不會被剔除)
switchThresholdMs整數 0~500020遲滯:新最優者必須比目前選中的源站好至少這麼多毫秒才切換
minHoldSec整數 0~360030切換後至少維持這麼久才允許再次切換
hashKeyclient_ip / uriclient_ipip_hash 策略使用的雜湊鍵
retryStatuses整數陣列(400~599,最多 20 個)[]空陣列=所有 5xx 都觸發換源;非空=只有清單內的狀態碼才觸發
retryOnErrorbooltrue連線失敗/逾時是否觸發換源
maxAttempts整數 0~100單一請求最多嘗試幾個源站;0=不限(最多嘗試所有源站)

每個源站的選項

除了 URL / 類型 / SNI / Host override 之外,origins[] 陣列裡的每個源站另外支援:

欄位範圍預設說明
weight整數 1~10001weighted / round_robin 的權重
backupboolfalsetrue=備用:只在所有非備用源站都不可用時才使用(任何策略皆然)
probeHosthostname / IP 或空字串""ICMP/TCP 探測的目標;空=用源站 URL 的 host。與源站 URL 適用同等的內網 / 保留位址檢查(非管理員不得指向私網)
latencyBiasMs整數 -1000~100000加到分數上的固定偏移:正值=懲罰、負值=偏好
probeDisabledboolfalse不探測此源站;latency 策略下會排在有分數的源站之後

探測與 ICMP 注意事項

源站與中間防火牆需放行 ICMP

節點是從自己的網路位置直接對源站送 ICMP echo。如果源站主機或中間的雲防火牆 / 安全組擋掉 ICMP,探測會全部逾時。

此時若 probeFallback: true(預設),節點會自動退回量測 TCP connect 時間;若 TCP 也不通(埠沒開、防火牆擋),該源站會被視為不可用。建議:

  • 在源站安全組放行 CDN 節點的 ICMP(來源可用上方「方式二」的回源 IP 公開 API 取得);
  • 或直接把 probeMethod 設成 tcp(例如源站只開 443 時)。
  • 剛啟動、還沒有探測資料時,節點會先依配置順序選路,等第一輪探測完成(預設 15 秒)後才切換到擇優結果。
  • 設定變更後(新增 / 修改源站),節點會立即對受影響的源站補跑一輪探測,不必等下一輪間隔。
  • 被判定為故障的源站會被既有健康檢查機制暫時排除;若全部源站都被判定故障,則 fail-open(照樣嘗試),避免整體 502。

遲滯(hysteresis):避免源站一直換來換去

延遲是會抖動的。如果只要「B 比 A 快 0.5ms」就切換,兩個源站分數接近時會不停來回切,反而製造更多回源連線與快取碎片。因此擇優加了兩道剎車:

  • switchThresholdMs(預設 20ms):新最優者必須比目前選中者好至少這麼多才切換。
  • minHoldSec(預設 30 秒):切換後至少維持這麼久,期間不再切。

範例:目前選中源站 A,分數 45.0ms。

情境候選 B 分數差距結果
140.0ms5ms < 20ms維持 A(差距不足門檻)
222.0ms23ms ≥ 20ms切換到 B,並在接下來 30 秒內不再切換
3兩者分數都高於 maxLatencyMs(若設為 3000)—兩者都被排到最後,但仍按分數排序

存取日誌欄位

啟用擇優後,每筆存取日誌會多帶:

欄位說明
originUrl最終實際回應的源站 URL(已遮蔽 userinfo)
originRttMs該源站在當下的探測分數(毫秒);沒有探測資料時省略
originStrategy這次請求使用的策略

originTrace 會在最前面帶上策略與候選排序,例如:

擇優(icmp) 源站2 12.3ms > 源站1 45.0ms | 源站2 http://b → HTTP 200

意思是:這次用 ICMP 擇優,候選排序為「源站2(12.3ms)優於源站1(45.0ms)」,最終由源站2 回應,HTTP 200。

透過 API 設定

擇優設定屬於登入後的 dashboard API(session 鑑權,與 CLI furcdn login 用的是同一套),不在對外開放的 /api/v1/** API key 端點內。

# 讀取目前設定(GET /api/domains 回傳 { domains: [...] },每筆含 originPolicy)
curl -s https://cdn.taipei/api/domains \
  -H "Cookie: furcdn_token=<session>" | jq '.domains[] | select(.id==123) | .originPolicy'

# 只改 strategy 等欄位(伺服器會淺合併既有設定後整體驗證)
curl -s -X PUT https://cdn.taipei/api/domains/123 \
  -H 'content-type: application/json' \
  -H 'Cookie: furcdn_token=<session>' \
  -d '{"originPolicy":{"strategy":"latency","probeMethod":"icmp","switchThresholdMs":20}}'

# 重置為全部預設
curl -s -X PUT https://cdn.taipei/api/domains/123 \
  -H 'content-type: application/json' \
  -H 'Cookie: furcdn_token=<session>' \
  -d '{"originPolicy":null}'

# 各節點最近一輪的探測結果(RTT / 丟包 / 該節點選中的源站)
curl -s https://cdn.taipei/api/domains/123/origin-probes \
  -H "Cookie: furcdn_token=<session>"

origin-probes 的回應格式:

{
  "nodes": [
    {
      "nodeId": 7,
      "nodeName": "hk-1",
      "ts": 1735689600000,
      "origins": [
        { "url": "http://a.example", "method": "icmp", "rttMs": 12.3, "lossPct": 0, "ok": true, "selected": true, "ts": 1735689600000 },
        { "url": "http://b.example", "method": "icmp", "rttMs": 45.0, "lossPct": 0, "ok": true, "selected": false, "ts": 1735689600000 }
      ]
    }
  ]
}

超過 15 分鐘沒有回報的節點不會出現在清單中。

透過 CLI 設定

# 啟用擇優(只改 strategy,其餘沿用預設)
furcdn domain origin policy set example.com --set strategy=latency

# 或等價的旗標寫法
furcdn domain update example.com --origin-strategy latency --probe-method icmp --switch-threshold 20

# 檢視生效中的完整設定(未自訂的欄位標為「預設」)
furcdn domain origin policy get example.com

# 調整單一源站:權重、備用、探測目標、不探測
furcdn domain origin set example.com --url http://b.example --backup --weight 3 --probe-host 1.2.3.4
furcdn domain origin set example.com --url http://c.example --latency-bias -50 --no-probe
furcdn domain origin list example.com

# 看各節點的探測結果(表格 / --json)
furcdn domain origin probes example.com

# 重置為全部預設
furcdn domain origin policy set example.com --reset

排查

  • 一直只走同一個源站:確認 strategy 已是 latency(預設是 failover),且該網域有多個非備用源站。
  • 探測全部逾時 / 分數都是空的:先確認源站放行 ICMP;不放行就把 probeMethod 改成 tcp。
  • 源站一直換來換去:調高 switchThresholdMs(例如 50)與 minHoldSec(例如 120)。
  • 更多症狀見「常見問題排查」。

© 2022-2026 Taipei101 Network 版權所有

9900 Corporate Campus Dr Ste 3000, Louisville, KY 40223, United States

由 langya.io 驅動

On this page