Chapter 24: 沙箱与安全 -- 在云上约束 Agent

Ch 9-11 讲了单进程内的权限三层防线。本章换到云的视角:当 Agent 运行在 Kubernetes 集群中、调用真实的 AWS API、向公网发起请求时,进程内的函数调用级约束远远不够。你需要基础设施级的隔离——双 Pod 沙箱模型就是 OpenHarness 对 CONSTRAIN 支柱的核心实现。

关键概念:Agent Pod / Sandbox Pod 分离、NetworkPolicy 在 Pod 级别生效、IAM/IRSA 最小权限、Kyverno 策略引擎、AGENTS.md 治理文档、命名空间多租户隔离。

                  ┌─────────────────────────────────┐
                  │        Namespace: project-x      │
                  │                                  │
┌────────────┐    │  ┌────────────┐  ┌────────────┐  │
│            │    │  │ Agent Pod  │  │ Sandbox Pod│  │
│  Bedrock   │◄───┤  │            │  │            │  │
│  API       │    │  │ LLM 调用   │  │ 命令执行   │  │
│            │    │  │ 任务编排   │  │ git clone  │  │
└────────────┘    │  │            │  │ npm install │  │
                  │  └─────┬──────┘  └─────┬──────┘  │
      ✗ 禁止      │        │    gRPC      │         │
  ┌────────────┐  │        └──────┬───────┘         │
  │ 公网 / 其他 │  │               │                  │
  │ AWS 服务   │◄─┼───✗───────────┘ (Sandbox 不可访问)│
  └────────────┘  │        │                         │
                  │        ▼                         │
                  │  ┌──────────┐                    │
                  │  │ /workspace│ ◄── 共享 PVC       │
                  │  │  PVC     │                    │
                  │  └──────────┘                    │
                  └─────────────────────────────────┘

24.1 云上 Agent 的威胁模型

问题

Ch 9 分析了本地 Agent 的风险:用户让 Agent 执行 rm -rf /,或者 Agent 被 prompt injection 诱导执行恶意命令。但这些风险都发生在用户自己的机器上——最坏的情况是用户自己的数据丢失。

把 Agent 搬到云上,风险维度急剧扩大:

  1. 横向移动:Agent 的 Pod 被攻破后,攻击者可能利用 Kubernetes 的 Service Account 访问集群内的其他服务——数据库、密钥管理、其他用户的 Pod。
  2. 云 API 滥用:如果 Agent 拥有过宽的 IAM 权限,一次 prompt injection 可能导致攻击者通过 Agent 创建 EC2 实例(挖矿)、读取 S3 中的敏感数据、甚至修改 IAM 策略(权限提升)。
  3. 资源耗尽:一个失控的 Agent 可能无限创建进程、消耗 CPU/内存、写满磁盘,影响同一节点上的其他 Pod。
  4. 数据泄露:Agent 处理的代码可能包含密钥、Token、内部 API 地址。如果 Agent 能向公网发起任意 HTTP 请求,这些信息可能被外泄。
  5. 多租户隔离失败:在多用户共享的集群中,一个用户的 Agent 不应该能访问另一个用户的代码和数据。

Ch 22 的第一条原则「安全优先」说:安全不依赖模型的判断力。在云上,这条原则的推论是:安全不依赖容器的边界,不依赖 Agent 的自律,只依赖基础设施的强制策略

思路

OpenHarness 的安全架构遵循纵深防御原则——不依赖单一安全层,而是在多个层面设置独立的防线:

Layer 5  AGENTS.md 治理文档     ← 软约束,声明式
Layer 4  Kyverno 策略引擎       ← K8s 准入控制
Layer 3  NetworkPolicy          ← Pod 级网络隔离
Layer 2  IAM/IRSA 最小权限      ← AWS API 访问控制
Layer 1  双 Pod 沙箱模型        ← 执行环境物理分离
Layer 0  命名空间隔离            ← 多租户边界

从下往上,每一层的防护对象不同:Layer 0 隔离不同用户,Layer 1 隔离同一用户的不同执行关注点,Layer 2 限制云 API 访问,Layer 3 限制网络通信,Layer 4 限制 K8s 资源操作,Layer 5 声明 Agent 的行为边界。

即使上层被突破(比如 AGENTS.md 被篡改),下层仍然有效(NetworkPolicy 不受 Agent 控制)。这是 Ch 9 三层防线的云上扩展:不是更多层,而是每层更硬——从函数调用级的检查变成了操作系统和网络级的强制执行。


24.2 双 Pod 沙箱模型

问题

为什么不把 Agent 的所有功能放在一个 Pod 里?一个 Pod 里同时运行 LLM 调用和命令执行,架构更简单,通信延迟更低。

答案藏在 Kubernetes 的一个基础设施约束中:NetworkPolicy 在 Pod 级别生效,不在容器级别生效

一个 Pod 可以包含多个容器(sidecar 模式),但所有容器共享同一个网络命名空间——它们拥有相同的 IP 地址,共享同一组 NetworkPolicy 规则。这意味着:如果你想让 LLM 调用容器访问 Bedrock API,同一 Pod 内的命令执行容器也能访问 Bedrock API。你无法在同一个 Pod 内对不同容器施加不同的网络策略。

这就是 OpenHarness 采用双 Pod 模型而非 sidecar 模型的根本原因:不是架构偏好,而是基础设施约束

思路

双 Pod 模型将一个 Agent 会话拆分为两个物理隔离的 Pod:

┌──────────────────────────┐    ┌──────────────────────────┐
│      Agent Pod           │    │     Sandbox Pod          │
│                          │    │                          │
│  ┌────────────────────┐  │    │  ┌────────────────────┐  │
│  │  Agent 执行引擎     │  │    │  │  命令执行器         │  │
│  │                    │  │    │  │                    │  │
│  │  - LLM 调用循环    │  │    │  │  - shell 执行      │  │
│  │  - 任务编排        │  │    │  │  - git 操作        │  │
│  │  - 上下文管理      │  │    │  │  - 包安装          │  │
│  │  - 工具调度        │  │    │  │  - 测试运行        │  │
│  └─────────┬──────────┘  │    │  └─────────┬──────────┘  │
│            │             │    │            │             │
│  ┌─────────▼──────────┐  │    │  ┌─────────▼──────────┐  │
│  │  gRPC Client       │──┼────┼──│  gRPC Server       │  │
│  └────────────────────┘  │    │  └────────────────────┘  │
│                          │    │                          │
│  NetworkPolicy:          │    │  NetworkPolicy:          │
│  ✓ Bedrock API           │    │  ✓ git 仓库 (github.com) │
│  ✓ Sandbox Pod (gRPC)    │    │  ✓ 包仓库 (npmjs, pypi)  │
│  ✓ API Server            │    │  ✓ Agent Pod (gRPC)      │
│  ✗ 公网                  │    │  ✗ Bedrock API           │
│  ✗ 其他 namespace        │    │  ✗ 公网 (其他)           │
│  ✗ Sandbox 之外的 Pod    │    │  ✗ 其他 namespace        │
└──────────────────────────┘    └──────────────────────────┘
           │                               │
           └───────────┬───────────────────┘
                       ▼
              ┌──────────────┐
              │  /workspace  │
              │  共享 PVC     │
              │              │
              │  源代码       │
              │  配置文件     │
              │  构建产物     │
              └──────────────┘

Agent Pod 负责「思考」:运行 LLM 调用循环,管理上下文,调度工具。它的 NetworkPolicy 只允许访问 Bedrock API(调用 LLM)、Sandbox Pod(发送命令)和 API Server(报告状态)。它不能访问公网——因此即使 Agent 被 prompt injection 诱导生成了一个 curl 命令,它也无法从 Agent Pod 发出。

Sandbox Pod 负责「动手」:执行 shell 命令、git 操作、包安装、测试运行。它的 NetworkPolicy 只允许访问 git 仓库(clone/push)和包仓库(npm/pip 安装)。它不能访问 Bedrock API——因此即使攻击者通过命令执行获得了 Sandbox Pod 的 shell 权限,也无法利用 Agent 的 LLM 调用配额。

两个 Pod 通过 gRPC 通信:Agent Pod 向 Sandbox Pod 发送「执行这条命令」的请求,Sandbox Pod 返回执行结果。它们共享一个 PVC(PersistentVolumeClaim)——挂载在 /workspace 路径下,包含源代码、配置和构建产物。

实现

gRPC 通信的接口设计反映了最小权限原则:

service SandboxService {
  // Agent Pod → Sandbox Pod:执行命令
  rpc ExecuteCommand(CommandRequest) returns (CommandResponse)
  
  // Agent Pod → Sandbox Pod:读取文件
  rpc ReadFile(FileRequest) returns (FileResponse)
  
  // Agent Pod → Sandbox Pod:写入文件
  rpc WriteFile(WriteRequest) returns (WriteResponse)
  
  // Agent Pod → Sandbox Pod:列出目录
  rpc ListDirectory(ListRequest) returns (ListResponse)
}

message CommandRequest {
  string command = 1        // 要执行的 shell 命令
  string working_dir = 2    // 工作目录(必须在 /workspace 下)
  int32 timeout_sec = 3     // 超时(秒)
  bool allow_network = 4    // 是否允许网络访问(受 NetworkPolicy 二次约束)
}

message CommandResponse {
  int32 exit_code = 1
  string stdout = 2
  string stderr = 3
  bool timed_out = 4
}

注意 allow_network 字段:即使 Agent Pod 告诉 Sandbox Pod「这条命令允许网络访问」,Sandbox Pod 的 NetworkPolicy 仍然只放行 git 和包仓库的流量。这是双重约束——软约束(gRPC 参数)和硬约束(NetworkPolicy)独立生效。即使 gRPC 协议被绕过(比如攻击者直接在 Sandbox Pod 中执行命令),硬约束仍然有效。

为什么不直接共享文件系统而要用 gRPC?三个原因:第一,gRPC 提供了一个审计点——每个命令执行请求都是一条可记录、可追踪的消息。第二,gRPC 允许超时控制——Agent Pod 可以在命令执行超时后主动取消,而不是依赖 shell 的超时机制。第三,gRPC 是结构化通信——返回的是 exit_code + stdout + stderr 的结构体,而不是原始字节流,这简化了 Agent 的结果解析。

共享 PVC 是两个 Pod 之间唯一的状态共享通道。它的访问模式是 ReadWriteMany——两个 Pod 都可以读写。但在实践中,写入通常由 Sandbox Pod 完成(因为它负责命令执行),Agent Pod 主要做读取(分析代码、生成修改方案)。这种「一个写、一个读」的模式降低了并发写入冲突的风险。


24.3 为什么不用 Sidecar?一个被反复问到的问题

问题

Kubernetes 的 sidecar 模式是一个成熟的模式——把辅助功能放在主容器旁边的 sidecar 容器中,共享网络和存储。Istio 的 Envoy proxy、Fluentd 日志收集器都用这种模式。为什么 OpenHarness 不用 sidecar,非要拆成两个 Pod?

思路

回到根本原因:NetworkPolicy 在 Pod 级别生效。

让我们用一个具体的攻击场景来解释:

假设使用 Sidecar 模式(一个 Pod,两个容器):

┌─────────────────────────────────────┐
│  Pod (共享网络命名空间,一个 IP)      │
│                                     │
│  ┌─────────────┐  ┌──────────────┐  │
│  │ Agent 容器   │  │ Sandbox 容器 │  │
│  │ (LLM 调用)  │  │ (命令执行)   │  │
│  └─────────────┘  └──────────────┘  │
│                                     │
│  NetworkPolicy: ✓ Bedrock ✓ git     │  ◄── 两个容器共享!
└─────────────────────────────────────┘

攻击路径:
1. Agent 被 prompt injection,生成恶意命令
2. 命令在 Sandbox 容器中执行
3. 攻击者获得 Sandbox 容器的 shell
4. 因为共享网络命名空间,攻击者可以直接访问 Bedrock API
5. 攻击者用 Agent 的凭证调用 LLM,消耗配额或提取信息

用双 Pod 模型,同样的攻击在第 4 步被阻断:

双 Pod 模型:

┌──────────────┐    ┌──────────────┐
│  Agent Pod   │    │ Sandbox Pod  │
│  NP: Bedrock │    │  NP: git     │
└──────────────┘    └──────────────┘

攻击路径:
1. Agent 被 prompt injection,生成恶意命令
2. 命令在 Sandbox Pod 中执行
3. 攻击者获得 Sandbox Pod 的 shell
4. Sandbox Pod 的 NetworkPolicy 不允许访问 Bedrock ← 阻断

这不是一个理论上的区别——它是 Kubernetes 网络模型的硬约束。如果 K8s 未来支持容器级 NetworkPolicy(社区确实在讨论这个特性),sidecar 模式将变得可行。但在当前的 K8s 版本中,双 Pod 是实现差异化网络策略的唯一方式。

有人可能会问:sidecar 模式下,可以用 iptables 规则在容器内做网络隔离吗?技术上可以,但这需要 privileged 容器权限(用于修改网络规则),而给 Agent 的 Pod 授予 privileged 权限本身就违反了最小权限原则。用双 Pod 模型,不需要任何特权——NetworkPolicy 由集群网络插件(Calico、Cilium 等)在 Pod 外部强制执行。


24.4 IAM/IRSA:每个 Agent 一把钥匙

问题

Agent Pod 需要调用 AWS Bedrock API(LLM 推理)。在 AWS 上,API 调用需要 IAM 凭证。最简单的做法是创建一个 IAM User,把 Access Key 作为环境变量注入 Pod。但这种做法有三个问题:长期凭证容易泄露,所有 Pod 共享同一个凭证无法审计,权限粒度由 User 而非 Pod 决定。

思路

OpenHarness 使用 IRSA(IAM Roles for Service Accounts)——Kubernetes Service Account 和 IAM Role 的映射。每个 Agent Pod 绑定一个 Kubernetes Service Account,每个 Service Account 映射一个 IAM Role。

┌──────────────────────────────────────────────────┐
│  Agent Pod                                        │
│                                                  │
│  ServiceAccount: agent-sa-project-x              │
│       │                                          │
│       ▼ (OIDC Token 自动挂载)                     │
│  AWS SDK 自动发现 IRSA 凭证                       │
│       │                                          │
│       ▼ (STS AssumeRoleWithWebIdentity)          │
│  IAM Role: agent-role-project-x                  │
│       │                                          │
│       ▼ (临时凭证,1 小时过期)                     │
│  权限:                                           │
│    ✓ bedrock:InvokeModel (指定模型 ARN)           │
│    ✓ bedrock:InvokeModelWithResponseStream        │
│    ✗ bedrock:CreateModel                         │
│    ✗ s3:*  (除了项目专用 bucket)                  │
│    ✗ iam:*                                       │
│    ✗ ec2:*                                       │
│    ✗ 其他所有 AWS 服务                             │
└──────────────────────────────────────────────────┘

IRSA 的关键优势:

临时凭证:不存在永不过期的 Access Key。每次 API 调用使用的是通过 STS AssumeRole 获取的临时凭证,默认 1 小时过期。即使凭证被泄露,攻击窗口有限。

Pod 级粒度:不同项目的 Agent Pod 绑定不同的 IAM Role,可以有不同的权限范围。Project-X 的 Agent 只能访问 Project-X 的 S3 bucket,看不到 Project-Y 的数据。

可审计:CloudTrail 日志中记录的 IAM Role ARN 可以追溯到具体的 Service Account,进而追溯到具体的 Pod 和项目。当安全事件发生时,你知道是哪个 Agent 的哪次会话触发了可疑的 API 调用。

实现

IAM Policy 的设计体现了最小权限的层层收窄:

// Agent Pod 的 IAM Policy(伪代码)
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream"
      ],
      "Resource": "arn:aws:bedrock:*:*:inference-profile/us.anthropic.claude-*",
      "Condition": {
        "StringEquals": {
          "aws:PrincipalTag/project": "${project_id}"
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::openharness-artifacts-${project_id}/*"
    }
    // 没有列出的操作 = 隐式拒绝
  ]
}

// Sandbox Pod 的 IAM Policy:空
// Sandbox Pod 不需要任何 AWS API 访问
// 它的 ServiceAccount 不绑定任何 IAM Role

Sandbox Pod 没有 IAM Role——它根本不需要调用任何 AWS API。git clone 和包安装走的是公网 HTTPS,不经过 AWS IAM 认证。这种「Sandbox 无云凭证」的设计确保了:即使攻击者完全控制了 Sandbox Pod,它也只是一个能上网(访问受限站点)的容器,无法触达任何 AWS 资源。

这种设计与 Ch 9 的权限模型有直接对应。Ch 9 中,Tool 的 checkPermissions() 在每次调用前做检查;IRSA 在每次 AWS API 调用前做检查。区别在于执行者不同:checkPermissions() 由 Agent 进程自己执行(软约束),IRSA 由 AWS 的 STS 服务执行(硬约束)。Agent 可以绕过自己进程内的检查(如果代码有 bug),但无法绕过 AWS STS 的鉴权——这是另一个「确定性脚手架包围非确定性行为」的实例。


24.5 Kyverno:Kubernetes 的 Hook 系统

问题

IAM 管的是 AWS API 访问,NetworkPolicy 管的是网络流量。但还有一类风险:Agent 可能通过 Kubernetes API 做危险操作——创建 privileged Pod、挂载宿主机目录、修改其他 namespace 的资源。如何在 K8s API 层面加一道防线?

思路

Kyverno 是 Kubernetes 的策略引擎——它像 Ch 11 的 Hook 系统一样,在「API 请求到达 K8s 之前」插入检查。每个进入 K8s API Server 的请求都会经过 Kyverno 的 admission webhook,被策略评估后才能放行或拒绝。

kubectl apply / Agent 操作
        │
        ▼
┌──────────────────────────────┐
│   K8s API Server              │
│   ┌────────────────────────┐ │
│   │  Admission Webhook     │ │
│   │  ┌──────────────────┐  │ │
│   │  │  Kyverno Engine  │  │ │  ◄── 策略检查点
│   │  │                  │  │ │
│   │  │  Rule 1: 禁止    │  │ │
│   │  │    privileged    │  │ │
│   │  │  Rule 2: 必须有  │  │ │
│   │  │    resource limit│  │ │
│   │  │  Rule 3: 镜像必  │  │ │
│   │  │    须来自 ECR    │  │ │
│   │  └──────────────────┘  │ │
│   └────────────────────────┘ │
│           │                  │
│    通过 / 拒绝                │
└──────────────────────────────┘

这和 Ch 11 的 Hook 系统是同一个模式:

维度Ch 11 的 HookKyverno
执行时机工具调用前/后K8s API 请求处理前
策略语言TypeScript 函数YAML 声明式规则
执行者Agent 进程内K8s API Server
可绕过性Agent 代码 bug 可能绕过只有集群管理员能修改
粒度单个工具调用单个 K8s API 请求

关键区别在第四行:Kyverno 的策略不在 Agent 进程内执行,Agent 无法绕过它。即使 Agent 获得了创建 Pod 的 Kubernetes 权限(通过 ServiceAccount),Kyverno 仍然可以拒绝不符合策略的 Pod 创建请求。这是又一层「确定性脚手架」。

实现

OpenHarness 的典型 Kyverno 策略集:

// 策略 1:禁止特权容器
rule "deny-privileged" {
  match: Pod
  condition: spec.containers[*].securityContext.privileged == true
  action: DENY
  message: "Agent pods must not run as privileged"
}

// 策略 2:强制资源限制
rule "require-resource-limits" {
  match: Pod (namespace: agent-*)
  condition: spec.containers[*].resources.limits is empty
  action: DENY
  message: "Agent pods must have CPU and memory limits"
}

// 策略 3:只允许来自 ECR 的镜像
rule "restrict-image-registry" {
  match: Pod (namespace: agent-*)
  condition: spec.containers[*].image NOT startsWith
             "${ACCOUNT_ID}.dkr.ecr.*.amazonaws.com/"
  action: DENY
  message: "Only ECR images are allowed in agent namespaces"
}

// 策略 4:禁止挂载宿主机路径
rule "deny-host-path" {
  match: Pod
  condition: spec.volumes[*].hostPath is not empty
  action: DENY
  message: "Host path volumes are not allowed"
}

// 策略 5:自动注入标签(变更策略,非验证策略)
rule "add-project-labels" {
  match: Pod (namespace: agent-*)
  mutate: add label "openharness.io/managed=true"
}

策略 5 展示了 Kyverno 不仅能拒绝,还能变更——在 Pod 创建时自动注入标签。这用于标记所有 Agent 管理的 Pod,方便后续的监控和审计。


24.6 AGENTS.md:声明式的治理文档

问题

前面的五层防护(命名空间、双 Pod、IRSA、NetworkPolicy、Kyverno)都是基础设施级的硬约束。但还有一类约束不好用基础设施表达:「这个仓库的 Agent 应该使用 Python 3.12 而不是 3.9」「修改 database/ 目录下的文件需要 DBA 审批」「commit message 必须包含 ticket 编号」。这些是项目级的行为规范,太细粒度、太项目特定,不适合写成 Kyverno 策略。

思路

OpenHarness 借鉴了 Ch 17 的 CLAUDE.md 模式——在仓库根目录放置一个 AGENTS.md 文件作为 Agent 的行为指南。但与 CLAUDE.md 的「记忆文件」定位不同,AGENTS.md 更接近一个治理文档

# AGENTS.md - 仓库级 Agent 治理文档

## 身份
role: backend-developer
language: Python 3.12
framework: FastAPI

## 约束 (CONSTRAIN)
forbidden_paths:
  - database/migrations/  # 需要 DBA 审批
  - .github/workflows/    # 需要 DevOps 审批
  - secrets/              # 永远不允许
max_file_changes_per_pr: 20
require_tests: true

## 上下文 (INFORM)  
architecture_doc: docs/architecture.md
coding_standards: docs/coding-standards.md
api_conventions: docs/api-conventions.md

## 验证 (VERIFY)
required_checks:
  - pytest
  - mypy --strict
  - ruff check
commit_message_format: "feat|fix|refactor(scope): description [TICKET-NNN]"

## 纠错 (CORRECT)
on_ci_failure: auto-fix (max 3 attempts)
on_review_reject: revise and resubmit
escalation: @team-lead

AGENTS.md 的设计哲学是:让不写代码的人也能参与 Agent 的治理。一个项目经理不需要理解 Kyverno YAML 就能在 AGENTS.md 中声明「修改 migrations 目录需要 DBA 审批」。一个 Tech Lead 不需要修改 CI 配置就能在 AGENTS.md 中添加「必须包含测试」。

这和 Ch 11 的 Hook 系统以及 Ch 19 的 Skills 系统有直接对应:

  • Hook 是「可编程的安全策略」→ AGENTS.md 的 forbidden_paths 是声明式的安全策略
  • Skills 是「用 Markdown 教 Agent 新能力」→ AGENTS.md 的 architecture_doc 引用是教 Agent 理解项目
  • CLAUDE.md 是「跨会话记忆」→ AGENTS.md 是「跨 Agent 治理」

区别在于:CLAUDE.md 属于 INFORM 支柱(提供上下文),而 AGENTS.md 跨越了所有四根支柱——它的不同 section 分别服务于 CONSTRAIN、INFORM、VERIFY、CORRECT。

实现

AGENTS.md 的执行不是纯靠 Agent「读了就遵守」——那是软约束。OpenHarness 在 Agent 会话启动时解析 AGENTS.md,将其中的硬约束(forbidden_paths、required_checks)转化为配置,注入到 Agent 的执行引擎和 CI 流水线中。

function loadGovernanceDoc(repoPath) {
  agentsMd = readFile(repoPath + "/AGENTS.md")
  governance = parseGovernance(agentsMd)
  
  // 硬约束:注入到执行引擎
  engine.setForbiddenPaths(governance.constrain.forbidden_paths)
  engine.setMaxFileChanges(governance.constrain.max_file_changes_per_pr)
  
  // 上下文:注入到 System Prompt
  for doc in governance.inform.references {
    context.addDocument(readFile(repoPath + "/" + doc))
  }
  
  // 验证:注入到 CI 配置
  ci.setRequiredChecks(governance.verify.required_checks)
  ci.setCommitFormat(governance.verify.commit_message_format)
  
  // 纠错:配置自修复策略
  correct.setAutoFixPolicy(governance.correct.on_ci_failure)
  correct.setEscalation(governance.correct.escalation)
}

这种「声明式文档 + 运行时解析 + 强制执行」的模式,让 AGENTS.md 不仅仅是一个提示词——它的部分内容(forbidden_paths、required_checks)被转化为代码级的约束,具有与 Kyverno 策略类似的强制力。


24.7 命名空间隔离与多租户

问题

到目前为止,讨论的安全机制都针对单个项目的 Agent。但在一个共享集群中,多个项目的 Agent 同时运行。如何确保 Project-A 的 Agent 看不到 Project-B 的代码和数据?

思路

Kubernetes 的命名空间是天然的多租户边界。OpenHarness 为每个项目创建一个独立的命名空间,所有项目级资源(Pod、PVC、Service、ConfigMap)都在这个命名空间内:

Cluster
├── namespace: openharness-system      ← 控制平面
│   ├── API Server Pod
│   ├── Task Queue Worker
│   └── Monitoring Stack
│
├── namespace: project-alpha            ← 项目 A 的隔离区
│   ├── Agent Pod (alpha)
│   ├── Sandbox Pod (alpha)
│   ├── PVC: workspace-alpha
│   └── ServiceAccount: agent-sa-alpha  → IAM Role: role-alpha
│
├── namespace: project-beta             ← 项目 B 的隔离区
│   ├── Agent Pod (beta)
│   ├── Sandbox Pod (beta)
│   ├── PVC: workspace-beta
│   └── ServiceAccount: agent-sa-beta   → IAM Role: role-beta
│
└── namespace: project-gamma            ← 项目 C 的隔离区
    └── ...

命名空间之间的隔离由三个机制联合保证:

  1. K8s RBAC:每个 ServiceAccount 只有本命名空间的权限,无法 list/get/watch 其他命名空间的资源。
  2. NetworkPolicy:默认拒绝所有跨命名空间流量,只允许到 openharness-system 的特定端口。
  3. IRSA:每个项目的 IAM Role 只允许访问该项目的 S3 路径和 Bedrock 资源。

这三层独立生效。即使 RBAC 配置有误(Agent 意外获得了跨命名空间的权限),NetworkPolicy 仍然阻止跨命名空间的网络通信。即使 NetworkPolicy 有漏洞,IRSA 仍然确保跨项目的 AWS 资源不可访问。这种「每层假设其他层可能失败」的设计,是 Ch 9 三层权限防线的云上版本。


24.8 六层防护的完整视图

回顾本章讨论的六层安全防护,它们形成一个从物理隔离到行为约束的渐进光谱:

    硬 ←───────────────────────────────────→ 软

    Layer 0         Layer 1-3           Layer 4-5
    命名空间隔离     双Pod + NP + IRSA    Kyverno + AGENTS.md
    ─────────       ─────────────       ──────────────
    多租户边界       执行环境隔离         策略与治理
    K8s 原生        K8s + AWS           声明式规则
    Agent 无感知     Agent 部分感知       Agent 主动遵守

从左到右,防护越来越「软」但越来越「细」。命名空间隔离是最粗粒度的(整个项目的边界),但 Agent 完全无法感知和绕过它。AGENTS.md 是最细粒度的(具体到某个目录的写权限),但它的执行部分依赖 Agent 的合作。

这就是为什么六层都需要:硬层(0-3)保证底线安全,软层(4-5)提供精细控制。只有硬层会过于粗暴(所有操作要么允许要么拒绝,没有中间地带),只有软层会不够安全(依赖 Agent 自律)。两者结合,才能在安全和灵活之间找到 Ch 22 所说的平衡点。


思考题

  1. 双 Pod 模型引入了 gRPC 通信延迟。对于需要大量小文件读写的 Agent 任务(比如逐行分析 1000 个源文件),这个延迟可能显著影响性能。如何优化?提示:考虑批量 API、本地缓存、或者选择性放松隔离。

  2. AGENTS.md 的 forbidden_paths 是静态列表。但有些路径的敏感性是动态的——比如 config/ 目录在开发分支上可以自由修改,在 release 分支上需要审批。设计一种支持分支感知的 forbidden_paths 语法。

  3. 本章讨论的所有安全机制都假设 Kubernetes 集群本身是可信的。但如果集群管理员误配置了 NetworkPolicy(比如遗漏了一条 deny 规则),整个安全模型就可能失效。设计一个「安全配置自检」工具:它应该检查哪些内容?多久运行一次?检测到问题时应该做什么?

  4. Sandbox Pod 可以访问 git 仓库和包仓库。但恶意的 npm 包可能包含后门(供应链攻击)。OpenHarness 应该如何防范这种场景?提示:考虑 Ch 25 的 VERIFY 支柱如何与 CONSTRAIN 支柱协作。