Agent 换了 Pod,身份怎么办?拆解 Substrate 的 Actor Identity

一个 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 日。源码分析固定基于 Commit fda35d0c04e4d55c5ddb9e1ff50ed4e1ceafbf5f,避免混用不同版本的接口。本文侧重于架构与源码深度拆解,并非生产部署实测报告。


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 的思路是在系统执行 RunRestore 时,重新生成并在运行时向 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 BrokerActorIdentity 实现 的代码逻辑,整个证书申请签发链路如下:

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 状态。

将"签发"与"使用"两端串联起来看,本质上是完成了两次互补的校验:

  1. 签发端确认 :当前的 Worker 节点有资格申请该 Actor 的证书;
  2. 出口端确认 :正在使用该证书的 Actor,在控制面中依然是合法且活跃的

当前示例的边界与注意事项:

  • 业务授权尚未补齐:上述链路仅完成了"身份认证(Authentication)"。Egress Demo 在文档中明确说明,基于目的地的访问控制授权(Authorization)以及下游 API Key/Token 的安全注入,仍属于后续建设内容。
  • 传输安全别混淆隧道层使用了 mTLS,并不代表网关到目标服务也是端到端加密的。 Demo 中为了展示透明代理使用了明文 HTTP 示例,在生产环境中绝不能直接参照其传输配置。
  • 连接撤销机制:控制面的状态校验目前发生在建立新连接或新隧道时,并不等同于能够"瞬间切断已经建立的长连接"。

5. MintJWT 和 MintCert:两条完全不同的校验链

在查看源码时,容易因为名称相似而误以为 MintCertMintJWT 只是同一种校验逻辑的不同输出格式。

然而,在本文分析的代码版本中,这两个接口的底层校验深度存在显著差异:

接口 当前源码实现行为 安全边界与局限
ActorIdentity.MintCert 严格校验调用者身份、Worker/Actor 分配关系及 UID;拒绝为处于删除状态的 Actor 签发;硬编码限定证书用途 适用于运行时 Actor 基础设施证书,不可直接作为用户委托 Token 使用
ActorIdentity.MintJWT 校验调用者 JWT 是否来自指定的可信 Issuer;校验基础请求参数;签发带有 Audience 的 Actor JWT 缺失交叉校验:目前对"调用者身份"与"被请求 Actor"之间的数据库归属关系,代码中仍标注为 TODO

在生存时间(TTL)方面,当前源码中的 JWT 在签发后约 15 分钟过期,而 Actor 证书的有效周期约为 1 小时。两者均设置了容忍时钟偏差的缓冲时间。

这里的 JWT 包含了 Actor 的 AtespaceName 以及 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:

相关推荐
人工智能AI技术1 小时前
从ChatGPT到GPT‑6 Astra:当AI开始解数学难题、自主操作软件
人工智能
财迅通Ai1 小时前
物理AI从产业叙事走向基本面重估 以SENASIC琻捷看端侧感算入口的产业价值
人工智能·senasic琻捷
Geek-Chow1 小时前
MCP 模型上下文协议:九、深入服务器 · 工具、资源与提示
人工智能
Theo_xx1 小时前
声学感知基础:Day1.常见音频类型(Chirp、FMCW、OFDM)的区别、联系和选择
人工智能·无线感知·声学感知
yanwumuxi1 小时前
Milvus使用和改进
云原生·eureka·milvus
DisonTangor1 小时前
Qwen-Drive-1.0:首个统一 3D 感知、视觉问答与运动规划的自动驾驶视觉语言基础模型
人工智能·3d·自动驾驶
XiaoKe20261 小时前
数海云擎:以内外双向数智化锻造竞争壁垒,跑通销售全链路
大数据·人工智能·科技·软件需求
m0_587383001 小时前
全民健身解决方案软件开发:从架构设计到落地实践
人工智能·数据挖掘·系统架构·需求分析
Zzj_tju1 小时前
小模型指令微调:数据混合、模板与过拟合的最小复现
人工智能·深度学习·机器学习·语言模型