☰
OpenShell Podman 驱动网络模型解析:network=none 外网围栏与 supervisor 受控出口
2026/9/25 7:07:09 网站建设 项目流程

【免费下载链接】OpenShell

OpenShell is the safe, private runtime for autonomous AI agents.

项目地址:https://gitcode.com/gh_mirrors/op/OpenShell
点击查看免费下载

导读

本文基于 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 与源码,可以把这套网络模型归纳为三条原则:

  1. 工作负载零网络面:network=none+ 无端口发布 + 无 capabilities,让 agent 容器在操作系统层面就不具备出口能力;围栏在启动前、重启前都被 inspect 复核,任何偏离都会导致失败关闭。
  2. 唯一出口归 supervisor:所有 TCP/DNS 流量经认证 gRPC 通道交给 supervisor,由策略与网关会话授权后在主机网络发出;通用 UDP 明确不支持。
  3. 代理与凭据不出容器环境:上游代理配置、网关 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.

项目地址:https://gitcode.com/gh_mirrors/op/OpenShell
点击查看免费下载
上一篇:CANN/amct Qwen3.6-MoE NPU量化实践
下一篇:mpv EDL(编辑决策列表)格式全解析:语法规范、高级特性与源码级实现原理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询