跳轉到

9.3 Zero Trust Access:把內部服務藏在身分驗證之後#

本節目標#

  • 理解 Zero Trust(零信任)的理念與它對「VPN 模式」的批判
  • 學會用 Access 建立應用程式、設定身分 Provider(Google / GitHub / One-Time PIN)
  • 完成「Tunnel + Access」的經典組合:外部只剩登入頁,實際內容只服務通過驗證的人

理論:從城堡護城河到零信任#

傳統安全模型的隱喻是中世紀城堡:一道厚牆(防火牆/VPN),牆內的一切被視為可信。Jericho Forum(2004)最早提出「去邊界化」(de-perimeterisation),Forrester 分析師 John Kindervag 於 2010 年將之形式化為 Zero Trust,核心戒律只有一句:

永不信任,一律驗證(Never trust, always verify)。

以公式寫下:

\[ \text{授權判定} = f(\text{身分}, \text{device}, \text{情境}, \text{行為}) \quad \text{而} \not\exists\ \text{「在內網」這個單獨充分條件} \]

傳統 VPN 的罪狀:一旦連上,你就拿到「整個內網」的網路層存取——攻擊者偷到一份 VPN 憑證等於拿到城門鑰匙(2021 年 FireEye/SolarWinds 事件後,這種「一次登入橫向擴散」的風險被業界深刻記住)。Zero Trust 的解法是把網路層存取換成應用層存取:你不再「連上內網」,而是「被授權使用某一支服務」,每個服務各自發卡。

Cloudflare 的 Zero Trust 產品(2020 年收購 SASE 廠商後整合而成)中,最貼合本書主題的是 Access:

   使用者 ──▶ https://home.example.tk
                     │
            Cloudflare 邊緣
                     │
        ┌────────────┴────────────┐
        │ 1. 有身分驗證 cookie?    │
        │    是 → 轉發至 Tunnel    │
        │    否 → 跳轉到登入頁      │
        └─────────────────────────┘

認證發生在邊緣,原始流量根本不會到你的伺服器——存取控制從「伺服器的問題」上移為「邊緣的問題」,與第 3 章「靜態優先」同構:把熱路徑的邏輯推到離使用者最近的地方。

名詞:應用程式、Policy、Provider#

名詞 角色
Access Application 一組 hostname/path 的保護範圍(例如 home.example.tk/*)
Identity Provider (IdP) 誰來證明「你是你」:Google / GitHub / One-Time PIN / SAML / OIDC
Access Policy 允許的規則:Allow email is a@example.com(決策在邊緣,毫秒級)
JWT(cf_access) 驗證後邊緣簽發的身分令牌,附在請求標頭轉給源

實戰:只允許特定 Email 登入自架服務#

步驟 1:選擇身分 Provider#

Zero Trust Dashboard → Settings → Authentication → Login methods → Add:

  • One-Time PIN:預設開啟、零設定;使用者輸入 email,收一次性驗證碼。最適合個人快速起步
  • Google / Google (Workspace):OAuth 流程,適合個人帳號或家庭群組
  • GitHub:OAuth,適合開發者驗證

個人自架最務實的組合是 Google(主)+ One-Time PIN(備援):即使郵件服務卡住,還有一條便宜的路可進。

步驟 2:建立 Access Application#

Zero Trust Dashboard → Access → Applications → Add an application → Self-hosted:

  • Application name: Home Nginx
  • Session Duration: 24h(每次登入後多久需要重新驗證;越短越安全、越煩)
  • Application domain: self-hosted zone,子網域填 home.example.tk,path 留空 = 保護整個 hostname
  • 選擇已啟用的 IdP(Google + OTP)

步驟 3:撰寫 Policy#

同一頁往下,Add a policy:

  • Name: 只有我
  • Action: Allow
  • Include rule: Emails → 填入 you@gmail.com(或 Emails ending in、特定 Access Group)

存檔後第一次測試:打開無痕視窗、連上 https://home.example.tk —— 應該被導到 Cloudflare 的登入頁,選擇 Google 登入你的帳號後,會被簽發 cookie 並重返服務。這就是「服務藏在身分驗證之後」的完成態。

步驟 4(進階):Tunnel + Access 的閉環#

9.2 的 token 模式剛好為這步準備。Tunnel 本身只會把流量乖乖送到 localhost;加上 Access 之後形成兩層防線:

使用者 ──▶ (Access 驗證:只有 you@gmail.com 放行)
        ──▶ (Tunnel 轉發:只送進本機服務)
        ──▶ localhost:80

若你的源站是 Workers/Pages 之外的傳統伺服器,還可以在伺服器端再驗證一次 Access 簽發的 JWT(防禦縱深,防「繞過 DNS 直打 CF 邊緣」以外的場景,例如別人把 home.example.tk 覆蓋到你自己的直連 IP 繞過邊緣):

# 由 Nginx 驗證 Access 的 JWT(需下載 Access 公鑰與 Team domain)
# https://developers.cloudflare.com/cloudflare-one/identity/authorization-check/validate-access/

步驟 5:其他身分 Provider 的注意事項#

  • GitHub OAuth:Callback URL 固定為 https://<your-team>.cloudflareaccess.com/cdn-cgi/access/callback;需要填 Client ID / Client Secret
  • Google Workspace:可限定組織,適合有限公司的全員 SSO
  • SAML(如 Azure AD / Okta):請求簽名與憑證交換稍繁瑣,企業環境才建議

工程邊角與陷阱#

  1. Service Token:讓無 UI 的機器人(cron、CI)存取 Protected 服務的方式;在 header 中攜帶 CF-Access-Client-Id/Secret,而不是塞官帳密
  2. Session Duration 的取捨:手機上來回登入很煩;24h 折衷,敏感服務才變短
  3. DNS split:若同機還有內網 hostname 記得不要讓 Access 規則覆蓋整個 Zone
  4. WARP Device Posture:對公司場景,可加「裝置已安裝 WARP 且合规」的 Policy,把「人 + 裝置」都放進驗證條件

小結與練習#

  • Zero Trust 的本質是把信任單位從「網路」換成「請求 + 使用者」
  • Access = 邊緣層的身分驗證與授權,與 Tunnel 組合後,你的伺服器變成「只服務通過驗證者」的後台:入口只剩登入頁
  • 從最簡單的 One-Time PIN 起手,再升級到 Google/GitHub OAuth,最後是 SAML/企業 IdP
  • 若再疊加「伺服器端驗證 JWT」,就得到縱深防禦

練習:

  1. 為你在 9.2 架好的 home.example.tk 加上 Access,只允許自己的 email;從另一部手機測試會擋在門外的證據截圖。
  2. 改用 GitHub OAuth 作為 Provider,找出 Callback URL 並理解 client credentials 的代管位置。
  3. 思考:如果 Tunnel 還沒接通,Access 會不會仍先放行使用者到登入頁?試畫出從「未驗證請求」到「源站回應」的完整時序圖。