iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0

昨天我們把容錯的不對稱講透了:身分服務掛掉時,提問那條路傾向 fail-open(放行、標成匿名),改設定那條路 fail-closed(一律拒絕),按操作的風險量級分別決定。但那整套討論都站在同一個前提上——「我們知道進來的使用者是誰」。今天把鏡頭轉到另一側:當 Portal 自己變成「呼叫方」,要去打下游的 Engine 時,Engine 憑什麼相信來敲門的真是 Portal 這是第三章「身分與請求生命週期」的最後一塊:把「身分」這件事從「使用者對系統」延伸到「服務對服務」。

本篇結構:

  • 下游為什麼會裸奔
  • 機制:跟雲平台借一張短命身分證
  • 取捨:零金鑰落地,代價是被雲平台綁住

下游為什麼會裸奔

先把處境講清楚。Portal 自己不產生答案,真正產生答案的後端引擎(這裡叫它 Engine)是另一個獨立部署的服務。在雲端環境裡,這兩者各自有網址、用 HTTP 互打,並不在同一個行程內互相呼叫函式——你可以想成同一棟大樓裡兩個不同部門,靠走廊(網路)往來。

問題在於,這條走廊不是只有 Portal 走得到。任何能解析到 Engine 網址、又連得上去的人,理論上都能對它送出一個「長得很像 Portal 會送」的請求。我們花了 Day 8 到 Day 10 三天,把「使用者身分」這道牆砌得密不透風——行員小林帶著簽章 cookie 進門、驗章、確認他是某個 8 碼 corpId 的本人。結果如果下游 Engine 對「誰來敲門」毫不設防,攻擊者根本不必騙過大門,他從側門就進來了:繞過前面所有 SSO(單一登入)與守門(guardrail),直接餵 prompt 給後端引擎。前面的功夫等於白做。

直覺的解法是「給 Portal 一把鑰匙」:發一組 API key 或 service account(服務帳號,雲平台上代表一個服務本身的身分)金鑰檔,Portal 每次呼叫都附上,Engine 比對無誤才放行。這能動,但它有個老問題——長期金鑰要存在哪? 塞進程式碼、打進映像檔、放進環境變數、掛成 secret 檔,每一種都是一個會外洩的點,而且一旦外洩,那把鑰匙在你輪換完之前一直有效。s2s(service-to-service)認證最大的風險,從來不是「演算法不夠強」,而是「那個長期祕密被人撈走了」。我們想要的是:能證明身分,但不必自己保管任何長期祕密。

機制:跟雲平台借一張短命身分證

Portal 的做法是乾脆不自己持有任何長期金鑰,改成借用雲端平台給每個工作負載配發的身分——也就是 workload identity。可以這樣理解:這個服務被部署到雲平台時,平台就「知道」它是誰了,就像員工進公司時人資系統裡早有他這個人;需要對外證明身分時,服務不必自己掏出什麼憑證,而是回頭跟平台說「幫我開一張證明,有效期很短,而且指名給某個對象看」。

具體流程是這樣:Portal 要打 Engine 之前,先向 GCP 的 metadata server(一個只有同平台內部服務連得到的內部端點)索取一張平台簽發的 OIDC(OpenID Connect,一套「由可信方簽發身分證明、任何人可驗簽」的標準)ID token(權杖)。索取時最關鍵的參數是 audience(受眾)——它指定「這張 token 是要給誰看的」,填的就是下游 Engine 的服務網址。拿到 token,掛在 Authorization: Bearer header 上,再去打 Engine

# Portal 向 GCP metadata server 鑄一張 token,audience 指定為下游服務 URL
GET http://metadata.google.internal/computeMetadata/v1/instance/
        service-accounts/default/identity?audience=https://engine-xxxx.run.app
Metadata-Flavor: Google

→ 回傳一段 JWT(OIDC ID token);Portal 掛上去再呼叫 Engine:
  Authorization: Bearer <jwt>

Engine 收到後做三件驗證就能確認來者:

  1. 簽章可信——這張 token 的簽章確實出自可信的雲平台簽發者(用平台公開的金鑰驗,不需要 PortalEngine 之間先共享任何祕密)。
  2. 尚未過期——token 還在有效期內(這些 token 都很短命,過期作廢)。
  3. audience 正確——也是最關鍵的,token 裡的 audience 正好是 Engine 自己的網址。

第三點是整套設計的關鍵。它防的是「token 轉手攻擊」:就算有人攔截到一張本來要給「服務 A」的 token,他也沒辦法拿去騙「服務 B」,因為 audience 對不上,B 一驗就退件。每張 token 都是「指名收件、短期有效」的臨時證明,而不是一把能反覆使用、又能冒充任意對象的萬能鑰匙。撈到一張,也只能拿去打它本來就指定的那個對象。

不過這裡得補一個誠實的邊界,免得把「驗 audience」當成防冒充的全部。簽章、效期、audience 三件合起來,擋的是「偽造的 token」和「轉手到別的服務」;但它們擋不了「同平台另一個合法工作負載,也去鑄一張 audience 指名 Engine 的 token」——對 Engine 來說那張 token 簽章、效期、audience 全對,看不出它不是 Portal 鑄的。要擋這種同平台冒充,還得再加一層:限定「只有特定身分(service account)能呼叫我」,也就是一份 subject 白名單。

而在這套跑在 GCP 上的實況裡,這層 subject 白名單不由 Engine 自己手刻 JWT(JSON Web Token,一段可驗簽的字串)解析,改由 Cloud Run IAM(GCP 的權限管理系統)接手Engine 的對外入口(ingress)設成「不開放未授權」,再把 Portal 的 service account 綁上 roles/run.invoker(=「准許呼叫這個服務」的角色),等於在平台層宣告「只有這個身分進得來」。所以上面那三件驗證,GCP 大多在入口處就替你做掉了,下游程式裡幾乎不必自己驗 incoming token。代價是:這層保護整個押在 IAM 設定對不對——萬一哪天把 invoker 開成 allUsersallAuthenticatedUsers,光有 audience 就形同虛設,同平台任何 workload 都能敲門。安全姿態從「我的程式驗得夠不夠嚴」,挪成了「我的 IAM 綁得夠不夠窄」。

把整趟流程和「攻擊被擋在哪一層」收成一張圖:

https://ithelp.ithome.com.tw/upload/images/20260812/20183385E27ofsMK9K.png

呼應 Day 4 鋪過、明天 Day 12 要正式展開的「宣告式抽換」基調,這套認證方式本身也是可抽換的——用哪種 s2s 認證,靠一行設定切換,不寫死在程式裡:

portal:
  mcp:
    auth:
      provider: google-idtoken      # 可選 static | oauth2 | google-idtoken
      google-idtoken:
        target-audience: https://engine-xxxx.run.app

三種 provider 各自的定位:

provider 機制 適用場景
static 最陽春的固定權杖 本機或測試用
oauth2 走標準授權流程 一般 OAuth2 對接
google-idtoken 上面講的雲平台 workload identity 跑在該雲平台上的正式環境

對主流程來說,它只知道「我需要一個 Authorization header」,至於那個 header 怎麼來的,是設定決定的事,換的只是底下那顆「怎麼弄到一張可信 token」的零件。

兩個工程細節順帶補上,這篇 s2s 才算收完整。其一,token 不是每次呼叫都現鑄——那會把 metadata server 打爆;實作上會把鑄到的 token 快取住、快過期前一小段才換新,而驗證端也會留一點時鐘偏移(clock skew)的寬限,免得兩台機器時間差幾秒就把好票誤判成過期。其二,workload identity 的 OIDC token 不是唯一解:在有 service mesh 的環境裡,mTLS(雙向 TLS、兩端互驗憑證)是另一條主流路線,把「證明我是誰」直接下放到傳輸層。兩者不互斥,差別在信任根與維運模型:OIDC 押在平台的身分系統,mTLS 押在憑證體系;選哪條,多半看你的基礎設施已經有哪一套。

取捨:零金鑰落地,代價是被雲平台綁住

這套設計有三個值得稱讚的地方,但每一個都連著一筆要算清的帳。先用一張表把這些帳的兩面攤開,再逐項展開說明:

優點 連著的代價
全程沒有任何長期金鑰檔落地 信任根基從「我保管好祕密」換成「我信任雲平台身分系統」
dev 與 prod 對稱、零改 code 本機「連不到就略過」是開發便利,不是正式環境等級的安全姿態
介面穩定、實作可換 換雲平台得重寫一個取 token 的 provider 實作
程式碼幾乎不必自己驗下游來的 token 防「同平台冒充」整個押在 Cloud Run IAM——invoker 綁定一旦放寬,audience 就守不住

第一,全程沒有任何長期金鑰檔落地

程式碼、設定檔、環境變數裡都不存放 service account 金鑰。token 是執行時當場鑄、用完即棄、生命期很短。這直接把「金鑰外洩」這個 s2s 最大的風險從根上拔掉——沒有東西可洩漏,自然不必煩惱輪換與事後補救。代價是,你把信任的根基從「我保管好一把祕密」換成了「我信任雲平台的身分系統」。這在絕大多數情況下是更划算的交易,但它意味著這條信任鏈的健康度,現在取決於雲平台 metadata server 的可用性與正確性——那是你交出去、不再直接掌控的一塊。

第二,dev 和 prod 對稱、零改 code

這是工程體驗上最舒服的一點。本機開發時,metadata server 那個內部端點根本連不到(它只存在於雲平台內部),於是鑄 token 這一步會直接略過、不掛 Authorization 就把請求送出去;同一包 code 部署上雲,metadata server 連得到了,token 就自動帶上。工程師在自己筆電上不需要任何雲端憑證、不必下載任何金鑰檔,clone 下來就能把整套服務跑起來——這跟 Day 8 講的「本機可離線開發」精神一致,也是壓低上手門檻的關鍵。

但這裡有個邊界要坦白講清楚:

本機這條「連不到就略過」的路徑,等於是「沒帶身分證明也放行」,它依賴的前提是「本機環境下沒有真正的下游需要保護」。它是開發便利性的妥協,不是正式環境等級的安全姿態——千萬別把這套行為誤當成正式環境也能省掉認證。

正式環境裡 metadata server 一定在、token 一定鑄得出來,這條旁路不該被觸發。

不過「不該被觸發」還不夠穩,值得再往前一步:別讓「連不連得到 metadata server」這種網路結果來決定安全模式——設定錯、換個部署位置、網路抖一下,都可能讓它意外走進無認證那條路。更保險的寫法是把它變成一個明確的 profile 決策:只有 localtest profile 才啟用「略過 token、用假身分」,而非本機的 profile 一旦鑄不到 token 就中止下游呼叫(fail-closed),絕不默默不帶憑證送出去;啟動時再把當前認證模式印出來,正式環境直接拒絕 staticnone/mock 這類危險組合。這正是 Day 10 那條原則的延伸——高風險的那一側,拿不準時要往「關」的方向倒。

第三,介面穩定、實作可換,但換雲平台是真有成本的

前面那段 YAML 的 provider 抽象,讓「換認證方式」變成換設定而非改主流程。可是要看清它的邊界:google-idtoken 這個實作是貼著 GCP 的 metadata 機制寫的——那個 http://metadata.google.internal/... 內部端點、Metadata-Flavor: Google 這個固定 header、回傳格式,都是 GCP 專屬的協定。哪天要搬到另一家雲,OIDC、audience 驗證這些「觀念」完全通用、不用重學,但「怎麼向這家雲的 metadata server 要 token」得重寫一個 provider 實作。

好消息是,因為介面早就抽象好了,這個改動被框死在一個零件裡:主流程、下游驗證邏輯、其他所有呼叫端都不動。對 Portal 主體來說,「服務間認證」永遠只是「給我一個 Authorization header」這麼一條約定,換雲平台是換一顆螺絲,不是拆引擎。誠實地說,它談不上「零成本可攜」,頂多算「換零件可攜」——你仍得為新平台寫並測一個取 token 的實作。

把這三點疊起來,s2s OIDC 是一個很乾淨的取捨:用「把信任根基託付給雲平台、並接受一定程度的平台綁定」,換來「程式裡零長期金鑰、dev/prod 對稱、外洩風險從根消除」。對一座定位是對外閘道、又跑在雲端的 Portal 來說,這筆交易非常划算——前提是你清楚知道哪一段是正式環境等級的保護,哪一段(本機略過 token 那條)只是開發期的便宜行事。

第三章到這裡收束:身分這件事,從使用者進門(SSO、簽章 cookie、correlation id、容錯不對稱)到 Portal 自己對下游出門(s2s OIDC),兩個方向的信任都補齊了。共通的哲學是同一條——盡量不自己保管狀態與祕密,讓身分成為「可被當場驗證的事實」,而不是「要回頭查詢、要小心藏好的東西」。 而那個讓認證得以一行設定切換的 provider 抽換,背後的功夫,明天就要當主角。

明天,我們「換了不同廠商,可是不需要去更改他程式碼內容」也就是說:同時養著好幾家 LLM、檢索器、守門,卻不必為了多接一家就動主流程一根寒毛,聽起來就很難。


上一篇
第十天|容錯不對稱:fail-open vs fail-closed
下一篇
Day XII|「不要動我的寶貝!!」-換模組不用換程式
系列文
轉生到全端工程師沒多久就要負責公司的大平台??18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言