iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
AI Security

Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統系列 第 16

Day 16|Microsoft Foundry 的 AI Agent 攻防實戰:換掉共享 API Key

  • 分享至 

  • xImage
  •  

Day 15 已經把 tenant_id 從使用者輸入拿掉,查詢 bamboo-hq 文件時,租戶條件由伺服器端的 principal 產生。這個修補有一個很現實的前提:principal 必須真的可信。

如果大魔術熊貓工程司前面仍放著一把所有人共用的 API key,系統頂多知道「有人帶了那把 key」,卻分不出是 Alice、FastAPI、排程工作,還是哪一隻半夜偷偷加班的 Agent。稽核紀錄裡大家穿同一件熊貓裝,最後當然只能認出熊貓,認不出誰動了竹筍採購單。

先備名詞:先分清楚「人」和「代表人做事的程式」

  • Principal(安全主體):可以被驗證、授權與稽核的身分,例如 Alice、FastAPI 服務或某個 Agent。
  • Workload identity(工作負載身分):給程式使用的身分;它證明「哪個服務在呼叫」,不等於證明「哪位使用者有權做這件事」。
  • Managed Identity(受控身分):Azure 代管憑證生命週期的 workload identity。它省去長效密碼,卻不會自動縮小 RBAC 權限。
  • Authentication/Authorization:前者確認「你是誰」,後者才判斷「你能不能做這件事」。登入成功不是業務操作的通行證。

Threat/Attack:共享 key 把兩位 caller 壓成同一個 actor

畫面判讀目標: 看見共享 credential 如何讓不同 caller 在 audit 中變成同一 actor。

Identity comparison 顯示 Alice 與 Mallory caller 都被記為同一 legacy actor。

共享 key 讓稽核失去誰真正發起請求的資訊。 可觀察狀態:兩個 caller alias 對應同一 legacy actor,side_effect_count=0。 Claim boundary:使用 synthetic credential;未執行 Entra authentication。

大魔術熊貓工程司的舊版採購 API 接受同一把 shared key。若前面還有聊天介面,Alice 只會說「幫我查一下我剛送出的費用申請」,拿到 key 的另一個人也只需送出一個普通請求,例如「幫我查一下 exp-bamboo-001 現在處理到哪裡了」。沒有人需要替測試程式背台詞;漏洞發生在 HTTP authentication boundary,不在這兩句話的寫法。

圖中的 K_shared 是測試框架持有的 credential 代號,不是 user message,也不會送進模型:

alice-client ──────┐
                    ├─ K_shared ─→ authentication boundary
mallory-client ────┘

兩次 request 的 claimed_caller 不同,舊版 authenticate_workload() 卻把兩筆 authentication outcome 都記成同一個 actor:

{
  "shared_key_attack": {
    "claimed_callers": ["alice-client", "mallory-client"],
    "distinct_audit_actors": 1,
    "reason_codes": [
      "LEGACY_SHARED_CREDENTIAL_ACCEPTED",
      "LEGACY_SHARED_CREDENTIAL_ACCEPTED"
    ]
  },
  "side_effect_count": 0
}

這裡要特別把名詞講準。ControlExecutionOutcome.receipt_count 是 cumulative executor 對 outcome 的記錄欄位;這兩筆的 action 都是 authenticate_workload,不是工具呼叫,也沒有副作用。攻擊成立的證據是兩個 caller 只剩一個 audit actor,不是 Agent 已經動過採購單。共享 key 若被複製,撤銷時通常也得整把輪替;正常程式與攻擊者一起被拔插頭,這不是我們想要的身分生命週期。

本篇的 key、token、tenant 與人名全部是本機 fixture。credential canary 只存在 authentication 測試與輸出掃描,不是使用者必須輸入的內容。程式不登入 Microsoft Entra ID、不取得 Azure access token,也不把任何 credential 傳進 prompt。

小學堂:四種身分解決的是四個問題

在 Foundry 架構裡,Managed Identity、project identity 與 Agent identity 很容易被寫成一團。先把今天需要的四個問題分開:

這一跳 要回答的問題 大魔術熊貓工程司的身分
使用者 → FastAPI 誰正在要求操作? Alice 的 delegated user principal
FastAPI → Foundry 哪個後端 workload 在呼叫? FastAPI 的 system-assigned 或 user-assigned MI
Foundry project 平台工作 哪個 project 執行平台層工作? project managed identity
Agent → 下游工具/資料 哪個 Agent 在執行? shared development Agent identity,或發布後的 distinct Agent identity

截至 2026-08-03,Microsoft 文件說明:Foundry 的未發布/開發中 Agent 會共用 project 內的 Agent identity;發布 Agent 時,平台會建立綁定該 Agent application 的 distinct identity。Agent identity blueprint 與 project managed identity 之間可使用 federated trust,但真正要在下游資源取得 RBAC 的 principal 是 Agent identity,不是因為 project MI 參與了信任鏈,就把下游角色全指派給 project MI。Agent identity concepts(查閱:2026-08-03)

有些工具連線仍提供 project managed identity 作為認證模式,所以不能只看「Agent 有 identity」便猜測實際送到下游的是哪個 principal。正式驗收要保存 Agent 發布狀態、connection authentication mode、實際 principal alias 與下游 allow/deny。

Fix:Inbound 先驗 token,再建立業務 principal

畫面判讀目標: 確認 inbound authentication 與 business authorization 分層。

Inbound auth matrix 顯示 missing、wrong audience、app-only 與 delegated token 結果。

先驗 token 建立 principal,再進 business PEP。 可觀察狀態:401/403 reason 分開;只有簽章 client 與 scope 符合者進入 PEP。 Claim boundary:token 皆為合成 fixture,不是 Microsoft Entra validation receipt。

FastAPI 是 access token 的 intended audience,所以驗證應發生在 API boundary。至少要驗 signature、issuer、audience、時間限制,以及 tid、subject 與 actor;delegated request 再依 scp 判斷,app-only request 則走獨立的 application policy。Microsoft 也明確提醒,access token 應由收到它的 Web API 驗證,client 不應自行把 token 解開後當授權結論。Claims validation(查閱:2026-08-03)

V2 Lab 使用 SyntheticTokenIssuer 固定這個契約。它是本機 HMAC fixture,不是 JWT 實作,也不能拿去代替 Entra middleware:

from datetime import timedelta

from magic_panda_agent.auth import SyntheticTokenIssuer
from magic_panda_agent.models import Principal

issuer = SyntheticTokenIssuer(key=b"SYNTH_MAGIC_PANDA_LOCAL_SIGNING_KEY_32B")

alice = Principal(
    subject="alice",
    tenant_id="bamboo-hq",
    roles=frozenset({"employee"}),
    auth_type="synthetic-delegated",
)

token = issuer.issue(
    alice,
    audience="api://magic-panda-local-lab",
    token_type="delegated",
    client_id="first-party-lab-client",
    scopes=frozenset({"Agent.Invoke"}),
    ttl=timedelta(minutes=2),
)

claims = issuer.validate(
    token,
    audience="api://magic-panda-local-lab",
    require_delegated=True,
)
principal = issuer.principal(claims)

Principal 才能進入 policy 與工具執行器;原始 token 不得進入 AgentRequest、model prompt、tool arguments、trace 或錯誤訊息。request body 自填的 role="finance" 只是未簽資料,不能取代 verified claims。

負向案例要一次只改一個條件:

Matrix key 改動 預期 authentication outcome
missing 沒有 token AUTH_REQUIRED
wrong_audience audience 錯誤 TOKEN_WRONG_AUDIENCE
expired token 已過期 TOKEN_EXPIRED
future_issued iat 超出允許的 30 秒 clock skew TOKEN_ISSUED_IN_FUTURE
not_yet_valid nbf 尚未生效 TOKEN_NOT_YET_VALID
valid 短效 workload fixture TOKEN_VALID

互動 HTTP route 另有一組測試:unsigned identity headers、wrong audience、app-only
token、不在 server allowlist 的 delegated client,以及缺少已簽 Agent.Invoke scope
都 fail closed;只有 client 與 scope 同時由簽章 claims 證明的 delegated token 才會進到
application PEP。401 回應帶 RFC 6750 WWW-Authenticate: Bearer challenge;已驗證身分但
client/scope 權限不足則回 403。這兩組矩陣不要混成同一批 case ID,
否則截圖看起來很完整,實際上對不到 structured capture JSON。

Outbound:開發者登入與 production workload 明確分流

畫面判讀目標: 確認明示 environment 只選擇 credential class,且不請求 token 或呼叫雲端。

Credential class-selection UI 顯示 development、production、invalid 結果以及零 token 和 cloud calls。

這張圖證明 class selection,不是雲端身分驗證。 可觀察狀態:development=AzureCliCredential、production=ManagedIdentityCredential、invalid fail closed,token_requests=0、cloud_calls=0。 Claim boundary:這是 class selection only;未取得 token、未呼叫 Azure,也不證明 Managed Identity 或 RBAC 已設定。

Foundry 的 Entra token scope 是 https://ai.azure.com/.default。Microsoft 建議 production workload 使用 Microsoft Entra ID;API key 雖可用於部分快速原型,卻不能表達個別使用者,授權粒度與稽核能力也較弱。Agents 等部分能力要求 Entra ID,不能把「Entra 失敗就改用 key」藏成 fallback。Foundry authentication and authorization(查閱:2026-08-03)

Lab 的 credential factory 刻意要求環境明確:

from magic_panda_agent.auth import EntraCredentialFactory

# APP_ENV=development:AzureCliCredential
# APP_ENV=production:ManagedIdentityCredential
# 未指定或其他值:fail closed
credential = EntraCredentialFactory.explicit()

# 不要 print token;這裡只展示 audience contract。
access_token = credential.get_token("https://ai.azure.com/.default")

這個選擇是為了防止 production process 不小心沿著寬鬆 credential chain 撿到開發者的 CLI 登入狀態。ManagedIdentityCredential 只解決 workload 如何取得與輪替 credential,RBAC scope 仍要在 Day 18 逐項驗證。

正式環境至少應保留以下 separation of duties:

Principal 建議用途 不該因此取得
Alice user principal 呼叫 FastAPI 的 delegated scope Foundry project 管理權
FastAPI MI 呼叫指定 Foundry Agent endpoint subscription-wide Owner
project MI 明確的平台/connection 工作 所有 Agent 的下游資料權
distinct Agent identity 指定工具與資源的最小 data role 其他 Agent 的工具權限
CI workload identity 部署所需 control-plane action runtime secret read

GA/Preview 邊界

截至 2026-08-03,Foundry readiness 頁把 Agents core、Entra authentication 與核心 RBAC 放在 GA 範圍;hosting 文件的粒度卻不完全一致:hosting 總覽稱 managed Hosted Agents GA,Agent Framework 的 Hosted Agents 子頁仍標 Preview,部分 session/API 也要求 preview surface。最安全的做法不是替整個「Hosted Agents」貼一張永久標籤,而是依實際使用的 managed service、session API、SDK/package 與 region 分別核對。本篇的本機 identity contract 不依賴任何 hosting package。

Managed Identity 也有環境前提:ManagedIdentityCredential 要在已配置 MI 的 Azure compute 上才是 MI 證據。在筆電以開發者 credential 取得 token,不會因為變數名稱寫成 managed_identity 就變成 Azure 托管身分。

實作後的資料流

Identity graph:nodes 與 edges

畫面判讀目標: 核對identity runtime graph 的 nodes、edges、format 與 rendered flag的 canonical runtime fields。

identity runtime graph 的 nodes、edges、format 與 rendered flag。

先讀 identity/data flow graph 本身。 可觀察狀態:graph_source 顯示 workload identity 從來源到 downstream resource 的節點與邊,rendered=true。 Claim boundary:畫面沒有 Foundry hop、outbound credential 或逐 hop token audience;也未執行 Entra、Agent identity 或 downstream cloud calls。 此子畫面只涵蓋identity runtime graph 的 nodes、edges、format 與 rendered flag。

Identity graph:D16-B02 outcome

畫面判讀目標: 核對D16-B02 source outcome 的 status、decision reasons 與零副作用的 canonical runtime fields。

D16-B02 source outcome 的 status、decision reasons 與零副作用。

再用 bounded outcome 驗證 graph 中的政策結果。 可觀察狀態:D16-B02 顯示 status、decision_reasons、receipt_count 與 side_effect_count。 Claim boundary:畫面沒有 Foundry hop、outbound credential 或逐 hop token audience;也未執行 Entra、Agent identity 或 downstream cloud calls。 此子畫面只涵蓋D16-B02 source outcome 的 status、decision reasons 與零副作用。

下列六步是目標架構;本圖實測只涵蓋前 3 步與 local tool,沒有執行第 4、5 步的雲端 hop:

  1. FastAPI 收到 bearer token,但不把它放進 Agent 輸入。
  2. authentication layer 驗證 token,建立 typed Principal
  3. business PEP 依 (tenant, subject, resource, action) 做決策。
  4. FastAPI 以自己的 MI 取得 Foundry token,audience 固定為 https://ai.azure.com/.default
  5. Agent 呼叫下游時,再依該 tool connection 的認證模式決定使用 Agent identity、project MI 或後續 Day 17 的 delegated 路徑。
  6. receipt 只記 principal alias、reason code、resource alias 與 count;不記 access token。

看到第五步可能開始有點昏,先抓住一句就好:每一跳都要重新回答「誰在替誰,對哪個資源做哪個動作」。只有「它有一顆 token」不算答案。

Test:先讓本機身分矩陣可以重跑

進入 day16/ 後執行:

uv run pytest tests/stages/day16/test_acceptance.py -q

V2 驗收必須確認:

  • 共享 key attack 讓 alice-clientmallory-client 的 authentication outcome 只留下同一個 legacy actor。
  • 修補後共享 key 得到 VERIFIED_WORKLOAD_IDENTITY_REQUIRED,兩顆不同的短效 workload fixture 留下兩個 actor。
  • missingwrong_audienceexpired 具有固定的 authentication reason;互動 route 的 app-only token 則得到 USER_CONTEXT_REQUIRED
  • 共享 key replay 沒有工具呼叫,side_effect_count=0;不要把 authentication outcome 的 receipt_count 說成工具 Receipt。
  • response、event、fake outbox、error 與 trace 不出現 SYNTH_MAGIC_PANDA_* canary。

本次本機 QA 的結果是 9 passed。第一個 JSON slot 的重點不是總
receipt.count=4,而是 distinct_audit_actors=1、修補後
distinct_verified_actors=2、五個輸出 sink 從命中 5 次降為 0,以及
side_effect_count=0external_callscloud_calls 都是 null:renderer 沒有
network observer,不能把「測試程式沒打算連線」冒充成量到零。那個總 receipt 是
四筆 control-executor authentication outcome 的累計,不是四次業務工具執行。

攻擊/before-state UI 要證明共享 credential 造成 actor 混淆;圖片需顯示兩個 caller alias、同一個 legacy actor、authentication action 與零 side effect,不能顯示 token 值。這張圖沒有 network observer 或雲端 receipt,外部/雲端呼叫只能標成未觀察;它不代表 Entra 或 Foundry 已接受這些身分。從 day16/ 執行:

PYTHONPATH=src:. uv run python scripts/render_stage_evidence.py --day 16 --evidence-id d16-workload-identity-attack-matrix

修補/test UI 使用實際本機 FastAPI path,應顯示 unsigned headers、wrong audience 與 app-only context 都是 401,delegated positive control 則留下 POLICY_ALLOW 與一筆 read-only Receipt。它仍是 synthetic token,不是 Entra 實測;也沒有 network observer 或雲端 receipt,所以外部/雲端呼叫維持未觀察:

PYTHONPATH=src:. uv run python scripts/render_stage_evidence.py --day 16 --evidence-id d16-identity-runtime-graph

這些 structured capture 命令只重現 disclosure-safe JSON;正式發布時,本篇 required app UI 圖
必須由目前相關原始碼經 repository UI capture pipeline 產生,schema 3 manifest 必須以 source-tree SHA-256
與檔案數綁定輸入並交由 strict verifier 重算。正式圖仍只支持 synthetic identity/本機 executor 證據,
不是 Entra、Managed Identity 或 Foundry cloud receipt。

雲端驗證邊界:PENDING-CLOUD

本篇尚未在 V2 重新執行以下雲端案例:

  • Azure compute 上的 ManagedIdentityCredential 實際取 token。
  • FastAPI MI 對指定 Foundry Agent endpoint 的 allow/wrong endpoint deny。
  • unpublished shared Agent identity 與 published distinct Agent identity 的 object/RBAC 差異。
  • Agent identity 對 Search、Key Vault 或 MCP 下游的正負向呼叫。
  • role revocation、token lifetime 與 Conditional Access 行為。

因此 cloud_validation 維持 PENDING-CLOUD。本機 synthetic token 通過,不代表 Microsoft Entra、Managed Identity 或 Foundry endpoint 已經通過。

Residual Risk:沒有 long-lived secret,不代表沒有 blast radius

Managed Identity 的 token 仍可能被同一執行環境中的惡意程式取得並濫用;若 MI 在 resource group 或 subscription scope 擁有過大的角色,攻擊者根本不需要把長效秘密偷走。發布 Agent 產生 distinct identity 之後,也要重新指派並重測下游角色;舊的 shared development identity 權限不應照單全收。

NIST SP 800-207A 對雲端原生應用的 Zero Trust 思路,在這篇落成「每個 principal、resource 與 action 都單獨做決策」;ISO/IEC 27002:2022 則補上身分建立、權限覆核與撤銷的管理視角。localhost evidence 只驗 synthetic authentication contract,雲端 principal 建立、role assignment 與撤銷傳播仍未觀察。

明天要處理更麻煩的狀況:FastAPI、Agent 與 Alice 的身分都是真的,Agent 仍可能拿自己的權限替 Alice 讀到 Bob 的費用。那時候問題已經不是「誰登入」,而是使用者的 authority 有沒有跟著走到下一跳。

第一方參考資料


上一篇
Day 15|Microsoft Foundry 的 AI Agent 攻防實戰:跨租戶 RAG 的三個常見攻擊與修補
下一篇
Day 17|Microsoft Foundry 的 AI Agent 攻防實戰:三張識別證都是真的,Agent 為什麼仍替 Alice 越權?
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言