dctc_青龙 dctc_青龙 当前离线 积分5499 256主题1回帖141银子 超级版主 积分5499 关注TA 发消息 发表于 2026-10-23 07:04 | 显示全部楼层 |阅读模式 先讲个很常见的场面。一个六人平台团队把编码 Agent 接进内网,给了 shell,给了 `pip install`,让它能读仓库里的 `.env`,顺手还挂了 GitHub token 方便提 PR。前两周风平浪静。第三周,Agent 排查依赖冲突时读到了某个 issue 里的第三方脚本,照着跑了一条 `curl`,环境变量就这么 POST 到了一个刚注册的域名。日志里有痕迹,可整个过程中没有任何东西拦它。复盘一看,这些权限全是"调试方便"一点点堆上去的,没有一处走过审批。 事故的形状几乎都长一样:权限是增量长出来的,约束从来没有跟着一起长。2026 年 AI Agent 翻车的主因,不是模型不够聪明,而是运行时的权限边界糊得太厉害。NVIDIA 在 2026 年 2 月 24 日开源、Apache 2.0 许可的 OpenShell,盯的正是这一层——一个 Rust 写的 Agent 运行时,把「策略声明」翻译成内核能真正执行的约束。到 10 月 23 日,仓库攒了 17,681 star、1,832 fork,10 月 20 日发的 v0.1.5 是 0.1.x 系列目前最新的稳定版。 对准备上生产的团队来说,难题不在"能不能装起来",而在"装起来之后,怎么证明它确实拦住了不该发生的事"。下面这份清单分四块:架构约束、部署前置条件、策略工程、日常巡检,尽量给能直接落地的做法。 ## 一、三个边界先认清,再谈部署 OpenShell 的信任模型压在三个东西上,任何一项理解错,后面的加固基本白做。 **网关是控制面,不是执行面。** 沙箱生命周期、策略下发、凭证存储与端点绑定、SSH 隧道入口,都归它管。真正的判定发生在沙箱那边:seccomp 把 TCP 连接和 DNS 查询截下来,走 OpenShell Sandbox Protocol(一条双向认证的 HTTP/2 多路复用连接)交给可信侧的 supervisor,supervisor 再按 OPA 策略决定放还是拒。所有流量——包括被审计的、被改写的——都得从这条单连接过。 **沙箱是数据面,而且必须假设它已经失陷。** Linux 后端下,workload 用单一非 root 身份、零 capability,Landlock 卡文件路径,seccomp user notification 接管网络操作。设计上还要求 fail closed:supervisor 没确认边界之前不启动 Agent,连接一断就冻结 workload。这条性质直接决定运维动作怎么取舍——任何为了"少重启一次"去放宽边界的改动,都是在拆最关键的那层。 **控制分静态和动态两类。** 文件系统路径、Landlock 开关、运行用户与组,在沙箱创建时就锁死了,改一行都得重建沙箱;网络规则和中间件倒是能热更新。这句建议抄在值班手册第一页:线上发现某条路径没权限,`policy set` 是改不动的。 还有两个细节常被跳过,却直接决定你往哪个方向排障。 一个是凭证的三段式设计。supervisor 连网关用 gateway JWT,连沙箱用 mTLS 加 sandbox JWT,Agent 自己从不直连任何一方。两枚 token 都绑在「某个沙箱的某一次运行」上——这个概念叫 generation,沙箱一重启就换一代,旧 token 和旧证书当场作废。共享部署记得给 `gateway_jwt.ttl_secs` 设过期时间,本地单用户网关才可以不设。所以线上碰到"重启后 CLI 还能连、Agent 连不上",先去查 generation,别一头扎进网络策略里翻。 另一个是出口的物理收敛。沙箱跑在独立 network namespace 里,一对 veth 把流量全部导到宿主侧的 `10.200.0.1`,代理就在那儿监听。哪怕 Agent 进程完全无视 `HTTP_PROXY`、直接 `connect()` 一个公网 IP,它也只能到那个代理。这比"设环境变量 + 教育用户"靠谱太多,也是 OpenShell 敢把默认策略设成拒绝全部出站的原因。 顺带说个会踩的坑。代理靠读首字节识别 TLS ClientHello,然后用每个沙箱一张的临时 CA 明文终止。好处是凭证注入和 L7 检查都不用额外配,代价是做证书 pinning 的客户端会直接连不上。内部服务要是开了证书固定,要么给它单独开个 `tls: skip` 的端点,要么干脆别让它进 Agent 的可达范围。 ## 二、部署前置条件核对表 下面每一项缺失,结果都是"装成功但防护失效",在 Kubernetes 上尤其明显。 | 检查项 | 要求 | 不满足的后果 | |---|---|---| | CNI 强制 NetworkPolicy | 沙箱命名空间的 ingress 与 egress 均需真实生效 | K8s 会接受策略对象但不执行,workload 可直连外网,绕过 supervisor | | Kubernetes 版本 | 1.29+,启用 RBAC | chart 前置检查直接拒绝 | | Agent Sandbox | 先装 SIG 项目控制器与 CRD | 网关无法创建 Sandbox 资源 | | 网关副本数 | >1 必须外置 PostgreSQL,且 `workload.kind: deployment` | chart 会拒绝多副本 StatefulSet | | 指标端口 | 9090 仅对内网与监控命名空间开放 | 该端点明文、无认证,暴露即等于公开内部容量数据 | | 遥测 | `server.telemetryEnabled=false` | 默认开启,虽只上报聚合计数,合规环境仍需关闭 | | 服务域名 | 自定义 HTTPS 域需把通配 SAN 写进网关证书 | service 暴露后浏览器侧证书校验失败 | | 镜像仓库 | 离线集群需镜像 agent-sandbox 控制器、网关、supervisor、沙箱基础镜像 | 拉取失败,卡在 Pending | | 用户认证 | K8s 下 chart 生成的 mTLS bundle 只用于传输,不用于用户认证 | 直接 `port-forward` 当生产入口,等于没有身份校验 | 最后一行值得多说两句。不少人照文档跑完 `port-forward` 觉得通了,就把它写进内部文档当访问方式。官方写得很清楚,那是本地评估用途;共享环境得接 OIDC 或者一个可信的接入代理,否则谁能碰到端口谁就是管理员。同理,`kubectl get --raw` 抓指标会被 NetworkPolicy 挡住,`port-forward` 走 kubelet 不受策略限制——两个命令在网络策略下的行为并不一样,排障时别拿错结论。 容量账也得提前算。一个沙箱在 K8s 上不是一个 Pod:workload Pod、supervisor Pod、一个提供稳定地址的边界 Service、一块存 `/sandbox` 的 PVC。100 个并发沙箱就是 200 多个 Pod、100 个 Service、100 块卷。集群的 IP 池、PVC 配额、节点密度,按这个数字规划,别按"100 个容器"算。 Docker 和 Podman 这条路更适合单机验证,共享集群终究要落在 Kubernetes 上。选型时有处差异容易被忽略:Kubernetes 把 `--cpu`、`--memory` 同时写成 request 和 limit,调度器预留多少,沙箱就能用多少,没有超卖空间;VM 驱动目前收这两个参数但不改变实际分配,做容量规划时不能照参数记账。 ```yaml replicaCount: 2 workload: kind: deployment server: externalDbSecret: openshell-postgres affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app.kubernetes.io/name: openshell app.kubernetes.io/instance: openshell topologyKey: kubernetes.io/hostname ``` 配套的 PodDisruptionBudget 得单独 apply,`minAvailable: 1`。它只挡自愿驱逐,节点整体失联时不管用——这句是 HA 章节里最容易被读漏的。另外反亲和用 `required` 时,集群里要是没有第二个可用故障域,副本会一直 Pending;宁可先上 `preferred`,也别为了"架构更严格"把控制面卡死。 ## 三、策略工程:默认拒绝之后的四道细化 OpenShell 默认拒绝全部出站,只放行策略里列出的目的地址。从这个起点出发是对的,但默认拒绝只是起跑线,真正决定安全水位的是下面四层。 **二进制身份绑定。** 每条网络规则都要写 `binaries`,只有列出来的可执行文件能到达对应端点。OpenShell 从可信的 `/proc` 观测里认发起请求的真实程序,不看 Agent 自称的路径;授权路径第一次出现时锁定 SHA256(trust on first use),之后摘要不匹配立刻拒绝,证据缺失或冲突也一律 fail closed。命令行里的路径只作诊断,永远不授权。`/**` 这种宽通配等于把这道控制作废,实践中按叶子或启动器收敛,Python 虚拟环境路径不一致时用窄通配,别整个目录放开。还有个操作细节:故意替换掉被授权的可执行文件之后,要重启沙箱运行时,让新进程重新建立 pin,否则你会看到一堆"文件没变却一直被拒"的怪事。 **L7 检查与访问预设。** 不带 `protocol` 字段的端点只校验主机、端口、二进制,HTTP 方法和路径完全放开。想限制意图,得显式写 `protocol: rest` 配 `rules`,或者用 `read-only`(仅 GET/HEAD/OPTIONS)、`read-write`、`full` 预设。GraphQL 端点加 `protocol: graphql`,按操作类型、操作名和根字段判定;WebSocket 用 `protocol: websocket` 检查握手和文本帧;数据库这类原生客户端用 `protocol: tcp`。凭证类端点默认拒绝未检查模式,`allow_uninspected_credentials` 应该当例外用,别当默认。 ```yaml version: 1 filesystem_policy: read_only: [/usr, /etc] read_write: [/sandbox] network_policies: - host: api.github.com port: 443 binaries: [/usr/bin/gh] protocol: rest access: read-only enforcement: enforce rules: - allow: { method: GET, path: /repos/** } ``` **这里有个默认值陷阱,得标红:** L7 开启后 `enforcement` 默认是 `audit`——违反规则只记日志,流量照旧转发。也就是说,你写完一份看着挺严格的 `rules`,上线后可能什么都没拦住,日志里全是 violation,业务一切正常,于是没人再看。正确路径是先在 audit 下跑一周看真实流量、把规则补准,然后显式改成 `enforce`。这条没写进变更单,审计时就会出现"策略存在但形同虚设",比完全没有策略更危险,因为它给你一种已经防住的错觉。 **凭证不出沙箱。** Agent 看不到真实密钥,OpenShell 只在发往已批准端点的请求上注入。这意味着:某个端点一旦被放宽成 `access: full` 又没做 L7 检查,凭证的可用范围就跟着 endpoint 一起扩大。凭证策略和网络策略是同一条战线,不能分开评估。K8s 上还能把凭证改存到独立的 Secret 命名空间(`server.credentialDrivers.kubernetesSecrets`),让网关的 Secret 权限只落在那个命名空间里,比默认存数据库更容易划审计边界。 **全局策略是替换语义。** 网关管理员下发的全局策略会替换掉每个沙箱自有的策略,不是叠在上面;生效期间沙箱策略修改和提案审批都被阻断,provider 贡献的规则也一并抑制。生产环境要启用全局策略,得同步冻结各团队的策略变更流程,否则会出现"改了没生效、也没报错"的排查黑洞。 热更新的副作用也写进变更单吧:新网络规则生效时,旧规则下建立的连接会被主动关闭,包括 HTTP keep-alive、隧道、WebSocket 和长连接流。客户端必须重连。长轮询类的 Agent 任务,这种断连很容易被误判成上游故障。 ## 四、运维清单:把「策略生效」变成可观测事实 OpenShell 输出三类信号:OTLP trace、Prometheus metrics、OCSF 安全事件。用途各不相同,缺一不可。 指标侧至少盯四个:`openshell_server_supervisor_sessions`(本副本持有的控制会话数,rollout 稳定后各 pod 求和应等于在线沙箱数)、`openshell_server_relay_pending`(等 supervisor 回连的中继通道,单副本上限 256)、`openshell_server_relay_rejected_total`(按 reason 分桶),再加数据库就绪和 API 请求指标。多副本部署必须逐 pod 抓取并保留 `namespace`、`pod` 标签,直方图跨副本可以聚合,summary 的 quantile 不能平均——Prometheus 的老规矩了,但容量告警里还是常写错。 几个阈值来自文档,不是拍脑袋:中继通道 10 秒内没被 supervisor 领取就记为 expired,并且不再计入 claim 时长直方图;mutation lock 冲突等待超过 10 秒即超时,网关同时打一条 `mutation lock held longer than the lock wait timeout`。所以告警这么配——`relay_rejected_total` 有任何增长就报(它直接等于客户端请求失败),`relay_expired_total` 有任何增长也报(说明 supervisor 或它所在节点有问题,不是容量不够),`mutation_lock_timeouts_total` 有任何增长就报(多半是 PostgreSQL 连接被打满)。`relay_pending` 反而只适合画图,它天然在 0 附近抖,拿它做告警迟早疲劳。 日志侧把 OCSF JSONL 落到文件,交给采集器转发,沙箱侧要显式 `openshell settings set --global --key ocsf_json_enabled --value true`;网关侧也可以直接用 `OPENSHELL_OCSF_JSON=1` 打开审计 sink。判断策略是不是真的在拦,就看这条: ```text [1791996125.789] [sandbox] [OCSF ] [ocsf] NET:OPEN [MED] DENIED /usr/bin/curl(2187) -> github.com:443 [reason:transparent_tcp_policy_denied] ``` 变更流程固化三步:`policy update --dry-run` 预览 → `--wait` 应用并等待加载 → `policy list` 确认 revision 被沙箱接收。预览不保证沙箱接受,别把 dry-run 结果当验收结论。 上线前用独立命令 `openshell-prover` 在 CI 里校验策略没超出既定边界,把「新增凭证可达」「新增 HTTP 方法」「访问云元数据端点」三类发现项设成阻断。但对它的覆盖面要有清醒认识:目前只建模 filesystem、network_l4、network_rest、process、landlock 五个域;碰到 GraphQL、MCP、JSON-RPC 规则会返回 `unsupported` 而不是放行;REST 端点如果还在 `audit` 模式同样返回 `unsupported`(它不拦请求,证明没有意义);网络规则超过 1,024 条或端点超过 4,096 个会返回 `inconclusive`。退出码 0 才算通过,1/2/3 全部按失败处理。还有个特别实际的限制:candidate 写 `/tmp/cache`、boundary 写 `/tmp`,结果是 `unsupported` 而不是通过——因为镜像里的软链接可能让前者指向后者之外。**想让证明跑得动,两边就写同样的路径。** 日常巡检其实能压成一张表,按节奏执行就行: | 节奏 | 动作 | 判定标准 | |---|---|---| | 每日 | 看 `relay_rejected_total`、`relay_expired_total`、`mutation_lock_timeouts_total` 增量 | 必须为 0 | | 每日 | 抽样 10 条 OCSF DENIED 记录,核对是否都属于预期拒绝 | 出现非预期拒绝即查策略 | | 每周 | 各 pod `supervisor_sessions` 求和 vs 在线沙箱数 | 两者相等,偏差即孤儿会话 | | 每周 | `openshell sandbox get <name> --policy-only` 导出实际策略,与基线 diff | 无未审批差异 | | 每次变更 | prover 边界检查 + audit→enforce 切换确认 | 退出码 0 且 enforcement 字段为 enforce | | 每季度 | 断网演练:切断 supervisor 通道,确认 Agent 冻结而非继续跑 | 冻结符合 fail closed | 升级这边注意顺序:就地升级 Agent Sandbox 后,网关会缓存旧的 API 版本判断,必须重启网关进程重新探测;已有沙箱在升级期间继续跑,不会中断。这条不写进 runbook,通常会在升级后第一批沙箱创建失败时才被发现。另一个坑是镜像版本:`supervisor.image` 和 `sandboxRuntime.image` 必须同一版本,混搭会在沙箱启动阶段失败,而报错只出现在 supervisor 日志里,翻网关日志什么也找不着。 ## 五、典型故障的代价评估 | 现象 | 根因 | 处置 | |---|---|---| | 沙箱长期停在 Provisioning | 镜像内 `/etc/openshell/policy.yaml` 非法,OpenShell 不回退默认策略 | 替换为合法策略或改用 `--policy` | | Agent 突然全部冻结 | supervisor 通道中断触发 fail closed | 检查网关可达性与中继容量,而非放宽策略 | | 修改文件系统权限无效 | 该段仅创建时生效 | 重建沙箱,或改走 `/sandbox` 持久卷 | | 规则写了却照样放行 | `enforcement` 停留在默认的 `audit` | 显式改为 `enforce` 并回归验证 | | prover 报通过但线上越权 | 策略里含 GraphQL/MCP 规则,落进 `unsupported` 之外的覆盖盲区 | 检查 `coverage.domains` 与 `reason_code`,不要只看退出码 | | 多副本下 exec 偶发超时 | 非属主副本经 peer 路由转发,属主切换窗口内请求失败 | 确认 5 秒优雅交接日志,排查 PDB 与反亲和 | | 升级后创建沙箱失败 | Agent Sandbox API 版本缓存 | 重启网关 | | 某内网服务一直连不上 | 客户端做了证书 pinning,被临时 CA 终止后校验失败 | 单列 `tls: skip` 端点或移出可达范围 | 成本这块也得算清:软件本身 Apache 2.0,没有授权费,钱花在三个地方——承载沙箱的计算资源(K8s 下 request 等于 limit,无超卖)、外置 PostgreSQL(HA 的硬依赖,而且网关副本不给数据库做 HA)、以及带 `--gpu` 请求时的加速卡占用。GPU 和 user namespace 同开的兼容性官方标的是"未验证",日志会给警告,GPU 集群上别顺手打开 `enable_user_namespaces`;user namespace 本身门槛也不低,要 K8s 1.33+(1.36 才 GA)、containerd 2.0+ 或 CRI-O 1.25+、内核 5.12+。 团队要是自建整机,硬件这边的采购和议价往往比软件配置更费时间。华南的整机渠道(华强北一带的服务器和配件供应链)在 GPU 整机和二手机架上选择多、价差也大,但同型号的卡在不同批次固件和驱动版本上可能对不上——上生产前统一固件版本,比多谈两个点价格更能省掉后面的事故。 ## 六、灰度路径:四步把权限收紧,而不是一次放开 多数团队栽跟头不是因为策略写错,而是从"全开"直接跳到"全拒",中间没有过渡数据,规则只能靠猜。可行的顺序是四步。 **第一步,单机 Docker 跑通。** 官方脚本装 CLI 和本地网关,起一个空沙箱,先确认 `openshell sandbox create` 能成功、OCSF 日志能落盘。这步只验证链路,不谈安全。 **第二步,影子模式收数据。** 把真实 Agent 放进去,策略写最小集,L7 全部留在 `audit`。跑一到两周,从 DENIED 日志里反推它到底访问了哪些主机、哪些路径、用了哪些方法。这步的产出不是策略,是一份「真实行为清单」。 **第三步,转 enforce 并做破坏性验证。** 逐条把关键端点切成 `enforce`,一次只切一条,切完立刻用一条预期被拒的请求验证——比如故意让 Agent 访问 `169.254.169.254`,确认它拿到的是拒绝而不是超时。切完一周没异常,再切下一条。 **第四步,接 prover 与审批。** 边界策略写进仓库,CI 里跑 `openshell-prover check`,任何 `exceeds_boundary` 阻断合并;同时把「新增凭证可达」列为必须人工复核的项。到这一步,"谁能给 Agent 加一条外网通路"才变成一个可审计的问题,而不是某人在群里说一句就改了。 四步加起来通常四到六周。听着慢,但比起事故后回滚、写报告、向客户解释数据去了哪里,这个时间成本几乎可以忽略。 ## 七、趋势判断 Agent 运行时的竞争焦点,正在从"支持多少模型、多少工具"转向"能不能给出可验证的权限证明"。OpenShell 把形式化验证放进策略审批环节——变更前先算出它会新授予哪些访问,风险项阻断自动批准——这个方向值得看,因为它把安全从"事后审计日志"前移到了"事前证明"。不过证明本身也有边界:SMT 求解器只对它建模过的策略语义负责,超出模型的部分会老老实实报 `unsupported`,这比假装通过要好,但也意味着"prover 绿了"不等于"策略安全",只等于"在你建模的那部分里没越界"。 0.1.x 阶段还是明显粗糙:561 个未关闭 issue、VM 驱动不落实 CPU/内存参数、user namespace 与 GPU 的组合待验证、GraphQL 和 MCP 规则还进不了证明。它适合当平台团队的统一执行层来评估,不适合当开箱即用的成品。 真正决定这套运行时能不能在生产站住的,不是安装脚本,而是那份「默认拒绝 + 二进制绑定 + L7 收敛 + 凭证端点绑定」的策略基线,以及围着它长出来的巡检、演练和审批习惯。工具给了内核级的执行力,剩下的是组织纪律问题——而纪律这件事,从来没法靠一个 Helm chart 解决。 你们团队现在给 Agent 开了多少条出站通路?有哪条是能说清楚"谁批的、为什么批"的,不妨在评论区聊聊。 快速回复 回复 使用道具 举报