跳转到主要内容

代理隔离最佳实践:沙箱隔离(私密测试版)

对于我们网络验证计划中的客户,我们在 API 中提供了新的沙箱逃逸分类器,用于监控和减少滥用。本文解释了为什么自主代理需要强隔离、参考设计如何隔离它们,以及如何确定和监督需要网络访问的任务范围。

此分类器处于私密测试版。

有关所有可用资源的概览,请参阅 代理隔离最佳实践:入门指南(私密测试版)。

概览

  • 在自主运行中,没有人类批准代理的工具调用,代理可能执行目标代码。我们建议在强沙箱中运行自主代理,该沙箱在主机与代理和目标代码之间放置硬件虚拟化内核。不要使用裸 Docker/runc,也不要使用 --privileged 或主机网络。

  • 参考设计在内部网络上的自己的微虚拟机(带 Firecracker 的 Kata Containers)中运行每个代理。它需要具有 KVM 的 Linux 主机,这限制了可以运行它的主机。Kata 已弃用 Firecracker 依赖的运行时。

  • 永远不要将包含凭据的路径(例如 ~/.aws、~/.ssh 或 .env)挂载到代理的环境中。也要将模型 API 凭据保留在代理的环境之外:让单独的凭据代理持有它,并将其添加到每个模型请求中(请参阅 凭据代理)。

  • 默认拒绝出站流量。在参考设计中,模型调用通过凭据代理进行,出站代理拒绝所有其他目标,除非您将其列入允许列表。

  • 在依赖沙箱之前,在每个主机上测试它,并在沙箱或模型更改时再次测试。

  • 对于需要网络访问的任务,在代理的指令中说明范围,在网络中强制执行,并将自主代理远离实时高风险系统(请参阅 范围和监督)。

沙箱隔离指南

目标属性

如果您构建自己的沙箱,这些是参考设计提供的属性。本文的其余部分描述了获得它们的一种方法。

  1. 每个代理都有自己的客户内核,它不与主机共享。

  2. 代理的文件和 shell 工具只能看到客户的文件系统。没有主机目录被共享到客户中。

  3. 模型 API 凭据不在代理的环境或磁盘上。沙箱外的代理将其添加到每个请求中。

  4. 代理没有互联网访问权限。出站流量在客户外部强制执行,所有目标都被拒绝,除非它在允许列表上。

  5. 无法从代理访问云元数据服务。

  6. 每个代理都有轮次限制,如果您设置了时间限制。

  7. 无特权模式、添加的功能、设备直通或主机网络。

  8. 编排器在受信任的主机上运行并将记录写入其中。

一般指南

强沙箱对自主运行最重要,其中代理执行目标代码,没有人类批准每个操作

在强沙箱中运行自主代理,并思考该沙箱中的进程可能仍然造成的副作用。前沿模型越来越善于找到绕过限制的创意路径:使它们成为有效漏洞猎人的相同属性意味着它们可能对自己的执行环境采取意外操作。这不是假设。Anthropic 已发布了模型绕过弱约束以完成任务的示例(请参阅 我们如何在各产品中隔离 Claude)。

具体来说,不要在裸 Docker/runc 中运行自主漏洞查找代理,尤其不要使用 --privileged 或主机网络。标准容器共享主机内核,因此容器内的内核漏洞是主机泄露。我们建议在目标代码和主机之间放置硬件虚拟化内核,这意味着在虚拟机中运行代理。如果不可能,请使用不包含任何其他内容的专用裸机主机。如果您构建自己的沙箱,我们建议在 Linux 上使用 Firecracker、在 Windows 上使用 Hyper-V,以及在 macOS 上使用基于 Hypervisor 框架的虚拟机。参考设计使用 Firecracker。

阻止来自沙箱的所有出站流量。代理仅通过添加凭据并在沙箱外运行的代理到达模型 API,因此沙箱不需要到 API 主机的直接路由。在运行开始前安装每个工具、包和依赖项,以便在运行期间无需获取任何内容。

交互式使用(有人在循环中)通常风险较低,但我们仍然建议为其使用沙箱。如果您从笔记本电脑上的 Claude Code 交互式驱动代理,要么审查每个工具使用(手动模式),要么依赖自动模式权限分类器并让人类批准到达存储库外的每个操作。自动模式删除常规权限提示:它自动批准读取和工作目录编辑,并将其他所有内容发送到旨在阻止破坏性、不可逆或偏离任务操作的后台分类器。这是尽力而为的检查。它可能会遗漏一些内容,在安全工作中,它也可能拒绝一些合法步骤。自动模式描述了它的工作原理、您可以期望什么以及如何为您的环境配置它。

永远不要将包含凭据的路径(例如 ~/.aws、~/.ssh 或 .env)挂载到代理的环境中。代理自己的模型调用使用的凭据也是如此。将其保留在沙箱外,并让代理无法读取的代理将其添加到每个请求中(请参阅 凭据代理)。不要将代理连接到具有对外部状态(如电子邮件、云存储或生产基础设施)的写入访问权限的 MCP 服务器或工具。

将每次运行分为具有不同网络策略的设置阶段和攻击阶段

这是将上述指南付诸实践的一种模式。设置阶段具有出站互联网访问权限和一个批准每个工具调用的人类。在其中,代理拉取依赖项、构建目标并从规范文档中建立其沙箱。攻击阶段没有一般互联网访问权限。所有出站流量都通过允许列表代理进行,该代理仅允许任务中命名的主机,因此代理也强制执行范围。模型调用通过单独的凭据代理进行。代理随后可以无人值守地探测目标。代理包含代理自己的流量。它不包含网络化目标代表代理发送的流量。

将代理需要重复使用的依赖项烘焙到设置映像中,以便攻击阶段运行根本不需要互联网访问。按目标确定凭据范围,以便在一个目标上工作的代理无法针对另一个目标使用它们。在攻击阶段期间在沙箱内保持 Claude Code 的自动模式打开,并向其分类器描述沙箱。请参阅 自动模式。

限制每次运行,并了解您的关闭开关

给每个代理一个明确的预算,以便超过其预期测试的运行在有人注意到时停止。对每个代理强制执行硬轮次限制。当代理耗尽它时,结束运行,不要自动授予更多轮次。使用更高的限制重新启动是一个人决定继续的时刻。也要在时间上限制运行。以相同的方式结束超过其时间限制的会话:运行是最终的,永远不会恢复,代理进程被停止,即使在长时间运行的命令中间达到限制。如果限制设置为无法读取的值,请拒绝启动而不是无上限运行。为任务故意设置限制,不要接受慷慨的默认值。将它们与 代理记录的离线监控中的监控频率配对,以便每小时或两小时查看一次长批次,而不仅仅在最后。了解如何停止单个代理而不停止批次。在基于 Docker 的设置中,这是 docker rm -f <agent-container>。编排器应将该运行记录为失败并继续处理其余批次。

有关更多指导,请阅读两个 Anthropic 资源。安全部署 AI 代理涵盖隔离选项、凭据代理和文件系统强化。工程回顾 我们如何在各产品中隔离 Claude 涵盖了这些相同机制在生产中运行时的保留内容和未保留内容。

范围和监督

沙箱限制代理可以到达的内容。对于渗透测试、红队和其他需要网络访问的任务,以下实践限制了代理被要求和允许做的事情。它们取决于您的目标和您的团队,因此工具无法为您实施其中大部分。

在代理的指令中说明范围

在运行之前,告诉代理哪些目标在范围内、允许哪些操作、网络边界在哪里以及什么超出范围。将每个约束表述为意图("不要访问 10.0.3.0/24 之外的主机"),而不是关于环境的声明(

在网络中强制执行相同的范围

在可能的情况下,在上述相同隔离内运行代理,并仅将出站流量列入允许列表到范围内的目标(请参阅 出站允许列表)。代理通过凭据代理而不是通过允许列表到达模型 API。如果有可用的,将任务指向与生产断开连接的暂存或副本环境。

在可能的情况下代理对目标的访问

通过您可以观察的东西(例如访问代理或一组定义的工具)给代理对目标系统的访问权限。避免让代理使用对目标的一般访问权限编写自己的工具。在自定义工具中,将只读工具与更改状态的工具分开,并更密切地监督第二组。考虑在提议时分析有风险的命令,并拒绝它们或将其升级给一个人。

监督具有网络访问权限的运行

我们建议工程师在运行执行时观察每次运行,跟踪工具调用和网络活动,并能够立即停止运行。代理以机器速度行动,因此实时观察补充了在操作执行前起作用的控制(网络允许列表、代理工具和状态更改操作审查)。它不会替代它们。对于连续关注不切实际的长期或自主运行,在软件中连续监控(请参阅 代理记录的离线监控)。

将自主代理远离实时高风险系统

不要针对实时生产系统运行自主代理,其中超出范围的操作可能危害安全或关键服务的可用性,例如 OT/ICS、医疗或能源系统。这已经是对此类系统进行人类主导测试的规范,它同样适用于此处。针对副本、测试台或数字孪生进行测试,或在计划的停机期间进行测试。如果实时访问不可避免,将代理限制为被动或只读活动,并让一个人执行每个状态更改步骤。

在依赖沙箱之前测试它

在从主机运行真实任务之前,使用以下两个步骤测试其沙箱。在首次真实使用之前执行此操作,并在每次更改模型或沙箱时再次执行。沙箱更改包括新主机、新版本的虚拟机管理程序或容器运行时、不同的网络或代理设置,以及编辑的容器或组合配置。

首先,让 Claude 审查配置

在主机上的交互式 Claude Code 会话中,要求它读取在此机器上设置的沙箱配置,并报告任何削弱本文所述边界的内容。指向它:

  • 虚拟机管理程序和容器运行时配置(对于参考设计,Kata 的呈现的 configuration.toml 和 Docker 的 daemon.json);

  • 代理的网络和运行时报告的出口代理,以及代理的允许列表;

  • 运行时报告的凭证代理,以及运行中的代理容器的环境(请参阅下面的 凭证代理);

  • 启动代理或目标的任何编排或编排文件;

  • 挂载或流式传输到来宾的内容列表(请参阅下面的 操作说明)。

此步骤仅读取配置和状态。它不启动代理。

然后,运行受监督的逃逸测试

以启动生产代理的相同方式启动测试代理,而不是使用手工构建的命令,以便测试执行真实路径。下面的 自己验证隔离 部分涵盖了边界的手工检查。给代理一个单一任务:尝试到达其边界之外的任何东西,并在任何东西成功时立即停止并报告。在提示中具体命名边界:主机、其他容器、容器运行时套接字、云元数据服务以及不在代理允许列表上的任何主机。仅在密切的人工监督下运行此操作,在没有互联网访问权限的主机上运行(除了模型 API),并且您已准备好重建,以便成功逃逸不会造成损害。在裸机上,在一次性虚拟机内运行测试。在云虚拟机上,使用新实例并在之后销毁它。在运行时观看会话。将任何成功视为阻止程序:修复主机,重复测试,并在测试通过之前不要从该主机运行参与。

之后,阅读整个记录

检查代理尝试了什么以及它是否逃脱。查找未通过代理的直接连接、凭证文件或元数据地址的读取、容器运行时套接字的使用或相邻容器的探测、容器自己的文件系统之外的写入,以及尝试更改代理自己的设置或工具。当记录显示每一个都被尝试并因您期望的原因失败时,测试通过。对于每一个,记录沙箱或权限分类器是否停止了它。分类器拒绝的尝试从未到达沙箱,因此用手工检查覆盖它(请参阅下面的 自己验证隔离)。通过的测试是最低标准,而不是隔离的证明。

避免不可能的任务,并首先查看失败的运行

在启动前确认任务可以按给定方式完成。无法完成的任务示例:没有任何内容可查找的焦点区域、无法到达的目标、不存在的错误或任务需要的未安装的工具。每当您更改设置或开始新类型的任务时,在启动前确认目标构建成功、可以到达,以及代理具有所需的工具。出于同样的原因,当您查看一批时,在查看成功的运行之前,先阅读失败或未发现任何内容的运行的记录。

自己验证隔离

在每个主机上手工运行这些检查。第二列描述参考设计(带有 Kata 运行时和 Firecracker 的 Docker)的检查。将其调整到您自己的运行时。

要确认的内容

如何

预期结果

单独的来宾内核

在沙箱容器和主机上运行 uname -r

两个版本不同

虚拟机监视器在主机上被监禁

对于运行中的容器,检查 Firecracker 进程:其根目录、/proc/<pid>/status 中的 Seccomp 和 NoNewPrivs、其挂载和网络命名空间,以及其 cgroup

只有虚拟机自己的文件在其根目录下。Seccomp: 2 和 NoNewPrivs: 1。两个命名空间都与主机的不同。cgroup 路径命名容器

主机文件在内部不可见

在主机上创建文件并尝试从沙箱容器中读取它

未找到。这是任何运行时都应该通过的基本检查

出口被拒绝

从代理容器中,通过出口代理请求模型 API 主机和另一个公共主机

两者都被拒绝,出口代理为每个记录一条拒绝行。代理仅通过凭证代理到达模型

代理容器中没有凭证

在运行时,列出代理容器的环境,按提供商名称过滤

占位符、凭证代理的地址和提供商设置。没有真实密钥、令牌或凭证文件

参考设计

本部分描述参考实现如何满足上述指导。实现未包含。将其作为您可以复制的设计阅读。

每个代理如何被隔离

每个代理在其自己的 microVM 内作为 claude -p 运行,位于目标二进制文件和源代码旁边。microVM 是由 Firecracker 虚拟机监视器支持的 Kata Containers 容器,注册到 Docker 作为运行时。代理的 Read、Write 和 Bash 工具仅看到该来宾的文件系统和内核。

来宾和主机之间的连接故意很小。它由 KVM 和 Firecracker 模拟的少数虚拟设备组成:容器磁盘的块设备、一个网络接口和 Kata 用来在来宾内启动进程的 vsock 控制通道。Firecracker 进程本身在主机上被限制。它在一个"监狱"中运行,这是一个 chroot 目录,仅包含该虚拟机的文件,具有自己的挂载命名空间、容器的网络命名空间和 Firecracker 的内置 seccomp 过滤器。它被计入容器的 cgroup。您在主机端信任的是 KVM、Firecracker 和 Kata 的运行时进程(Docker 与之通信的每个容器"shim",以 root 身份运行)。

编排器保留在受信任的主机上。它管理容器生命周期、流式传输记录,并使用 docker exec 移入和移出文件,Kata 通过其在来宾内的代理提供此功能。启动器仅在此沙箱内启动代理。在任何代理启动之前,它检查沙箱运行时是否已注册、/dev/kvm 是否存在以及出口代理是否启动,否则拒绝运行。

沙箱为每个表面更改的内容:

表面

没有沙箱

有沙箱

代理 Read/Write

主机文件系统

仅来宾文件系统(容器自己的虚拟磁盘,在来宾内核下)

代理 Bash

仅来宾 shell

仅来宾 shell(来宾内核;主机仅通过 KVM 和 Firecracker 的虚拟设备可达)

网络出口

主机拥有的任何内容

无互联网访问;仅限模型调用,通过凭证代理

模型-API 凭证

在代理的环境中

不在代理容器中;由单独的凭证代理容器持有(见下面的凭证代理)

主机耦合

完全

docker exec 用于文件进出,由 Kata 在客户机内的代理提供;只读输入在容器启动时作为副本到达;在运行期间更改的文件由编排器流式传入

权限检查

仅自动模式分类器

自动模式分类器加上微虚拟机边界(见自动模式)

每个属性的强制执行位置:硬件虚拟化提供内核和文件系统边界。代理的 Read/Write/Bash 针对客户机内核运行,其后的 Firecracker 进程在主机上被隔离和 seccomp 限制。出口策略在虚拟机网络接口的主机端强制执行,通过 Docker --internal 网桥(无默认路由)和该网络上的两个代理。凭证代理仅转发模型调用,不转发其他内容。出口代理拒绝所有其他目标,因为其允许列表为空,除非您添加内容。流量通过客户机自己的网络堆栈离开;过滤发生在两个代理中。

凭证代理

模型 API 的凭证(您的 API 密钥、OAuth 令牌、AWS 或 Google 凭证)永远不会放在代理容器中。主机上的编排器读取并验证它,每次启动都会启动一个小的额外容器,即凭证代理,来持有它。代理连接到代理的内部网络,代理容器将其模型调用发送到它:它们的 claude CLI 获取代理的地址作为其 API 基础 URL,以及一个固定的占位符值,其中密钥或令牌通常会出现。这遵循仅通过代理到达模型 API 的最佳实践,API 密钥从沙箱外部注入。在参考设计中,代理是代理网络上的单独容器,而不是代理自己的 localhost 上的进程。对于每个请求,代理:

  1. 拒绝任何不是对启动配置的一个提供商端点的模型调用的内容(其他路径和主机获得 403 和日志行);

  2. 删除容器发送的任何身份验证标头;

  3. 添加真实凭证:API 密钥标头、持有者令牌(代理在令牌短期时自行刷新)或通过精确请求计算的 AWS SigV4 签名;

  4. 通过 HTTPS 将请求转发给提供商,进行证书验证,并将响应流式传回。

对于受损的代理容器,这意味着没有密钥、令牌或凭证文件可读取、复制或发送到任何地方。云元数据服务也无法从容器访问。容器仍然可以做的是通过代理进行模型调用,在您的帐户上,只要运行持续进行。因此,对于代理运行,更喜欢范围狭窄的凭证,并在任何运行后轮换它,其成绩单显示意外行为。代理为每个请求记录一行,包含客户端地址、方法、路径、状态和大小。它不记录标头或正文。在运行结束时保存该日志(见离线监控代理成绩单中的保留部分)。

代理发送到模型 API 的任何内容都会离开您的网络,在代理不检查或记录的请求正文中。

每个身份验证路由,每一方持有的内容:

路由

凭证代理持有的内容

代理容器获得的内容

API 密钥

密钥

代理地址 + 占位符 ANTHROPIC_API_KEY

OAuth 令牌(claude setup-token)

令牌

代理地址 + 占位符 CLAUDE_CODE_OAUTH_TOKEN

工作负载身份联合 (WIF)

身份令牌,加上代理交换并刷新的访问令牌。使用 ANTHROPIC_IDENTITY_TOKEN_FILE,令牌文件的目录以只读方式挂载到代理中,因此代理看到轮换的令牌。使用内联 ANTHROPIC_IDENTITY_TOKEN,代理持有该值,没有文件可重新读取

代理地址 + 占位符 CLAUDE_CODE_OAUTH_TOKEN

ant auth login 配置文件

配置文件目录,以只读方式挂载到代理中

代理地址 + 占位符 CLAUDE_CODE_OAUTH_TOKEN

Bedrock

持有者令牌或访问密钥集(代理签署每个请求)

代理地址、CLAUDE_CODE_SKIP_BEDROCK_AUTH=1、区域;无 AWS 凭证

Vertex

服务帐户密钥,加上代理从中生成并刷新的访问令牌

代理地址、CLAUDE_CODE_SKIP_VERTEX_AUTH=1、区域和项目;无 Google 凭证

凭证代理容器是设置的受信任端的一部分。它在普通 runc 而不是沙箱运行时下运行,以 root 身份运行,除了读取挂载文件所需的一个功能外,所有功能都被删除,并且文件系统为只读。它仅在代理内部网络上的其地址上侦听,并直接连接到提供商,而不是通过出口代理。编排器通过 docker exec 将凭证传递给运行中的代理。对于 WIF 令牌文件和配置文件路由,持有令牌或配置文件的目录以只读方式挂载到代理中,代理从那里读取文件。凭证不在代理容器的环境或命令行中,因此对其进行 docker inspect 显示挂载而不是秘密。

每次启动启动一个代理,并在运行结束或中断时将其删除。检查被杀死的运行留下的代理,并在下次启动前将其删除。

凭证代理有两个副作用。首先,代理的 CLI 被赋予非默认 API 地址,因此仅适用于默认地址的一些 CLI 功能在代理容器内关闭。其遥测和更新检查也被关闭,因为它们无论如何都没有路由。其次,在 Vertex 上,代理的路径过滤器是阻止代理容器使用 Vertex AI API 其余部分的主要控制(见出口允许列表)。在启动时检查密钥的 IAM 范围是第二层。

出口允许列表

出口代理的允许列表默认为空。代理容器仅通过凭证代理到达模型 API(见凭证代理)。每次启动都会为您的凭证选择的提供商启动该代理,因此沙箱设置中不存储任何提供商特定的内容。代理通过出口代理请求的任何其他目标,包括模型 API 主机本身,都会获得 403 和其日志中的拒绝行。选择 Bedrock 和 Vertex 端点的区域值(AWS_REGION、CLOUD_ML_REGION)在使用前会根据严格的模式进行检查,以便格式错误的值无法将凭证发送到不同的主机。

Vertex 有一个需要注意的限制。…aiplatform.googleapis.com 提供整个 Vertex AI API,因此允许创建自定义作业的凭证可以在您的项目中运行具有完全互联网出口的任意容器。使用两个控制来对抗这一点。让凭证代理仅在您配置的项目上转发发布者模型调用(…/publishers/anthropic/models/…:rawPredict, :streamRawPredict 和 :countTokens)并拒绝所有其他路径。并在启动时检查密钥的 IAM 范围并关闭失败:拒绝 gcloud 用户 ADC,使用 testIamPermissions 检查服务帐户密钥,如果它持有任何工作负载创建权限,则拒绝它。向帐户授予仅持有 aiplatform.endpoints.predict 的自定义角色。元数据服务器、Google 的 STS 和 IAM 凭证端点不应从代理容器访问。

如果代理需要到达额外的主机,例如包镜像或范围内的目标,将它们作为 host:port 条目添加到出口代理的允许列表中。仅添加参与所需的内容(见范围和监督)。

不要将 model-API 主机添加到此列表。代理不需要它,因为它们的模型调用通过凭证代理进行。添加它会给代理容器一条直接通往 API 的路由,而设计的其余部分(包括权限分类器被告知的内容)假设它们没有这条路由。

更改允许列表意味着重新启动出口代理,这会断开任何活跃的代理连接。在批次之间而不是在批次期间执行此操作,并在之后确认代理加载了哪个列表。

从您的凭证配置中派生提供商端点,并忽略在主机上设置的基础 URL 变量(如 ANTHROPIC_BASE_URL):凭证代理应始终转发到凭证选择的端点。

构建和运营这样的沙箱

本部分汇总了在参考实现中使用 Kata 和 Firecracker 运行代理所学到的内容。这不是一套安装步骤。

主机要求

参考设计需要一台物理 Linux 机器或能够自身运行虚拟机的虚拟机:

  • Linux x86_64 或 aarch64 with KVM(/dev/kvm 存在且可用):裸机或启用了嵌套虚拟化的云虚拟机。

    • GCE、Azure 和 AWS 在选定的实例类型上提供嵌套虚拟化(例如在 AWS 上,C8i、M8i 和 R8i 系列)。AWS 裸机(*.metal)实例也可以工作。

    • 嵌套虚拟化会降低一些性能。预计不会削弱边界。

  • 容器的块设备存储。Firecracker 无法将主机目录共享到来宾中;它能附加到虚拟机的唯一存储是块设备。因此,容器根文件系统必须作为块设备存在,而不是通常的覆盖文件系统。对于 Docker,containerd 的 devmapper 快照程序提供了这一功能。它需要 rootful Docker(参考设计使用 Engine 25 或更新版本)由系统 containerd(2.0 或更新版本)支持。

  • 在构建主机和镜像时的出站网络访问。代理不会获得此出口。

它不会在以下情况下工作:

  • 容器或 Kubernetes pod,包括 Docker-in-Docker 和基于容器的 CI 运行器。主机的 containerd、Docker 守护程序和设备映射器无法从容器内部配置,KVM 通常也不可用;

  • 无根 Docker;

  • 没有嵌套虚拟化的虚拟机,这是大多数通用云实例类型,除非您选择提供它的实例并将其打开;

  • macOS 或 Windows,包括 Docker Desktop 和 WSL2。

参考设计不支持 macOS 或 Windows 主机

其沙箱需要 Linux 主机上的 KVM。Mac 上的 Docker 在共享 Linux 虚拟机内运行,该虚拟机挂载用户的主目录并保存 Docker 守护程序,因此无法提供每个代理的硬件隔离。如果您在 Mac 上工作,请在具有 KVM 的 Linux 主机(裸机或具有嵌套虚拟化的云虚拟机)上运行代理,并通过 SSH 驱动它们。如果您在 macOS 或 Windows 上构建自己的沙箱,请参阅上面一般指南部分下命名的虚拟机管理程序。

切换 Docker 的镜像存储有一个副作用

在切换到 devmapper 快照程序后,在 Docker 之前的存储上创建的镜像和容器对 Docker 变得不可见。它们保留在磁盘上。要再次看到它们,请将 daemon.json 中的两个存储设置(storage-driver 和 features.containerd-snapshotter)恢复为之前的状态并重新启动 Docker。

Kata 设置

固定 Kata 版本,并在安装前验证其摘要。参考设计从 Kata 的 Firecracker 配置文件开始,并在 /etc/kata-containers/configuration.toml 中固定这些设置:

设置

值

原因

jailer_path

Kata 的 jailer 二进制文件的路径

在其监狱内启动 Firecracker:一个 chroot,仅包含虚拟机的文件、其自己的挂载命名空间、容器的网络命名空间和 Firecracker 的 seccomp 过滤器。如果未设置,Kata 将在没有监狱的情况下运行 Firecracker。

enable_annotations

[]

容器无法通过 OCI 注释更改任何虚拟机管理程序设置。

disable_guest_seccomp

false

容器的 Docker seccomp 配置文件也应用于来宾内部,这是虚拟机边界下的第二层。

sandbox_cgroup_only

true

Firecracker 及其 I/O 线程被放置在容器的 cgroup 中,因此虚拟机被计入容器。容器的 --memory 限制是否也限制虚拟机取决于主机(请参阅大小调整)。

static_sandbox_resource_mgmt

true

Firecracker 无法向运行中的虚拟机添加 CPU 或内存,因此虚拟机在启动时从容器的限制中调整大小。

default_vcpus / default_memory

4 / 4096 MiB

每个虚拟机的基线;容器的 --memory 在顶部添加。为构建密集型目标提高(Firecracker 每个虚拟机最多允许 32 个 vCPU)。

debug_console_enabled, enable_debug

false

没有进入来宾的控制台,主机日志中也没有来宾控制台输出。

[factory] enable_template

false

关闭,因为虚拟机模板化会在虚拟机之间共享来宾内存页面。

entropy_source

/dev/urandom

为客户端提供非阻塞主机熵。

Kata 版本附带的 kernel、image 和 kernel_params 值保持不变。

使客户端内核和镜像不可变

每个虚拟机都从相同的两个文件启动,Kata 将它们绑定挂载到每个监狱中,jailer 以 root 身份启动,因此普通文件权限无法阻止受损的 Firecracker 进程重写每个后续虚拟机启动的镜像。在两个文件上设置不可变标志(chattr +i)。清除该标志需要一个系统调用,Firecracker 的 seccomp 过滤器不允许该调用,该过滤器旨在阻止监狱内的 root 用户执行此操作。在安装新的 Kata 版本之前,请自己清除该标志。在不支持 chattr 的文件系统上,改用只读绑定挂载。

大小调整

内存

每个代理虚拟机在启动时调整大小一次:default_memory 加上容器的 --memory。基线为 4096 MiB,容器限制为 4g,代理是 8 GiB 虚拟机。主机在客户端使用内存时将该内存分配给虚拟机,而不是在启动时全部分配,但要为每个并发代理规划完整数量:十个并行代理可能增长到 80 GiB。容器的 --memory 限制是否也限制整个虚拟机取决于主机的 cgroup 如何布局。检查以下哪一项适用于您的主机:

  • 容器限制适用于整个虚拟机。代理无法使用超过 --memory 的主机内存,即使其虚拟机名义上更大,超过限制的虚拟机会从主机端被杀死。

  • 主机上没有任何东西将虚拟机限制在其启动大小以下。代理的内存使用受虚拟机大小(default_memory + --memory)和客户端内核自身的内存不足杀手的限制。

  • 父 cgroup 将其限制为不同的值。

我们尚未在每种主机上确认此行为。无论如何,按虚拟机大小调整主机大小。

CPU

每个虚拟机获得 default_vcpus。Firecracker 无法向运行中的虚拟机添加 CPU,因此对于构建密集型目标,在启动前提高该值;每个虚拟机的最大值为 32。

磁盘

每个镜像和每个容器都从设备映射器精简池中获得虚拟磁盘,该池支持镜像存储。如果池填满,容器内的写入会失败并出现 I/O 或空间不足错误。如果保存池的文件系统先填满,池将变为只读,其上的每个容器都会失败。使用 docker rmi 和 docker system prune 释放空间(释放的块返回到池)。sudo dmsetup status <pool> 显示使用情况。对于长期运行的主机,专用磁盘上的 LVM 精简池是更好的布局。

启动时间

每个代理启动都会启动一个客户端内核。在裸机上这需要不到一秒,在嵌套虚拟化下需要几秒,与代理运行时间相比很小。

加强主机安全

  • 将主机专用于此任务。

    • 假设代理可以读取其上的任何内容,除了代理需要的模型 API 凭证外,不要在那里保留任何凭证或敏感数据。

  • 保持代理流量接触的每一层都已修补,不仅仅是内核:Docker、代理容器的基础镜像以及主机和模型 API 之间的任何防火墙或网络设备。沙箱边界上过时的代理或防火墙本身就是攻击面。

  • 保持内核、KVM 和 CPU 微代码最新,并将内核的 CPU 漏洞缓解措施保留在其默认值。

    • grep . /sys/devices/system/cpu/vulnerabilities/* 不应显示任何 Vulnerable 行。

    • 如果主机与不能相互观察的工作负载共享,还应遵循 Firecracker 的 生产主机设置 指南中关于 SMT 和推测执行设置的内容。

  • 禁用交换(sudo swapoff -a,并从 /etc/fstab 中删除),以便客户端内存不会写入交换。

  • /dev/kvm 不需要对所有人可写,Kata 的安装和运行时目录必须保持由 root 拥有且不可被其他用户写入。

    • root:kvm 且模式为 0660 对 /dev/kvm 足够,因为 Kata 的运行时以 root 身份运行。

  • 永远不要向代理容器或目标服务添加 --privileged、--device、--cap-add 或主机网络。

    • 在 Kata 下,这些标志将主机设备和权限传递到虚拟机中。

  • 将代理保持在两个代理后面的内部网络上。

    • Firecracker 本身不进行数据包过滤;主机端网络和代理是出口控制。

  • 在故障排除之外保持 enable_debug 关闭;调试日志包括客户端控制台输出。

验证新主机

设置新机器后,此序列显示沙箱端到端工作。步骤 3 和 4 进行真实的模型调用。

  1. 在 自己验证隔离 下运行隔离检查。两个内核版本必须不同。注意容器内存限制是否限制虚拟机(参见 大小调整)。

  2. 在 在依赖沙箱之前测试它 下所述的沙箱运行时中,在您将使用的镜像中确认代理 CLI 运行。

  3. 测试边界:让 Claude 审查沙箱配置,然后运行受监督的逃逸测试,两者都如 在依赖沙箱之前测试它 中所述。

  4. 端到端运行小批次针对您知道的目标。使用您将用于真实参与的凭证模式运行此步骤,而不是替代 API 密钥:凭证代理以不同方式处理每种模式(WIF 的令牌交换、Bedrock 的签名、Vertex 的令牌铸造),此运行确认您的端到端工作。之后检查凭证代理的日志。

  5. 检查没有留下任何东西。批次完成后,不应保留任何代理容器、辅助容器、虚拟机进程或凭证代理。出口代理的日志应仅显示来自步骤 1 和 3 的探测的拒绝行,凭证代理的日志应仅显示模型调用。

操作说明

在 Kata 下使用 Firecracker 运行代理与运行普通容器的不同之处。

绑定挂载是副本;实时文件必须流式传输

Firecracker 没有主机文件系统共享,因此 Kata 在容器启动时将绑定挂载的文件或目录复制到客户端,之后主机上的更改在内部不可见。这对于在运行期间不更改的输入(例如目标源)很好,这些可以保持为只读挂载。不挂载或流式传输任何凭证文件,因为代理容器不包含任何凭证(参见 凭证代理)。在运行期间确实更改的文件必须由编排器通过 docker exec 写入容器,并保持最新。容器内副本是代理可以编辑的普通文件,因此让编排器读取主机上的原始文件,并从代理在测试中无法到达的副本判断发现。

每个代理需要一个伴随容器用于其网络

微虚拟机的网络接口必须在虚拟机启动时存在;Firecracker 之后无法添加一个。但是,Docker 仅在运行时创建容器后才连接容器的网络。参考设计解决了这个问题。它首先启动一个小的空闲容器,该容器连接到正确的网络并仅运行 sleep,然后在该容器的网络命名空间中启动代理(--network container:<name>)。Kata 在启动时在那里找到接口并将它们附加到虚拟机。伴随容器恰好服务一个虚拟机,因为 Kata 将虚拟机的主机端网络设备留在其中,因此一起创建和删除该对,不要重复使用伴随容器。一个伴随容器的成本大约是 12 MB 的空闲进程。

停止卡住的代理

docker rm -f <agent-container> 就足够了。编排器应检测死容器,将运行标记为失败,并在拆除运行时删除伴随容器。

一切都通过 IP 寻址

Docker 的嵌入式 DNS 在主机端网络命名空间中运行,从客户机内部无法访问,因此请将代理作为 IP 地址传递给代理,并为网络化目标分配静态 IP。

docker exec 有效,docker cp 无效

docker exec 进入代理容器的行为正常;Kata 内部的客户机代理运行该命令。docker cp 进出代理容器不起作用,因为容器的文件位于客户机内的虚拟磁盘上。使用 docker exec <container> cat <path> 等命令。

Firecracker 在其监禁环境中以 root 身份运行

Kata 以 uid 0 启动 jailer,因此 Firecracker 进程受其 chroot、命名空间、seccomp 过滤器和 cgroup 的限制,而不是由无特权用户 id 限制。每个 VM 共享的两个文件(客户机内核和镜像)由不可变标志保护(参见 Kata 设置)。

Kata 已弃用 Firecracker 依赖的运行时

Kata 通过其较旧的 Go 运行时运行 Firecracker。Kata 4.0 为其他虚拟机管理程序采用了较新的 Rust 运行时作为默认值,并弃用了 Go 运行时。上游表示 Go 运行时仍然接收关键错误修复和 CVE 修复,并且可能不会早于 Kata 5.0 被移除。Rust 运行时在其虚拟机管理程序中未列出 Firecracker,上游主要使用 QEMU 测试其 Docker 集成。规划迁移。更改 Kata 版本时,重复 验证新主机。Kata 也可以使用 Cloud Hypervisor 代替 Firecracker;参考实现尚未测试或加固该配置。

日志

Kata 的运行时、Firecracker 和客户机代理消息进入日志:journalctl -t kata。containerd 和 Docker 问题在 journalctl -u containerd 和 journalctl -u docker 中。

这是否解答了您的问题?