一个 Agent 在 Worker A 上处理完请求后进入休眠。几分钟后,它从快照中恢复到 Worker B,继续响应下一条消息。
业务状态被完整保留了下来,但身份呢?
如果恢复后的进程依然记着模板快照里的旧 ID,多个并发恢复的 Agent 就可能共享同一个身份;如果申请证书的瞬间,Worker 恰好被重新分配了,一次尚未完成的请求甚至可能误拿到另一个 Agent 的凭证。
当底层的运行资源被反复复用时,身份机制就必须跟着调度、快照以及生命周期一起重新设计。
在前两篇文章中,我们讨论了 Agent Identity 的整体方案以及 Agent 访问 Kubernetes 的认证方式。本文不再赘述宏观概念、协议清单和云平台选型,而是直接深入到开源项目 Agent Substrate 的源码中,追踪一条具体的执行链路:
text
恢复 Actor -> 重新绑定身份 -> 申请证书 -> 建立出口隧道
前文阅读:
从万能 Key 到零信任委托:重构 AI Agent 身份安全的四层模型
AI Agent 如何安全访问 Kubernetes:9 种认证方式、用法与选型
说明 :本文资料整理于 2026 年 9 月 8 日。源码分析固定基于 Commitfda35d0c04e4d55c5ddb9e1ff50ed4e1ceafbf5f,避免混用不同版本的接口。本文侧重于架构与源码深度拆解,并非生产部署实测报告。
1. Actor 可以迁移,Worker 身份却不能代替它
Substrate 的核心架构思路是:让海量的逻辑 Actor,复用少量预先准备好的底层 Worker。 当 Actor 空闲时,系统将其内存和文件系统状态保存为快照;等到新请求到来时,再将其恢复至任意可用的 Worker 上。
这里的关键在于界定两者的关系:Actor 是被调度的逻辑工作负载,Worker 则是承载它的物理/容器资源。
即使一个 Worker 先后运行了两个不同的 Actor,这两个 Actor 也绝不应该共享身份;反过来,同一个 Actor 在不同 Worker 之间迁移,也不应该被系统误判为一个新建的逻辑主体。
注:本文讨论的是开源 Runtime agent-substrate/substrate,而非 Google Cloud 官方的 Agent Identity 服务。官方仓库已明确说明:该项目并非 Google 官方支持的产品,且尚未达到生产就绪状态。后续的源码实现均需在此前提下理解。
2. 身份绝对不能跟着 Golden Snapshot 复制
Substrate 允许从 Golden Snapshot(即初始化好的模板快照)中恢复出多个 Actor 实例。
假设模板在初始化启动时,直接把身份信息加载进了环境变量或全局变量:
text
Golden Snapshot(模板快照)
内存中缓存的身份 = template-actor
|
+--> 恢复出 Actor A
+--> 恢复出 Actor B
如果恢复时不进行身份重绑定,Actor A 和 B 就会继续对外声明模板的凭证信息。
为了解决这个问题,Substrate 的思路是在系统执行 Run 或 Restore 时,重新生成并在运行时向 Actor 注入全新的系统信息文件。例如:
text
/run/ate/actor-id
/run/ate/actor-uid
/run/ate/atespace
(注:atespace 是 Substrate 用于隔离和划分资源的分组空间概念)
在 Substrate 的 身份恢复测试 (Identity Test) 中,不仅校验了恢复出的两个 Actor 是否拥有不同的 ID 和 UID,还覆盖了一个容易被忽略的细节:对于进程在快照前就已经打开、且恢复后继续持有的文件描述符(FD),也必须能读取到更新后的正确身份。
这表明,身份重绑定不仅仅是"在磁盘上写一个新文件",更包含了快照恢复瞬间对文件路径与底层句柄的重新映射。
工程启示: 在对接此类应用 Runtime 时,业务应用切勿在制作模板快照时永久缓存身份。业务记忆(状态)可以随快照恢复,但运行时身份必须重新拉取。 否则,就算底层 Runtime 已经正确完成了重绑定,应用层依然会拿着旧凭证发送请求。
同时还要明确:actor-id 仅用于进程在内部感知自身标识,它本身并不是一个可以凭空提交给远端服务的认证证明。远端服务校验身份时,依然需要验签正式的证书或 Token,而不是盲信请求头里自报的 ID。
3. Actor 证书必须绑定真实的运行分配
在本文对应的代码版本中,Substrate 提供了 ActorIdentity 服务,其中的 MintCert 接口专门用于签发短期的 Actor X.509 证书。
该证书的 Subject Alternative Name (SAN) 中包含如下格式的 URI:
text
spiffe://substrate-actor.local/atespace/<atespace>/actor/<actor-name>
同时,证书还嵌入了一个自定义的 X.509 ActorIdentity 扩展字段:
text
Atespace
ActorName
ActorUID
Purpose
(当前版本中,签发用途 Purpose 被硬编码限定为 atunnel,即出口隧道组件)
比起 URI 格式本身,安全设计上更关键的问题是:控制面如何确保只有合法的申请者才能拿到这张证书?
3.1 从工作负载证明,到真正的 Actor 身份
结合 Credential Broker 和 ActorIdentity 实现 的代码逻辑,整个证书申请签发链路如下:
text
atunnel(出口隧道组件)
| 1. 为本次 Activation 生成私钥与 CSR
v
节点本地 atelet Credential Broker
| 2. 携带 Worker Pod 自身凭证进行身份认证
v
ate-api / ActorIdentity.MintCert
| 3. 严格校验:调用节点、Worker 实际分配关系、Actor UID 及当前生命周期状态
v
签发并返回 Actor 证书
从链路可以看出,控制面绝非仅仅凭请求里附带的"Actor 名称"就盲目签发证书。相反,它会向调度模块查询该 Worker 节点的物理分配记录,核对当前 Worker 上运行的是否确实是该 Actor。
更进一步,代码还会显式比对 expected_actor_uid。这样一来,即使某个旧 Activation(激活周期)发起的迟到请求在 Worker 被重新分配后才到达,也会因 UID 不匹配而被直接拦截,无法错领新 Actor 的证书。
这就体现了一个通用安全原则:
请求方可以声明"我期望获取什么身份",但最终的身份赋予,必须由可信控制面基于物理运行状态独立裁决。
需要提醒的是,此处的"证明"依赖于 K8s Pod、节点身份以及控制面的调度状态,并不意味着系统具备硬件级(如 TEE/SGX)的远程证明能力。
3.2 实例名称并不等于"同一个主体"
在分布式调度中,经常会出现以下操作序列:
text
创建 Actor: payment-agent (分配 UID = old-uid)
删除 Actor: payment-agent
重新创建: payment-agent (分配 UID = new-uid)
虽然两次创建的业务名称都是 payment-agent,但在逻辑上它们是两个截然不同的生命周期实例。
如果控制面只校验名称,旧实例遗留的证书申请请求就可能错误地拿到新实例的凭证。Substrate 在出口认证时,会强行将证书中的 UID 与控制面当前的实时记录进行比对,从而彻底厘清"同名"与"同一个主体"的界限。
因此,在设计 Agent 身份标识时,必须将"稳定的逻辑名称"与"不可复用的实例 UID"结合使用,切忌将可复用的显示名称直接作为唯一的身份信任根。
3.3 私钥跟着 Activation 走,而不是快照
根据 atunnel 凭证实现,系统会在 Actor 每次被激活(Activation)时,现场生成全新的 ECDSA P-256 私钥。该私钥保留在 atunnel 内存中,只有导出的 CSR 会发送给 Broker;在同一 Activation 生命周期内的证书续签,则继续复用该密钥。
由此,我们可以清晰地梳理出三种不同维度的生命周期:
| 对象 | 生命周期说明 |
|---|---|
| Actor UID | 标识本次创建的逻辑 Actor 主体,在挂起/恢复(Suspend/Resume)过程中保持不变 |
| atunnel 私钥 | 为单次激活(Activation)实时生成,绝不从快照中继承 |
| Actor 证书 | 短时有效,仅允许在单次 Activation 内为同一个私钥进行续签 |
此外,虽然这里采用了 SPIFFE 格式的 URI 和本地 CA,但并不等同于系统已经集成了完整的 SPIRE 架构或实现了标准 SPIFFE Workload API,更不代表已经具备跨组织的身份互信能力。
4. 出口网关如何认出这个 Actor?
Substrate 的 Egress Demo 给出了一套非常清晰的流量出站认证链路:
text
Actor 发起出站请求
|
v
nftables(透明拦截流量,强制重定向至本地 atunnel)
|
v
atunnel(自动挂载 Actor 证书,建立 mTLS + HTTP CONNECT 隧道)
|
v
Egress Gateway(出口网关)
| 1. 校验 mTLS 证书中的 Actor 身份
| 2. 实时核对控制面中该 UID 是否处于 RUNNING 状态
v
目标外部服务
在网关层的选择上,系统既可以支持 Envoy,也可以适配 agentgateway。
这套设计最突出的优点在于:Actor 的出站身份完全由经过密码学签署的证书提供,而非依赖应用层自主注入的 X-Agent-ID 等易被伪造的 HTTP Header。 同时,网关还会实时校验证书中的 UID,确保该 Actor 在控制面中确实处于 RUNNING 状态。
将"签发"与"使用"两端串联起来看,本质上是完成了两次互补的校验:
- 签发端确认 :当前的 Worker 节点有资格申请该 Actor 的证书;
- 出口端确认 :正在使用该证书的 Actor,在控制面中依然是合法且活跃的。
当前示例的边界与注意事项:
- 业务授权尚未补齐:上述链路仅完成了"身份认证(Authentication)"。Egress Demo 在文档中明确说明,基于目的地的访问控制授权(Authorization)以及下游 API Key/Token 的安全注入,仍属于后续建设内容。
- 传输安全别混淆 :隧道层使用了 mTLS,并不代表网关到目标服务也是端到端加密的。 Demo 中为了展示透明代理使用了明文 HTTP 示例,在生产环境中绝不能直接参照其传输配置。
- 连接撤销机制:控制面的状态校验目前发生在建立新连接或新隧道时,并不等同于能够"瞬间切断已经建立的长连接"。
5. MintJWT 和 MintCert:两条完全不同的校验链
在查看源码时,容易因为名称相似而误以为 MintCert 与 MintJWT 只是同一种校验逻辑的不同输出格式。
然而,在本文分析的代码版本中,这两个接口的底层校验深度存在显著差异:
| 接口 | 当前源码实现行为 | 安全边界与局限 |
|---|---|---|
ActorIdentity.MintCert |
严格校验调用者身份、Worker/Actor 分配关系及 UID;拒绝为处于删除状态的 Actor 签发;硬编码限定证书用途 | 适用于运行时 Actor 基础设施证书,不可直接作为用户委托 Token 使用 |
ActorIdentity.MintJWT |
校验调用者 JWT 是否来自指定的可信 Issuer;校验基础请求参数;签发带有 Audience 的 Actor JWT | 缺失交叉校验:目前对"调用者身份"与"被请求 Actor"之间的数据库归属关系,代码中仍标注为 TODO |
在生存时间(TTL)方面,当前源码中的 JWT 在签发后约 15 分钟过期,而 Actor 证书的有效周期约为 1 小时。两者均设置了容忍时钟偏差的缓冲时间。
这里的 JWT 包含了 Actor 的 Atespace、Name 以及 UID 等声明(Claims)。它本质上是 Actor 自身的凭证签发接口,切不可仅凭方法名将其误读为 OAuth OBO (On-Behalf-Of) 模式或用户身份委托的实现。
类似地,也不能将早期的 SessionIdentity、App/User/Session 模型与当前的 ActorIdentity 混为一谈。
这个细节提示我们在评估安全架构时:"存在接口"、"校验完整"、"链路打通"与"满足生产级安全",是四个完全不同的层级。
6. 当前开源实现的边界在哪里?
Substrate 已经实现了非常优秀的访问约束(如 Worker/Actor 的绑定校验、出口 UID 与运行状态的核验)。但局部链路的校验闭环,并不等于已经具备了完整的控制面 RBAC 与业务授权体系。
官方 控制面认证文档 中明确指出:该版本尚未实现通用的 Authorization/RBAC。因此,如果配置了可信的 JWT Provider,必须将所有被接受的调用方均视为控制面高信任主体。
为了更直观地评估,我们可以梳理出当前版本的能力边界:
| 维度 | 本文分析版本 (Commit: fda35d0) 的判定 |
|---|---|
| Actor 标识与快照重绑定 | 已实现 |
| Actor X.509 证书与出口 mTLS | 已实现(属于早期架构验证阶段) |
| JWT 签发与 Audience 声明 | 已实现(但 Actor 归属关系的绑定校验未完整) |
| 通用控制面 RBAC | 未实现(文档明确标注) |
| 出口目的地授权与下游 Token 注入 | 未实现(官方 Demo 标记为后续计划) |
| 用户 OAuth 委托与权限收缩 | 未实现(相关能力仅在 Roadmap 中规划) |
| MCP / A2A 协议原生支持 | 未原生支持(仅能作为底层通用 Runtime 承载相关工作负载) |
| 企业级身份治理与完整审计 | 未实现(无法仅凭 Runtime 日志直接推导得出) |
注:项目文档中的 Roadmap 往往会滞后于实际代码提交。在评估真实能力时,应始终以源码和当前版本的实际行为为准。
7. 深入测试时,重点观察生命周期切换
如果你计划基于该开源项目进行上手验证,建议直接从固定 Commit 的 Egress Demo 和 身份恢复测试 (Identity Test) 切入,重点观察在各类生命周期切换时系统的实际表现:
| 测试场景 | 关键观察点与预期行为 |
|---|---|
| 从同一 Golden Snapshot 恢复两个 Actor | 检查各自的 ID 和 UID 是否相互独立且正确;快照前已打开的文件句柄在恢复后能否读到新身份 |
| 同一 Actor 挂起 (Suspend) 后恢复 (Resume) | 检查 Actor UID 是否保持不变;新的 Activation 是否生成了全新的私钥与凭证链路 |
| 证书签发过程中 Worker 分配发生变更 | 验证 expected_actor_uid 机制能否成功拦截迟到请求,防止旧 Worker 误领新 Actor 的证书 |
| 删除 Actor 后重新创建同名 Actor | 观察出口网关是否能基于 UID 的变更拒绝旧证书的连接请求,而不仅仅是比较 Actor 名称 |
| 更新信任包 (Trust Bundle) 后恢复 Actor | 验证恢复后的 Actor 能否正确读取最新的信任包(注意:目前仅支持 Restore 时刷新,不支持运行中实时热推送) |
写在最后
Substrate 用极具参考价值的源码实现,把一个经常被抽象架构图忽略的工程难题具体化了:当底层的计算资源处于高速的动态调度与复用之中时,逻辑身份如何既保持连续性,又绝不被错误继承?
它的回答并不是给进程发一个"一劳永逸"的永久 Token,而是将恢复时的身份重绑定、不可变 Actor UID、Worker 物理分配校验以及 Activation 级别的动态私钥有机结合,并在流量出口处再次实施状态二次核对。
这套针对动态运行时的身份设计链路,才是我们在设计 AI Agent 基础设施时最值得借鉴的核心模式。至于上层的业务权限划分与企业级身份治理,则是需要在此基础上再行衔接的下一层课题了。
参考资料
为便于对照,以下链接均已锁定至本文分析的特定 Commit: