# AI 代理操作多个云账户：绑定每次操作

只有在每个请求都明确指定一个不可变的账户边界，并且执行器拒绝所有不匹配时，AI 代理才能安全地操作多个云账户。代理可以说出「部署到生产环境」，却无法指出这句话对应的 AWS 账户、Azure 订阅或 Google Cloud 项目，那么它得到的权限就是含糊的。

团队经常把这件事当作 IAM 清理工作。其实，这是执行设计问题。IAM 可以正确地授予两个账户访问权限，但代理、包装器或 shell 配置文件仍然可能选择错误的账户。这样一来，完全有效的凭据就会在错误的地方完成完全有效的变更。

解决办法并不如复杂的权限模型那么炫。提前定义可执行目标，把每个目标绑定到云服务商签发的标识符和专用执行身份，让代理通过句柄请求目标，并在改变状态的调用前立即验证调用方。人类可读的环境名称仍然有用，但不能由它们决定代码运行在哪里。

## 含糊的目标会带来真实权限

环境标签不是权限边界。「生产环境」只告诉人们团队打算如何使用资源，却不会告诉 API 哪个 AWS 账户、Azure 订阅、Google Cloud 项目、租户、区域或承担的角色必须接收请求。

当一家公司在不同组织中同时存在 `prod`、`production`、`prod-old` 和 `production-sandbox` 时，这种区别就不再是吹毛求疵。我见过迁移过程中复制账户别名，也见过过时的 shell 配置文件仍然指向已弃用的账户，还见过看似无害的读取操作落到了事故响应人员正努力保护的账户中。代理会把自然语言标签当真，而且行动速度远快于发现歧义的人，这会让问题更加严重。

执行前，一个目标必须回答以下所有问题：

- 哪个服务商拥有该资源？
- 哪个不可变的账户边界会接收调用？
- 组织为该边界指定了什么环境？
- 哪个执行身份可以在这里操作？
- 哪些地域或组织范围限制适用？

顺序很重要。账户边界要先于环境标签。如果请求写着 `environment: production`，却没有账户 ID 或订阅 ID，那么请求就是不完整的。应当拒绝它，不要让代理从代码库名称、工单标题或当前终端配置中推断缺失的信息。

有人会建议，账户命名规范可以解决这个问题。命名规范确实方便人们浏览列表，但不能充当控制措施。名称是可变字符串。账户 ID、订阅 ID、租户 ID、项目编号、集群 UID 和角色 ARN 都是服务商签发的引用，更能证明身份。

## 环境标签不能决定账户

应将环境视为附加在目标记录上的受控元数据，而不是执行时用来解析目标的输入。这样可以避免请求「应用这个生产环境修复」时搜索实时资产清单，再选中恰好匹配某个标签的账户。

这一区别会带来实际影响。部署目录中可能有许多目标都标记为 `environment: production`，例如支付服务、内部工具、区域服务和收购时期遗留的账户。它们仍然是不同的目的地。代理必须选择一个获批准的句柄，例如 `aws-prod-payments`，而不是提交 `environment=production` 这样的宽泛选择器。

使用能确定实际边界的服务商标识符：

| 服务商 | 绑定到 | 不要依赖 |
| --- | --- | --- |
| AWS | 账户 ID、分区、角色 ARN、允许的区域 | 账户别名、配置文件名称、角色显示名称 |
| Azure | 租户 ID、订阅 ID，以及需要时的资源组范围 | 订阅显示名称、门户中的目录选择 |
| Google Cloud | 项目编号和项目 ID，以及相关时的组织或文件夹 | 项目显示名称、本地当前配置 |

Google Cloud 的项目 ID 通常足够稳定，适合放进请求中，而项目编号还能提供额外的不可变检查。Azure 需要同时使用租户和订阅，因为单独的订阅无法说明签发令牌的身份环境。在 AWS 中，账户编号本身也不能说明操作会使用哪个角色。两者都要绑定。

不要把这些信息藏在提示文本中。代理指令里写着「永远不要碰生产环境」，无法阻止本来就有权限执行该操作的凭据。可信执行器必须持有目标记录，并判断请求的身份和目的地是否与记录匹配。

## 目标注册表应包含事实，而不是猜测

维护一份小型、经过审核的可执行云目标注册表。这不是第二套 IAM 系统，也不应试图重复所有云权限。云 IAM 仍然决定某个角色是否可以调用 API。注册表回答的是一个更具体的问题：使用这个目标句柄时，请求可以被发送到哪里？

下面的示例使用虚构标识符，但结构是有意设计的：

```yaml
targets:
  aws-prod-payments:
    provider: aws
    environment: production
    account_id: "482901736154"
    partition: aws
    role_arn: "arn:aws:iam::482901736154:role/agent-payments-deploy"
    regions:
      - us-east-1
      - us-west-2

  azure-prod-fulfillment:
    provider: azure
    environment: production
    tenant_id: "2f34c630-1ce8-4ed3-a7ce-8a3286e799a1"
    subscription_id: "841742bf-9db5-4cf8-9f6b-f1778d5b7511"
    scopes:
      - "/subscriptions/841742bf-9db5-4cf8-9f6b-f1778d5b7511/resourceGroups/fulfillment-prod"

  gcp-prod-catalog:
    provider: gcp
    environment: production
    project_id: "catalog-prod-417"
    project_number: "548201736915"
    parent: "organizations/193847561029"
```

注册表应像生产配置一样管理。为每个目标指定负责团队、审核路径，以及账户停用后的退休日期或状态。否则，旧目标会悄悄变成代理仍可访问的地点清单。

不要从自由格式的发现结果创建记录，然后立即允许执行。云资产清单 API 可以帮助找出需要负责人的账户，但它们不是授权决定。发现的账户可能是取证副本、供应商管理的订阅、收购遗留账户，或与生产环境使用相似标签的测试租户。

注册表还可以防止一种隐蔽的漂移问题。有人可以重命名 Azure 订阅或 AWS 角色，却不改变不可变 ID。显示标签可以为了可读性而更新，绑定仍然保持不变。如果不可变 ID 发生变化，就应将其视为需要重新审核的新目标，即使名称没有改变。

## 代理应请求句柄，不能自行拼接目的地

代理应发送意图和目标句柄。它不应发送从命令中复制的角色 ARN、任意云配置文件名称，或包含用户自行选择的订阅的 shell 命令。让代理控制这些字段，就等于让它控制请求中最敏感的部分。

请求可以简单到这样：

```json
{
  "request_id": "chg-8a31f6",
  "target": "aws-prod-payments",
  "operation": "aws.ec2.reboot_instances",
  "region": "us-east-1",
  "arguments": {
    "InstanceIds": ["i-0abc123def4567890"]
  },
  "reason": "Recover the failed checkout worker after approved release"
}
```

执行器从自己已经持有的记录中解析 `aws-prod-payments`。它只承担记录中列出的角色，拒绝 `eu-west-1`，因为该区域不在记录中，然后使用选定的账户身份发送请求。在这次交换中，代理不会获得可重复使用的云凭据。

这种方式可以阻止一种很容易识别的故障。代理使用 `--profile prod` 运行命令，但开发者本地的 `prod` 配置文件却指向共享服务账户。命令语法正确，API 也接受了它，团队直到发现预期中的支付工作进程仍在运行时，才意识到出了问题。由代理之外的系统解析目标句柄，再加上身份检查，可以在重启请求离开执行器前拦截命令。

不要为了「灵活性」同时接受句柄和调用方提供的目的地。这样会产生两个事实来源。如果请求包含 `target: aws-prod-payments`，却带有不同的角色 ARN，执行器必须拒绝请求。它绝不能选择看起来更具体的字段。

## 在写入前验证当前调用方

固定的目标记录很重要，但它不能证明当前凭据就是预期凭据。令牌会过期，角色承担可能失败，​​本地配置文件可能泄漏到子进程中，云 SDK 也可能使用出乎意料的凭据提供链。应在改变状态的调用前立即检查当前身份。

AWS 文档说明，`GetCallerIdentity` 会返回调用身份的账户、ARN 和用户 ID。文档还指出，即使显式拒绝本来会阻止该调用，它仍然会返回这些信息。因此，它很适合作为诊断和预检工具。但它不会授予其他操作权限，也不能替代与预期记录的比较。

预检命令的输出形式如下：

```bash
aws sts get-caller-identity --output json
```

```json
{
  "UserId": "AROAEXAMPLEID:agent-run-8a31f6",
  "Account": "482901736154",
  "Arn": "arn:aws:sts::482901736154:assumed-role/agent-payments-deploy/agent-run-8a31f6"
}
```

将 `Account` 与目标的 `account_id` 比较。解析 ARN，并将承担的角色与配置中的角色比较。不要使用「ARN 包含 payments」这样的子字符串检查。在写入调用前拒绝不同的分区、账户或角色。

在其他服务商中也采用同样的模式。对 Azure，查询当前租户和订阅，再将两者与选定目标比较。对 Google Cloud，查询当前项目和已认证主体，然后在修改 API 调用前根据目标记录验证项目。这些检查必须在发送操作的同一个进程上下文中运行。检查一个终端，却在另一个终端执行，只能带来安慰感。

预检检查也有局限：角色可能正确，但权限过大。云角色本身仍需针对分配的目标遵循最小权限原则。绑定可以阻止请求进入错误账户，IAM 则限制正确账户身份进入后能做什么。两种控制都需要。

## 跨账户宽权限角色会把错误藏到造成损害之后

一个可以承担所有账户角色的管理员角色很受欢迎，因为它能减少配置工作。但它也会让目标选择变成不可逆的高风险决定。如果代理或执行器选错账户，同一个宽权限身份通常仍然足以完成错误操作。

按账户和用途分开执行角色。支付服务的生产部署角色不应同时拥有通往分析、身份或灾难恢复账户的信任路径。如果代理需要跨多个账户读取数据，就使用独立的读取角色，并让每次调用的目标绑定清晰可见。读取权限也可能暴露客户数据、基础设施拓扑，以及保存在不当位置的凭据。称作只读，并不会让选择错误变得无害。

使用云服务商原生的信任条件，限制角色可以如何被承担。AWS 角色信任策略可以限制受信主体，并在适合调用方的情况下要求外部 ID。Azure 工作负载身份可以通过联合凭据声明和资源角色分配进行限制。Google Cloud 服务账户模拟可以限制谁能够创建访问令牌。具体机制各不相同，但设计原则一致：一个执行身份应对应一个经过审核的目的地和用途。

不要把中央代理身份误认为宽泛权限。代理可以为许多目标协调请求，同时让每个请求获得目标专属身份。这比通用管理员角色需要更多准备，但当进程、提示或集成出错时，影响范围会清楚得多。

## 审批内容必须展示人能够核验的目的地

如果审批隐藏了账户边界，人就无法判断操作是否安全。「重启生产环境中的结账工作进程」要求审核者相信看不见的解析逻辑。审批内容应包括目标句柄、环境标签、不可变账户标识符、当前角色或服务身份、操作，以及 API 将影响的资源范围。

对于 AWS 实例重启，一份有用的审批内容如下：

```text
Target: aws-prod-payments
Environment: production
Account: 482901736154
Identity: agent-payments-deploy
Action: ec2:RebootInstances
Region: us-east-1
Resource: i-0abc123def4567890
Reason: Recover failed checkout worker after approved release
```

这些细节不是审批作秀。审核者通常熟悉自己服务的账户编号或目标句柄，而代理的文字描述可能听起来合理却是错误的。应把目的地放在操作描述之前，并让它足够醒目，方便快速查看。人们更容易注意到账户编号异常，而不是发现埋在命令负载中的细微不匹配。

当一个会话涉及多个生产账户时，不要要求对「本会话中的所有工作」进行一次性审批。会话可以有合理边界，例如反复修改一个部署目标，但边界不能悄悄扩大。目标句柄变化、操作从读取变为写入，或请求触及比审核者批准范围更敏感的资源时，都应要求新的审批。

Sallyport 的 per-session authorization 会识别连接进程，其 per-call credential 设置可以要求每次使用都经过审批。只有当操作请求携带审核者能够识别的目标，并且执行器已将其绑定到一个目的地时，这项能力才真正有用。

## 日志必须连接意图、身份和服务商证据

云审计日志会告诉你哪个身份调用了 API，但很少解释代理的指令、目标选择或审核决定。代理侧记录可以解释意图，却不能证明云端收到了预期调用。两类记录都要保留，并通过请求 ID 和不可变目标标识符将它们关联起来。

每次操作前后都记录以下字段：

- 发出请求的代理进程或会话
- 选定的目标句柄及其解析出的不可变标识符
- 预检期间观察到的执行身份
- 操作、资源范围、结果和请求 ID
- 服务商提供时对应的审计事件引用

AWS CloudTrail、Azure Activity Log 和 Google Cloud Audit Logs 都会记录服务商侧活动，具体取决于所使用的服务和日志配置。将这些记录保存在独立的安全边界中。不要把代理活动日志当作服务商证据的替代品，因为遭到入侵的本地进程可能会谎报一个从未完成的请求。

Sallyport 会将代理运行和单独调用记录在不同日志中，这些日志来自加密的哈希链审计日志。其 `sp audit verify` 命令可以对密文离线验证哈希链，这在事故发生后检查本地操作记录是否被篡改时很有帮助。

实际测试方法是进行一次调查演练。选择一项已批准的变更，请工程师在不猜测的情况下回答四个问题：哪个代理进程发起了请求，选择了哪个目标，哪个云身份执行了操作，以及哪个服务商侧事件证明了它。如果任何答案都需要在三个控制台之间手动对照时间戳，说明记录还不够可靠。

## 错误账户事故通常在 API 调用前就开始了

表面上的故障往往是错误账户中的生产变更。更早发生的故障通常是没有受到控制的解析路径。应将这类事故视为绑定缺陷，而不只是某个人「用了错误配置文件」。

考虑一个合理的过程。代码库中有一个接受 `ENV=prod` 的脚本。它的包装器将这个值转换为名为 `prod` 的 AWS 配置文件。迁移期间，一名开发者修改了该配置文件，让它指向共享服务账户，因为旧的支付账户已经不再需要直接访问。后来，代理收到重启支付工作进程的请求。它找到脚本，设置 `ENV=prod` 并运行。脚本通过配置文件解析指向共享服务账户。由于团队为了操作方便给角色授予了广泛的 EC2 权限，重启操作在错误账户中成功了。

云 API 没有产生混淆。脚本使用有效身份发出了有效请求。事故后如果只告诉代理「仔细检查账户」，问题还会再次发生，因为原来的解析路径仍然存在。

应按以下顺序修复这类故障：

1. 停用受影响的会话或角色路径，并保留代理和云审计记录。
2. 找出选择错误目的地的确切选择器，例如配置文件名称、默认订阅、环境标签或调用方提供的角色 ARN。
3. 用由执行器解析的获批准目标句柄替代该选择器。
4. 添加实时身份比较，在写入前拒绝任何不匹配。
5. 如果宽权限角色允许跨账户执行无关操作，就拆分该角色。

然后有意测试拒绝路径。让一个预发布目标指向另一个测试账户的凭据，确认执行器会在修改 API 调用前停止。团队经常测试成功路径，却从未证明账户绑定会以安全失败的方式停止。

## 从风险最高的写入操作开始，最适合逐步推广

不要等到完成全部云资产清单后，才为危险操作设置明确绑定。先从可能改变或暴露生产状态的操作开始：部署、身份变更、网络编辑、数据导出、密钥轮换和破坏性基础设施命令。只读发现可以帮助建立注册表，但不应悄悄授予新发现账户的执行权限。

首先列出可以接收这些操作的账户，为每个账户指定清晰的环境标签和负责人。接着为每个账户和用途组合创建目标记录。然后让一条执行器路径解析句柄、获取目标专属凭据、验证当前身份并写入操作记录。最后移除代理对环境配置文件、默认订阅和凭据文件的直接访问。

习惯在终端中用一个变量切换账户的工程师可能会反对。这个习惯之所以快，是因为它把上下文放进了人的记忆中。自主代理没有这种记忆，审核代理操作的人也没有。应将上下文放进请求、目标注册表和执行证据中。

如果在操作运行前，你无法说清账户编号、执行身份和资源范围，那么代理还没有足够信息安全地行动。让它补齐缺失事实，或者把操作交给人来完成。
