Agent 憑證與密鑰管理實作課:別把金鑰塞進 Prompt——短期憑證、OIDC 與 Vault 的三層防線

前言:最貴的一行 log

前陣子,一次值班檢查的執行紀錄裡出現了 API 金鑰的前二十個字元。不是有人故意外洩,是檢查腳本把帶著金鑰的環境變數印了出來。金鑰當下就輪換了,讀取方式也改成只在腳本內讀入變數、不印出的 helper,但這件事留下一個教訓:金鑰外洩很少是駭客攻進來,多半是我們自己在某個環節把它印出來、寫進檔案、或塞進了不該去的地方。

Agent 讓這個問題放大了三倍。傳統服務的金鑰躺在設定檔裡,誰拿了、去哪了,大致可控;Agent 會自己決定呼叫哪些工具、把什麼寫進 log、把什麼貼進上下文。金鑰一旦進了 Agent 的上下文,就可能出現在模型輸出、工具參數、錯誤訊息、甚至下一輪的提示詞裡。防堵的辦法不是「叫 Agent 小心一點」,而是讓 Agent 手上根本沒有長期有效的金鑰。

這篇文章談三層防線:身分先行、動態憑證、零知識傳遞,以及一套可以在 Kubernetes 上今天就跑起來的骨架。

一、為什麼 Agent 的憑證問題比傳統服務嚴重

三個放大器:

第一,呼叫次數是乘法。 一個值班 Agent 每五分鐘檢查一次,一天就是 288 次工具呼叫。每一次呼叫都是一次憑證暴露的機會。靜態金鑰的有效期是九十天,Agent 一天就把它用了兩百多次——外洩窗口不是九十天,是九十天乘以每天的呼叫次數。

第二,上下文會把秘密帶進模型。 金鑰一旦出現在 Agent 的上下文裡,就脫離了你的控制。模型可能在回答裡把它複述出來,工具可能把它寫進錯誤訊息,下一輪的摘要可能把它當成「重要資訊」保留下來。HashiCorp 的做法是把這條路直接切斷:憑證由 MCP Server 持有,Agent 只拿到執行結果,連看都看不到金鑰本身。

第三,工具鏈的長尾最難管。 Agent 背後往往掛著十幾個 MCP Server、資料庫、雲端 API,每一個都要憑證。人工一個一個輪換不現實,結果就是大家共用同一把金鑰、永遠不換。企業裡非人類身分的數量早已遠超人類員工,而且大多數從未輪換、權限過大、沒人認領——Agent 只是讓這筆舊帳一次到期。

二、三層防線:身分、動態憑證、零知識傳遞

如圖一,三層防線各擋一種風險:身分層回答「你是誰」,動態憑證回答「你這次能拿什麼」,零知識傳遞回答「金鑰經過誰的手」。

第一層:身分先行——先證明你是誰,再談你能拿什麼。 傳統做法是發一把金鑰給 Agent,拿著金鑰就等於是本人。工作負載身分(Workload Identity)反過來:Agent 不持有任何秘密,而是向身分提供者證明「我就是這個工作負載」,再換一張短期通行證。Kubernetes 的 Service Account JWT、OIDC 聯盟、SPIFFE/SPIRE 都是這個形狀:SPIRE 會根據 Pod 的命名空間、Service Account、映像檔等特徵做工作負載驗證(attestation),發出的 X.509 SVID 預設只有一小時效期,JWT SVID 更只有五分鐘。

這條路在 2026 年已經走到模型供應商那一端。Anthropic 的 Workload Identity Federation(WIF)在 2026 年 6 月 17 日正式上線:工作負載拿著自己 IdP 簽發的 OIDC JWT,向 Anthropic 換一張短期存取權杖,完全不需要靜態的 sk-ant-... 金鑰。權杖效期可在 60 秒到 24 小時之間設定,預設一小時;而且驗證鏈最上層的那一關最緊——如果你的 SPIFFE JWT-SVID 只有五分鐘效期,換到的 Anthropic 權杖最多也只有十分鐘。搭配新推出的 Service Account(svac_...),每個工作負載在組織裡有自己的身分、自己的速率限制、自己的稽核軌跡。OpenAI 也在 2026 年 7 月做了平行的發佈。

第二層:動態憑證——用完即丟,租約到期自動失效。 有了身分,下一步是讓憑證本身變短命。HashiCorp Vault 的動態密鑰(dynamic secrets)是代表做法:資料庫帳號、雲端憑證、SSH 憑證都在需要時才產生,帶著租約(lease),到期自動撤銷,不用排輪換時程。HashiCorp 的概念驗證裡,每個聊天工作階段拿到的資料庫憑證五分鐘就到期;Vault 收到撤銷租約的指令,憑證立刻失效,不必等它過期。

Vault 2.1 在 2026 年 9 月把 Agentic IAM 帶出測試,變成 Enterprise 功能:Agent 登記在冊,權限取三者的交集——擁有者的政策、Agent 自己的基準、以及授權上限;高風險操作可以做到每個請求單獨授權,權杖只為那一次請求而存在。開源版沒有這一套,但第一層加第二層的組合——Kubernetes auth 加上動態密鑰——已經能解決九成的問題,不必等 Enterprise 功能。

第三層:零知識傳遞——Agent 不碰金鑰,只拿結果。 前兩層讓憑證變短、變窄,第三層讓 Agent 連碰都不碰。做法是把憑證放在一個仲介(broker)手裡:Agent 送出請求,仲介檢查授權、用自己持有的憑證代打那一次呼叫,再把結果還給 Agent。HashiCorp 的驗證模式就是這個形狀:MCP Server 夾在 Agent 與 Vault 之間,動態憑證由 Server 持有,Agent 拿不到、也到不了目標資源。代價是多一跳延遲、以及仲介本身變成要重點守護的元件。

這裡有一個要小心的時間差:HashiCorp 官方的 Vault MCP Server 目前還是測試版,官方自己提醒不要用在正式環境,原因是它可能把密鑰暴露給客戶端與模型。第三層的觀念是對的,但現成的零件還沒成熟——自己搭的時候,仲介要先做最小權限與稽核,再談方便。

三、實作骨架:Kubernetes 上的動態資料庫憑證

如圖二,靜態金鑰與動態憑證的差別,畫在時間軸上最清楚:前者的外洩窗口是九十天,後者是五分鐘,而且到期不用人動手。以下是一段可以在 Kubernetes Pod 裡跑的骨架:Pod 用 Service Account JWT 向 Vault 證明身分,換一組只活五分鐘的資料庫帳號,用完就丟。


import hvac

# Pod 掛載的 Service Account JWT:身分證明,不是密鑰
with open("/var/run/secrets/kubernetes.io/serviceaccount/token") as f:
    sa_jwt = f.read().strip()

client = hvac.Client(url="https://vault.internal:8200")
client.auth.kubernetes.login(role="drydock-readonly", jwt=sa_jwt)

# 動態憑證:Vault 現場產生一組專屬帳號,TTL 只有 5 分鐘
creds = client.secrets.database.generate_credentials(name="readonly")
db_user, db_pass = creds["data"]["username"], creds["data"]["password"]

run_health_check(db_user, db_pass)  # 只在這次呼叫裡有效

# 租約到期自動失效;也可以主動撤銷,不必等它過期
client.sys.revoke_lease(creds["lease_id"])

三個關鍵都寫在程式裡,而不是寫在提示詞裡:第一,登入用的是 Pod 的身分,不是寫死的金鑰;第二,帳號是每次執行現場產生的,不是共用的靜態帳號;第三,租約可以主動撤銷——值班 Agent 收到可疑告警的第一件事,可以是撤銷這條租約,而不是去改設定檔、重啟服務。

同一套邏輯可以套到模型 API 上:把 ANTHROPIC_API_KEY 從環境變數拿掉,改用 OIDC 聯盟在每次啟動時換權杖。Anthropic SDK 從 v4.8.3 起內建 oidc-federation 憑證提供者,設定只有三個公開識別碼——組織編號、Service Account 編號、聯盟規則編號——沒有一個是秘密。

四、落地前先回答的五個問題

第一,TTL 多短才夠? 從 5 到 15 分鐘起跳。太短會讓 Vault 變成瓶頸、資料庫連線數暴增;太長就失去動態憑證的意義。先量 Vault 每秒能發多少租約,再定 TTL,不要用猜的。

第二,權限取交集了嗎? Agent 的權限應該是三者的交集:擁有者能做的、Agent 這個角色該做的、以及這次任務需要的。值班 Agent 巡檢只需要唯讀,就不要給它寫入資料庫的動態帳號。交集寫在 Vault 政策裡,不是寫在 Agent 的提示詞裡。

第三,稽核能不能回答四個問題? 誰、在何時、用哪個身分、做了什麼。Vault 的稽核 log 與 Service Account 的使用軌跡要能對得起來,否則出事時只知道「某個 Agent 幹的」,不知道是哪一個、替誰幹的。

第四,洩漏演練跑過了嗎? 撤銷一條租約要幾秒?輪換一把金鑰要幾分鐘?這兩個數字要實際測過、寫下來。沒測過的應變流程,等於沒有。

第五,還有哪些靜態金鑰沒清掉? 動態憑證上線之後,最危險的是以為自己清乾淨了、其實角落還躺著一把舊金鑰。盤點要做到檔案層級:設定檔、環境變數、CI 密鑰、聊天紀錄裡貼過的片段,一把一把對。

我的看法

結論:能用。 三層防線的第一層與第二層今天就能用開源零件搭起來:Vault 開源版加 Kubernetes auth 加動態密鑰,不必等 Enterprise 的 Agentic IAM;Anthropic 的 WIF 已經正式上線,Claude API 的靜態金鑰可以直接換掉。真正要花錢、花時間的,是第三層的仲介與正式環境的 Vault MCP Server——那個還在測試版,先看就好,不急著上正式環境。

為什麼是「能用」而不是「先看」:因為這件事的成本結構很清楚。靜態金鑰的成本是「不知道什麼時候會爆」,動態憑證的成本是「多一次 Vault 呼叫與一條租約」。前者是或有負債,後者是固定開銷。對每天跑幾百次工具呼叫的值班 Agent 來說,固定開銷便宜得多。

務實建議:這週先做一件事——把 Drydock、Relay、WebTracker 裡的靜態金鑰列一張表,標出每一把的效期、用途與權限範圍;先把最常用、權限最大的那一把換成動態憑證或 OIDC 聯盟,跑兩週沒問題再換下一把。同一把金鑰不要一邊輪換、一邊還留在舊的讀取方式裡——輪換本身擋不住洩漏,改掉讀取方式才算修好。

結論

Agent 的憑證問題不是「金鑰不夠安全」,是「金鑰的生命週期跟不上 Agent 的呼叫次數」。三層防線各解決一件事:身分先行讓 Agent 不必持有秘密,動態憑證讓秘密短命到不值得偷,零知識傳遞讓 Agent 連碰都不碰。先把第一層與第二層跑起來,第三層等零件成熟再補——順序對了,每一步都有回報;順序錯了,買再多保險庫也只是換個地方放同一把金鑰。

來源

- HashiCorp,Secure AI agent authentication using HashiCorp Vault dynamic secrets(Validated Pattern):https://developer.hashicorp.com/validated-patterns/vault/ai-agent-identity-with-hashicorp-vault

- HashiCorp,Secure AI identity with HashiCorp Vault:https://www.hashicorp.com/blog/secure-ai-identity-with-hashicorp-vault

- arXiv 2504.14760,Establishing Workload Identity for Zero Trust CI/CD: From Secrets to SPIFFE-Based Authentication:https://ar5iv.labs.arxiv.org/html/2504.14760

- DEV Community,Anthropic Just Killed the API Key: A Deep Dive into Workload Identity Federation for Claude:https://dev.to/ramasankar_molleti_f7f80d/anthropic-just-killed-the-api-key-a-deep-dive-into-workload-identity-federation-for-claude-4chj

- ByteIota,Claude API Workload Identity Federation: Drop Keys Now:https://byteiota.com/claude-api-workload-identity-federation-drop-keys-now

Uploaded Image Uploaded Image

沒有留言:

張貼留言