IdP
Okta / Entra。人类认证永远发生在这里,我们只消费断言。
Integration · primary path
这是 V1 的主接入路径:agent 经你既有的 MCP 网关访问远程/内网 MCP server,AgentPAM 作为外部 Authorization Server + PDP + 凭据 Broker 插在中间。(本地笔记本 / stdio 场景不在当前范围内,属 V2+ 长期路线图。)
Deployment topology
IdP 是你的,MCP Gateway 是你的,MCP server / API 是你的。我们只提供中间那个控制平面容器组 —— 它跑在你自有的 Linux 上,可气隙,无任何出站回连。
+-- Your own Linux environment (air-gap capable, no outbound callback) --------+ | | | +------------+ OIDC federation +------------------------------+ | | | Your IdP |<----------------------| AgentPAM control plane | | | | Okta/Entra | (consumes assertions only) | /authorize /token AS+STS | | | +-----+------+ | PDP AuthZEN / COAZ | | | | human SSO (Tu) | Credential broker RFC 8693 | | | +-----v------+ ext_authz / AS | Audit sink | | | | Your MCP |<--------------------->| JWKS (independent HA) | | | | gateway | inject cred (Tup) +------------------------------+ | | | (RS) | | | +-----+------+ | | | inject short-lived credential | | +-----v------+ | | | Sensitive | <- remote / internal, NOT a laptop | | | API/server | | | +------------+ | +------------------------------------------------------------------------------+ ^ agent holds Ts (session token, DPoP, 5-15 min) +----+-----+ | AI Agent | managed workstation / VDI (agent is on the endpoint, | Claude | but the MCP server is not) +----------+
Okta / Entra。人类认证永远发生在这里,我们只消费断言。
它是 Resource Server。我们接入它,不替换它。
远程或内网托管,非笔记本。凭据由 broker 注入,agent 看不到。
Connect the IdP
AgentPAM 不认证人类(宪法 C-02)。人类认证发生在你的 IdP,我们通过 OIDC 联邦信任它签发的断言。我们不存人类口令、不做 MFA。
/callback 内网地址。sub、email、groups / roles)。identity_providers:
- id: corp-okta
issuer: https://corp.okta.internal # your IdP, internal
jwks_uri: https://corp.okta.internal/oauth2/v1/keys
audience: agentpam # we are the audience
claim_mapping:
subject: sub
email: email
groups: groups| IdP | 方式 | 备注 |
|---|---|---|
| Okta | OIDC + ID-JAG / Cross-App Access | 首选。EMA(Enterprise-Managed Auth)已 stable,把同意与策略移到 IdP,无同意屏 |
| Microsoft Entra | OIDC + OBO(On-Behalf-Of) | 直接消费 Entra Agent ID 的 facet claim(xms_act_fct / xms_sub_fct) |
| Google Workspace | OIDC | 标准 OIDC 联邦 |
| Keycloak / 自建 | OIDC + RFC 8693 | 气隙客户常用;Keycloak 原生支持 token exchange |
| SAML-only 老 IdP | SAML 断言 → 我们转 OIDC | 兜底路径 |
Connect the gateway
DR-001 修正后:不存在「通吃」路径(我们已经撤回过那个说法)。按你的网关型号选一条。第三条是兜底。
| 网关 | 接入路径 | 你改什么 |
|---|---|---|
| agentgateway / ContextForge / Kong EE / AWS AgentCore | 路径 B:我们当外部 AS | 一段配置指向我们 |
| Envoy/Istio / Kong OSS / Pomerium / Obot / 有 PDP 钩子的 | 路径 A:我们当外部 PDP | ext_authz / OPA 指向我们 |
| Docker MCP Gateway / 无钩子 | 路径 C:作为上游 MCP 代理(兜底) | 网关把我们当上游 server |
# 1) the gateway publishes protected resource metadata (RFC 9728) # GET /.well-known/oauth-protected-resource -> mcpAuthentication: issuer: https://agentpam.customer.internal # points at us audiences: [ "https://mcp.customer.internal" ] # * MANDATORY in agentgateway jwksUri: https://agentpam.customer.internal/.well-known/jwks.json # 2) upstream credential injection: the gateway exchanges for Tup, the agent never sees it backendAuth: oauthTokenExchange: path: /token # * local YAML key is 'path', NOT tokenEndpointPath grantType: urn:ietf:params:oauth:grant-type:token-exchange
键名按 G-04 源码核对更正(resource.proto):本地 YAML 键是 path,不是 tokenEndpointPath(后者是 xDS/proto 字段名,写进配置文件会加载失败)。audiences 在 agentgateway 里是结构性必填,缺失则配置加载失败 —— 这是安全的默认。⚠️ 注意这段 backendAuth 换取 / 注入链路是配置级理解,未对真实后端运行时验证(G-22)。
# Envoy ext_authz -> our PDP http_filters: - name: envoy.filters.http.ext_authz typed_config: grpc_service: envoy_grpc: { cluster_name: agentpam_pdp } # points at our PDP with_request_body: # * pass body so we see MCP method/tool max_request_bytes: 8192 allow_partial_message: true --- our PDP interface (AuthZEN / COAZ-MCP compatible) --- POST /pdp/evaluate { "subject": { "id": "u_dana", "type": "human" }, # the human "context": { "agent": "agent_support_7f3a", # agent, 2nd dimension (COAZ) "task": "task_88213" }, "action": { "name": "tools/call", "tool": "payments.refund" }, "resource": { "server": "payments", "args": { "amount": 120 } } } -> 200 { "decision": true, "obligations": ["audit:high"], "headers": { "Authorization": "Bearer <Tup short-lived>" } } # injected on allow
关键是允许 PDP 在 allow 时改写 header(凭据注入原语),并把请求体传给我们以便看到 MCP 的 method / tool。我们的 PDP 接口兼容 AuthZEN / COAZ-MCP:subject 是人,context.agent 是 agent 第二维度。
网关无任何钩子时(如 Docker MCP Gateway):网关把我们配成上游 MCP server,我们在真实 server 前做策略执行代理。代价:多一跳,只看得到被路由过来的流量。优点:对你的网关零要求。对 Docker MCP Gateway 这是唯一可行路径 —— 它的 before:http: 钩子无法改 header,路径 A 也不成立。
mcpAuthentication 即把我们发布为 AS 并发出 RFC 9728 challenge)。真正换取并注入 Tup 的 backendAuth.oauthTokenExchange 链路尚未对真实后端运行时验证(G-22),只到配置级。路径 B 干净支持的其余三家与所有路径 A 的片段同样是文档级、非运行时验证。我们不会把这些说成「已验证」,完整矩阵见 架构页。
End to end
[1] Human SSO -> your IdP -> Tu (OIDC, ~1h) [2] agent -> AgentPAM /token (RFC 8693) subject_token=Tu, actor_token=agent instance assertion authorization_details=[{ type: agent_task, task_id, purpose, sensitivity }] AgentPAM verifies Tu (against your IdP JWKS) -> issues Ts Ts: sub=human, act={sub: agent_instance}, cnf.jkt (DPoP), 5-15 min [3] agent calls your MCP gateway with Ts gateway verifies Ts (against OUR JWKS) + audience [4] per tools/call -> PDP decision: server / tool / parameter + monotonic narrowing [5] allow -> gateway exchanges at AgentPAM /token for Tup (upstream cred, 60-300s) ** the agent never touches Tup ** [6] gateway injects Tup and calls the upstream API / MCP server [7] AgentPAM writes the audit event (human/agent/task/tool/resource/decision/traceparent) [8] task ends or CAEP revocation -> Ts invalidated immediately (no wait for TTL)
整条链只有委派语义,没有冒充。sub 永远是人,act 承载 agent 实例。第 5 步之后,agent 从不接触 Tup(上游凭据)。
One-day POC
| # | 动作 | 谁做 | 验证 |
|---|---|---|---|
| 1 | docker load 导入 AgentPAM 容器组镜像(离线介质) | 你的运维 | docker images |
| 2 | 配 agentpam.yaml:IdP issuer / jwks、audience | 你的运维 | 启动日志无错 |
| 3 | 在 IdP 注册 AgentPAM 为 RP | 你的 IdP 管理员 | discovery 可达 |
| 4 | 网关配置指向我们(路径 B 的 metadata + token 端点,或路径 A 的 ext_authz) | 你的平台团队 | curl .well-known/oauth-protected-resource 返回我们 |
| 5 | 注册第一个 AgentBlueprint + AgentIdentity(含人类 sponsor) | 你的平台团队 | Portal 显示 |
| 6 | 定义策略(server / tool / 参数 三级) | 你的安全团队 | 策略生效 |
| 7 | 跑一次 agent 调用 | 你的开发者 | 见下方验收 |
| # | 检查 | 期望 |
|---|---|---|
| 1 | agent 调一个允许的工具 | 成功;上游日志见短时凭据 |
| 2 | agent 调被策略拒绝的工具 | 请求不抵达上游,审计有拒绝记录 |
| 3 | 参数超限(refund $2500 > $500) | 参数级拒绝 |
| 4 | 抓 agent 会话,搜上游凭据 | 找不到 Tup(只有 Ts) |
| 5 | 审计事件 | 含人 + agent + 任务,可关联 traceparent |
| 6 | 标记任务完成后再调 | 立即拒绝(即使 Ts 未到期) |
| 7 | 断开 AgentPAM 再重启网关(HA 测试) | ⚠️ 见下方「两件诚实话」—— 这是我们必须告知的耦合 |
Bridge, not bucket
上一节承认了我们是高价值目标。这一节是它的架构答案:一个存下所有客户凭据的中介只是集中了风险(Salesloft Drift 正是如此塌的)。我们不做那个篮子。
AgentPAM 的任何持久化存储中,都不存在可直接用于访问客户上游 API 的长期凭据明文。
| 密钥材料 | 是什么 | 能做到「桥」吗 | 处置 |
|---|---|---|---|
| ① 上游 API 凭据 | 客户 GitHub / DB / 云的访问凭据(Tup 的来源) | ✅ 能 | 不存。 请求时从客户 vault / 云 IAM 动态取,注入后即焚 |
| ② 客户 IdP 令牌(Tu) | 人类 SSO 令牌 | ✅ 能 | 不存。 只在一次 token 交换中校验,不落库 |
| ③ 我们的签名私钥 | 签 Ts / JWKS 的密钥 | ❌ 必须存在某处 | 入 HSM / KMS,硬件内生成、永不导出、只做签名 |
| ④ Secret zero | 我们凭以访问客户 vault 的引导凭据 | ⚠️ 最难 | 尽量无长期密钥:projected SA token / workload identity;裸机回退到可轮换 mTLS |
| 模式 | 机制 | 我们存了什么 |
|---|---|---|
| A. Vault 直通 | 客户用 HashiCorp Vault / Infisical。请求时调其动态 secret 引擎签一个短时凭据,注入,即焚 | 什么都不存 —— 连引用都是客户 vault 的 path |
| B. 云 STS 交换 | 客户用云 IAM。用 workload identity 换 STS 短时凭据(如 AWS AssumeRole 15min),注入,即焚 | 只存一个角色 ARN 引用 |
| C. 上游 OAuth 客户端 | 上游是 SaaS(GitHub / Slack)。refresh token 存在客户的 vault,不在我们这里;请求时取来换 access token,注入,即焚 | 只存 vault 引用 |
| 篮子(Salesloft 模式) | 桥(AgentPAM) | |
|---|---|---|
| 直接拿到 | 所有客户所有上游凭据明文 | 一堆 vault path / ARN 引用 + 无法导出的 HSM 句柄 |
| 兑现还需要 | 无,直接用 | 穿过 secret zero → 客户 vault 的独立策略与审计 |
| 每次兑现 | 无痕 | 留痕、可限速、可被客户 vault 撤销 |
| 爆炸半径 | 所有客户(多租户 SaaS) | 单个客户(私有化,C-13) |
| 客户侧应急 | 依赖 SaaS 厂商 | 自己轮换 vault 凭据、撤销 secret zero、停容器 —— 主动权在客户 |
这六项客户自己就能跑。第 1 和第 6 项是核心 —— 它们经验性地证明了 BNB-0:我们的存储里没有鸡蛋,凭据的生杀大权在你手里。
| # | 验证 | 期望 |
|---|---|---|
| 1 | 转储我们的持久化存储(DB / 磁盘),grep 上游凭据特征(ghp_ / sk- / AKIA / DB 密码) | 零命中 —— 只有 path / ARN 引用 |
| 2 | 检查 HSM / KMS 配置,确认签名私钥不可导出 | CKA_EXTRACTABLE=false |
| 3 | 断开我们到你 vault 的连接,观察 agent 调用 | 失败 —— 证明凭据是实时取的,不是我们存的 |
| 4 | 审计我们的出站网络(气隙下应为零) | 无「回家」连接(C-15) |
| 5 | 验证镜像签名 + SBOM | 签名有效、依赖可核 |
| 6 | 撤销你 vault 里的一个凭据,立即重试 agent 调用 | 立即失败 —— 证明我们不缓存、不留存 |
Ts / JWKS 要用我们的私钥签名,这把钥匙必须存在某处 —— 我们靠 HSM 做到「存但拿不走」,不是「不存」。残余风险:签名预言机 —— RCE 后攻击者拿不到密钥,但可能诱导 HSM 合法签发恶意令牌。这靠策略侧防御(签发内容模板校验 + 限速 + 异常检测),HSM 挡不住。
K8s 用 projected SA token、云上用 workload identity federation 都能做到无长期密钥;但气隙裸机最好情况是一个可轮换的 mTLS 客户端证书,不是零密钥。即便它泄漏,攻击者拿到的是「访问你 vault 的权限」,仍要穿过你 vault 自己的策略、审计与撤销。
即使在桥上,凭据也会在取出→注入之间于内存出现 ≤ 秒级。缓解:注入后立即 zeroize、不进日志、不进长生命周期对象、默认不跨请求缓存(以最小暴露换一点延迟)。我们不假装这个窗口是零。
Two honest disclosures
agentgateway 在启动时急切拉取 JWKS 且 fail-closed —— 如果那一刻 AgentPAM 不可达,网关可能起不来。缓解:JWKS 端点独立高可用部署,与策略引擎故障域隔离,并支持你侧静态托管 JWKS。这必须写进 SLA 与部署文档,我们不隐瞒。
audiences 会静默关闭校验agentgateway 结构性强制 audiences(缺失即配置加载失败,安全)。但若你用 Envoy,省略 audiences 会静默关闭 audience 校验 —— token 可被跨受众重放。部署检查必须主动探测这一项,不能假设默认安全。
缓解:见上方「桥不是篮子」—— 我们的持久层里没有可直接访问上游 API 的凭据明文(不变量 BNB-0)。加上根密钥入你的 KMS / HSM、单租户私有化天然隔离、审计哈希链防篡改。桥 + 私有化 = 双重收窄:爆炸半径限制在你自己的故障域内,被攻破时拿不到可离线带走的凭据库 —— 只剩一个留痕、可撤销、限单租户的活预言机(诚实边界见本节 §三处做不到绝对的桥)。