【免费下载链接】OpenShell
OpenShell is the safe, private runtime for autonomous AI agents.
导读
本文基于 OpenShell 仓库中 Podman 计算驱动的官方网络说明 crates/openshell-driver-podman/NETWORKING.md,完整讲解该驱动如何在不创建 veth、不发布端口、不配置 nftables、不需要CAP_NET_ADMIN的前提下,让无特权 AI Agent 沙箱获得"受监督"的网络出口:工作负载容器使用network=none,其 DNS 与 TCP 流量经私有 Unix 套接字上的认证 gRPC 通道,由独立 supervisor 容器在 Podman 主机网络上代为放行。读完本文,你将理解外层网络围栏(outer network fence)的实现与验证方式、supervisor 主机网络的接入形态、两类容器的网络职责划分,以及官方故障排查清单背后的源码依据。
1. 网络拓扑总览:一条不经过 veth 的数据通路
官方文档开篇即给出该驱动的核心网络立场:
只有外部 supervisor 拥有外部网络连接能力。工作负载容器使用
network=none;它的回环 DNS 中继与 TCP 套接字中介,通过受保护的 Unix 套接字触达 supervisor,而不是经由 veth 或代理环境变量。
其数据流示意图为:
workload container supervisor container agent -> sandbox -- private UDS / gRPC -> policy proxy -> host network -> destination | +-- authenticated gateway session对应到仓库源码,这一"两容器 + 一通道"的模型在 crates/openshell-driver-podman/src/container.rs 中逐项落实:
- 工作负载容器(
openshell-sandbox)的netns.nsmode被硬编码为"none",同时networks.clear()、portmappings.clear()、hostadd.clear(),即不挂任何网络命名空间、不发布端口、不添加主机别名; - 工作负载容器被挂载一个名为
openshell-channel-<id>的私有命名卷(channel volume)到/.openshell/channel,用于承载 bootstrap 与双向 mTLS 材料; - supervisor 容器(
openshell-supervisor)的netns.nsmode被设置为"host",networks.clear()、portmappings.clear(),以 Podman 主机网络的身份对外出口,同时只读挂载同一 channel 卷("ro"+"z"共享 SELinux 重标记)。
也就是说,agent 产生的任何出口流量都无法直接在主机网络上出现——它必须先经过 sandbox 内的回环 DNS 中继与 TCP 中介,再经由私有 Unix 套接字上的 gRPC 连接交给 supervisor,由 supervisor 依据策略代理出去。这个设计把"谁能上网"和"出去的内容是否被授权"收敛到了唯一一个受信任进程上。
2. 外层网络围栏:默认拒绝出口
2.1 创建时不给任何网络能力
文档 "Outer network fence" 一节列出驱动在启动容器时的硬性约束:
- 创建工作负载时不带网络、不带主机别名、不发布端口、不附加任何 capabilities;
- 启动前与重启前,驱动都要检查 Podman inspect 响应,确认隔离围栏仍然成立;
- sandbox 在运行 agent 之前自行安装 seccomp 中介与 Landlock 约束;
- 驱动不创建网络命名空间、不配置 nftables、不需要
CAP_NET_ADMIN。
源码中的落实分两层:
镜像规范层(crates/openshell-driver-podman/src/container.rs):工作负载的cap_drop = ["ALL"],附加 capabilities 为空;同时设置sysctl net.ipv4.ip_unprivileged_port_start = 0,这样无特权的 sandbox 回环 DNS 中继可以在不持有任何 capability 的情况下把 DNS 服务绑定到 53 端口。
运行时验证层(crates/openshell-driver-podman/src/client.rs):verify_isolation_fence在启动前后通过/libpod/containers/{id}/json读取容器 inspect 结果,并检查三个条件——host_config.network_mode == "none"、容器非 privileged、network_settings.networks中不出现除none之外的任何网络名。任一条件不满足即返回错误:"sandbox requires an unprivileged container with network mode none and no attached networks"。
2.2 围栏证据参与信任投影
驱动不仅在运行时检查网络模式,还会把检查到的"本机事实"编码进隔离边界契约。在 crates/openshell-driver-podman/src/isolation.rs 中,PodmanOuterFenceEvidence携带container_id、network_mode: "none"与unexpected_networks,被投影为外层围栏保证(OuterFenceGuarantee):
DefaultDenyEgress(默认拒绝出口):工作负载没有可用的网络命名空间,任何出口天然被拒绝;RevocationVerified(吊销已验证):即使后续撤销凭据,流量也无法逃逸;ControllerLossFailsClosed(控制器丢失时失败关闭):即使 supervisor 退出,出口依然被拒;NoUnmanagedEgressPath(不存在非托管的出口路径):未发现任何意外的附加网络。
单元测试outer_fence_projection_rejects_each_missing_native_fact(crates/openshell-driver-podman/src/isolation.rs)专门验证:容器 ID 为空、network_mode 为bridge、或存在意外网络时,证据投影一律失败——保证"围栏必须被证明成立,而不是被假设成立"。
2.3 数据面只走一条认证通道
"Outer network fence" 一节的最后一段说明了流量类型与通道的关系:
- TCP 连接建立、TCP 字节流、DNS 请求/应答、生命周期控制操作,全部共享同一条认证 gRPC 通道;
- DNS 由 supervisor 解析并授权;
- 通用 UDP 不支持。
这与 crates/openshell-driver-podman/README.md 中"Seccomp socket mediation carries TCP and DNS through one authenticated gRPC connection"的表述一致。对需要 UDP 的场景(例如 QUIC、NTP),本驱动当前不提供放行路径,这是设计取舍而非疏漏。
3. Supervisor 网络:Podman 主机网络与网关回环
文档 "Supervisor network" 一节说明 supervisor 的接入形态与平台差异:
- supervisor 伴生容器使用Podman 主机网络(host network);
- Linux 上,supervisor 直连网关的主回环端点(loopback);
- macOS 上,由 Podman Machine 提供"主机回环路由"(host-loopback route);
- 上游公司代理只应用于 supervisor,不进入工作负载容器;
- 网关的 SSH 隧道使用 supervisor 在私有 Unix 套接字上的中继,因此驱动不发布 supervisor 端口。
这一平台差异在 crates/openshell-driver-podman/src/driver.rs 中有精确实现:PodmanEndpointEnvironment::current()根据目标平台选择网关主机——Linux 使用127.0.0.1,macOS(Podman Machine)使用host.containers.internal。随后select_grpc_endpoint在配置未显式指定grpc_endpoint时,按是否启用 TLS 拼出http(s)://<gateway_host>:<gateway_port>。这正是"supervisor 通过主机网络直连网关主回环端点"的源码证据。
Supervisor 容器本身的配置同样在 crates/openshell-driver-podman/src/container.rs:netns.nsmode = "host"、cap_drop = ["ALL"]且不新增任何 capability、userns使用主机用户命名空间。值得注意的细节是:文档强调 supervisor 与工作负载共享工作负载的用户命名空间——但源码注释显示,在 rootless Podman 下"加入工作负载用户命名空间与主机网络互不兼容",因此 supervisor 实际运行在主机用户命名空间(nsmode: "host"),同时依靠 channel 卷的共享 SELinux 重标记(z)来访问共享卷。文档中的"共享用户命名空间保留卷 UID/GID 映射"指的是工作负载自身的 userns 设置(如keep-id),而 supervisor 通过同样的 UID/GID 身份与只读挂载来维持共享卷的可读性;PID、mount、网络命名空间三者始终相互独立。
与网络配置相关的另一项约定是上游代理只作用于 supervisor:配置项https_proxy、proxy_auth_file、proxy_ca_bundle都在 crates/openshell-driver-podman/src/config.rs 中定义,且由驱动在 sandbox 创建时把凭据以仅 root 可读的 Podman secret方式投递给 supervisor(见 crates/openshell-driver-podman/src/driver.rs 的create_sandbox_proxy_auth_secret与validate_sandbox_proxy_ca_bundle)。这些凭据从不出现在容器环境变量中,也不进入工作负载容器。
4. 两类容器的职责边界:一张对照表
官方 README 用一张属性表界定了工作负载与 supervisor 的差异,结合 NETWORKING.md 可整理为网络视角下的职责清单:
| 属性 | 工作负载容器(openshell-sandbox) | Supervisor 容器(openshell-supervisor) |
|---|---|---|
| 网络模式 | none,仅回环 | Podman 主机网络(host) |
| 出口路径 | 无外部接口、无发布端口 | 策略放行后由主机网络直出 |
| UID/GID | 固定的非 root 工作负载身份 | 相同映射身份 |
| Capabilities | 全部丢弃、不新增 | 全部丢弃、不新增 |
| Seccomp | 运行时默认 + sandbox 自装过滤器 | 运行时默认 |
| 网关 JWT 与上游凭据 | 永不挂载 | 以 Podman secrets 挂载 |
| 用户卷与 CDI 设备 | 仅挂载到工作负载 | 永不挂载 |
| channel 卷 | 私有命名卷,可写(rw) | 同一卷,只读(ro) |
channel 卷中的内容同样有严格分工:卷内只有 sandbox bootstrap 与 sandbox 侧 TLS 身份;supervisor 私钥与运行时描述符存放在 supervisor 的私有文件系统(/.openshell/supervisor/层级)中,具体路径常量见 crates/openshell-driver-podman/src/isolation.rs。此外,Landlock 拒绝 agent 访问顶层/.openshell控制层级,防止镜像预置的符号链接把私有控制状态别名到用户挂载中。
5. 生命周期与就绪:网络围栏在重启时依然被验证
文档 "Troubleshooting" 一节提到"运行中的工作负载容器本身并不构成就绪"——就绪状态以 supervisor 的健康信号为准。这与 crates/openshell-driver-podman/README.md 中"Readiness uses the supervisor's private health socket; there is no shell, legacy marker, or TCP-listener shortcut"互相印证。
源码层面(crates/openshell-driver-podman/src/watcher.rs):inspect_workload在推导工作负载条件时,会检查其openshell.ai/isolation-role=sandbox标签,并在 supervisor 事件(create/start/stop/die/health_status)发生时联动工作负载;watcher 对 supervisor 的退出/移除事件会停止缺少伴生容器的运行中工作负载。同时网关在发布 Ready 之前,还要求 supervisor 完成认证会话(README 原文:"The gateway also requires the authenticated supervisor session before publishing Ready")。
生命周期事件中的网络含义:
- Start会从 supervisor 私有文件系统中的副本恢复已消费的 sandbox bootstrap,重新验证围栏(
verify_isolation_fence),再启动同一对容器; - Create先构建两个处于停止状态的容器并预置私有归档,工作负载先于 supervisor 启动,以便其用户命名空间在 supervisor 加入时已存在;
- Delete先移除伴生容器,再移除工作负载、channel、workspace 与驱动自有 secrets,用户自有卷保留。
6. 故障排查清单:官方步骤的源码依据
文档 "Troubleshooting" 一节给出了五条排查建议,且每条都能在仓库中找到对应实现,整理如下:
| 现象 | 排查步骤 | 源码依据 |
|---|---|---|
| 沙箱不合格探针失败(qualification probe) | 用容器日志定位被拒绝的内核/运行时原语;不要添加 capabilities 或禁用运行时 seccomp | 运行时必须通过无特权强制探针(含嵌套 seccomp 通知与 Landlock),见 README 的 Runtime posture 一节 |
| 沙箱无法认证 supervisor | 检查私有 channel 卷、匹配的用户命名空间映射、共享 SELinux 标签 | channel 卷以rw,z/ro,z挂载,见 container.rs;bootstrap 与 TLS 材料路径见 isolation.rs |
| supervisor 无法连接网关 | 检查其配置的网关端点、凭据、主机网络、网关主监听器 | 端点自动选择逻辑见 driver.rs,Linux 用127.0.0.1、macOS 用host.containers.internal |
| DNS 或出口被拒 | 检查 supervisor 的策略决策;不要给工作负载加网络、不要加解析器绕过、不要加直达网关的路由 | 出口必须经认证 gRPC 通道由 supervisor 代理,见外层围栏投影 isolation.rs |
| 配对未就绪(Pair not Ready) | 检查 supervisor 健康套接字与网关会话;仅容器运行不构成就绪 | watcher 依赖 supervisor 健康事件,见 watcher.rs |
官方文档建议配合 驱动总览 README 与 Podman 运行时文档 使用,后者用于查阅具体的运行时选项(如 userns 模式、network=none语义)。
7. 运维观察手段
文档与源码给出了两组实用的观测方式:
1. 用 sandbox-ID 标签区分两类容器。两个容器共享同一个 sandbox-ID 标签,但角色标签不同:工作负载为openshell.ai/isolation-role=sandbox,supervisor 为openshell.ai/isolation-role=supervisor(常量定义见 crates/openshell-driver-podman/src/isolation.rs)。可直接用 Podman 过滤查看:
podman ps -a --filter label=openshell.ai/isolation-role=sandbox podman ps -a --filter label=openshell.ai/isolation-role=supervisor podman inspect <container-id> | jq '.[0] | {NetworkMode: .HostConfig.NetworkMode, Privileged: .HostConfig.Privileged, Networks: .NetworkSettings.Networks}'其中 inspect 的三个字段(NetworkMode、Privileged、Networks)正是驱动在启动前后验证围栏时检查的对象,见 crates/openshell-driver-podman/src/client.rs。
2. 通过网关/驱动日志观测。驱动在连接 Podman 时会记录 cgroup 版本、网络后端、rootless 状态等信息(crates/openshell-driver-podman/src/driver.rs),并且要求 cgroups v2(否则启动失败并提示systemd.unified_cgroup_hierarchy=1)。网络相关的问题通常能在"围栏验证失败"或"supervisor 无法建立认证会话"的日志中直接定位。
8. 总结:网络最小权限模型的三个要点
回顾 NETWORKING.md 与源码,可以把这套网络模型归纳为三条原则:
- 工作负载零网络面:
network=none+ 无端口发布 + 无 capabilities,让 agent 容器在操作系统层面就不具备出口能力;围栏在启动前、重启前都被 inspect 复核,任何偏离都会导致失败关闭。 - 唯一出口归 supervisor:所有 TCP/DNS 流量经认证 gRPC 通道交给 supervisor,由策略与网关会话授权后在主机网络发出;通用 UDP 明确不支持。
- 代理与凭据不出容器环境:上游代理配置、网关 JWT、TLS 材料以 Podman secrets 与私有文件系统投递,channel 卷只承载 sandbox bootstrap 与 sandbox 侧 TLS 身份,保证秘密最小暴露。
这种"外部无接口、内部全监督"的网络形态,是 OpenShell 让无特权 AI Agent 在受控边界内运行的核心前提之一。相关实现可继续深入阅读 crates/openshell-driver-podman/src/container.rs、crates/openshell-driver-podman/src/client.rs、crates/openshell-driver-podman/src/isolation.rs 与 crates/openshell-driver-podman/src/watcher.rs。
【免费下载链接】OpenShell
OpenShell is the safe, private runtime for autonomous AI agents.
相关推荐
Podman 网络选项 --interface-name 深度解析:为 network create 指定宿主接口及其驱动语义
Podman 网络选项 interface name 深度解析:为 network create 指定宿主接口及其驱动语义 本文聚焦 Podman 的 inte
容器运行时云原生CLIPodman 网络配置检查实战:podman network inspect 命令、JSON 输出与 Go 模板格式化全解析
Podman 网络配置检查实战:podman network inspect 命令、JSON 输出与 Go 模板格式化全解析 导读 podman network
容器运行时云原生CLI深入解析 reth network crate:P2P 网络的四大任务、核心接口与事件驱动模型
深入解析 reth network crate:P2P 网络的四大任务、核心接口与事件驱动模型 本篇围绕 reth 仓库中 network 架构文档 https
区块链
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考