7.6 KiB
| name | description | version | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| sandboxed-deployment | Isolate multi-user deployments without root. bwrap sandbox. | 1.0.0 |
|
沙箱化部署隔离(零 root)
为多用户提供隔离的部署/运行环境,全程零 root。核心模式:逻辑账号(目录 + DB 映射,不建真实 Linux 账号)+ bwrap 沙箱(unprivileged user namespace)。
先问清楚需求本质(决定要不要 root)
动手前必须先确认:隔离的目的是「给人 SSH 登录」还是「给部署进程一个隔离环境」?
- 要真实 Linux 账号(人 SSH 登录 / 独立 uid / 磁盘配额)→ 需要 root
useradd。但 root 能力应放在独立最小权限服务里(只暴露固定动作),不要给 web 服务 sudo 免密。 - 只要隔离部署(部署进程在隔离环境里跑)→ 逻辑隔离:目录 + DB 记录 + bwrap,全程零 root。
为什么不要给 web 服务 sudo 免密
给运行 LLM 生成命令的 web 服务 sudo 免密(哪怕命令白名单)不安全:
- 一旦被 RCE 或 prompt 注入,sudo = 直接 root。
- 白名单可被绕过:
useradd -o -u 0 attacker直接造 uid=0 账号;usermod/chown/setfacl链式提权。
替代:逻辑隔离(零 root)或独立 root systemd 服务(unix socket 调用,只接受「创建账号/删账号」固定动作,不接受任意命令)。
bwrap 零 root 获取(无需 apt install,无需 sudo)
apt download bubblewrap # 普通用户可下载 .deb,无需 root
dpkg -x bubblewrap*.deb /tmp/x # 解包提取二进制,无需 root
cp /tmp/x/usr/bin/bwrap /path/to/bin/bwrap
动态库(libcap/libseccomp)系统通常已自带(ldd bwrap 验证无 "not found")。
前提检查:unprivileged user namespace 必须启用,否则 bwrap 不可用:
cat /proc/sys/kernel/unprivileged_userns_clone # =1 表示启用
unshare -Ur echo 'userns OK' # 能输出则可用
bwrap 沙箱参数模板
隔离 user/pid/ipc/uts namespace(保留网络,部署应用通常要绑定端口/访问外部),只读挂载系统目录,可写挂载用户部署目录,tmpfs /tmp。完整模板见 references/bwrap-template.md。
关键点:
- 用
--unshare-user --unshare-pid --unshare-ipc --unshare-uts(不是--unshare-all,后者会--unshare-net把网络也隔离掉)。 --setenv HOME /home必须加:否则沙箱内 HOME 继承宿主机路径(如/d/pipeline),而该路径在沙箱内不存在,应用cd $HOME会失败。- 执行用
create_subprocess_exec(*cmd)传列表(非 shell 字符串),防命令注入。
逻辑隔离账号模式
账号名 ag_<username> ← 用户名需 sanitize([^a-zA-Z0-9_-] → _),防路径穿越
目录 /base/deploy_envs/ag_<username>/{home,workspace,data,logs}
DB 记录 sd_deploy_accounts(id, user_id, account_name, deploy_dir, status, sandbox_config)
- 「免密访问」在逻辑隔离下 = 部署进程直接文件系统读写(无 SSH/密码),天然免密。
- 「超级用户权限自动开启」在零 root 下 = bwrap user namespace 内 root-like 映射(沙箱内 uid 0,映射回宿主机是普通用户)。
- 文件读写/列表必须做路径穿越校验:拒绝绝对路径、拒绝
..,并os.path.abspath后校验startswith(deploy_dir + "/")。
防逃逸验证(每次都要实测,别口头声称)
# 沙箱内执行:
touch /etc/test_escape # → "Read-only file system"(只读系统目录生效,防逃逸)
echo $$ # → PID 从 1 开始(PID namespace 生效)
ls / # → 只显示挂载的目录(文件系统隔离生效)
ls /home # → 显示部署目录内容(可写目录正确挂载)
sudo -n id # → "no new privileges" 报错(sudo 提权被自动阻断)
bwrap 自动阻断 sudo 提权(关键安全机制):--unshare-user 的 user namespace 会设置 no_new_privileges 标志,setuid 程序(sudo 等)无法提权。实测沙箱内 sudo 直接报:
sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
所以即使宿主用户(如 pipeline)在 sudo 组里,沙箱内进程也无法通过 sudo 提权。给陌生用户开 Wterm/终端时,这是防提权的第一道自动防线,无需额外配置。
bwrap 一键部署集成
把 bwrap 获取放进 build.sh(零 root),start.sh 把 bin 目录加到 PATH,服务代码用 shutil.which("bwrap") 检测:
# build.sh:系统已有则复用,否则 apt download + dpkg -x 到 $cdir/bin/
if [ ! -x "$cdir/bin/bwrap" ]; then
if command -v bwrap >/dev/null 2>&1; then
cp "$(command -v bwrap)" "$cdir/bin/bwrap"
else
tmpd=$(mktemp -d)
( cd "$tmpd" && apt download bubblewrap >/dev/null 2>&1 && dpkg -x bubblewrap*.deb . && cp usr/bin/bwrap "$cdir/bin/bwrap" )
rm -rf "$tmpd"
fi
fi
# start.sh:export PATH="$WORKDIR/bin:$PATH"(让 python 进程 shutil.which 能找到)
远程主机沙箱(免密 SSH + 远程 bwrap)
沙箱可切到远程主机:ssh -i key user@host 在远程同样跑 bwrap。安全校验必须过一遍(audit 踩过的坑):
- 每机构独立 key:不要所有机构共用一个私钥(泄露一个 = 全崩,且 pipeline 是跑 LLM 命令的高风险用户)。
ssh-keygen -t ed25519 -N "" -f ~/.ssh/org_keys/<org_id>/id_ed25519每机构一把。 - host key 校验:
-o StrictHostKeyChecking=accept-new(首次记录 host key 后严格校验,防 MITM),不要=no——=no等于把机构数据 rsync 给任意伪装主机。 - remote_dir 禁止系统目录:迁移用
rsync --delete,remote_dir 设成/或/etc会删远程系统文件。黑名单 + 前缀检查;注意.rstrip("/")会把"/"变成空串绕过黑名单,要先判空。 - remote_key_path 限制
~/.ssh/下:允许任意绝对路径 = 能读服务器上任意私钥(git deploy key 等)去认证别的主机。 - host/user/port 正则白名单:防
evil.com;rm -rf、root;rm注入;port 要int()加 try/except(传"abc"会 500)+ 范围 1-65535。 - 机构级配置需权限校验:org 级 remote_host 仅
owner.superuser可设(查 userrole),否则任何登录用户都能把整个机构内容迁移到攻击者服务器。 - 迁移失败状态一致:先迁移成功再写 DB,失败不回写 mode(否则 mode 已切但内容没迁,状态分裂)。
SSH 命令构造:用 create_subprocess_exec(*["ssh", ...]) 列表传参(非 shell 拼接);远程 bwrap 路径探测 BW=$(command -v bwrap || echo "$HOME/bin/bwrap"); exec "$BW" ...(远程 SSH 会话 PATH 不含自定义 bin)。
Pitfalls
- 不要
--unshare-all:它会同时--unshare-net,隔离网络后 web 应用无法监听端口/访问外部,部署应用基本都要网络。 --setenv HOME /home必加:漏了会导致沙箱内 HOME 指向不存在的宿主机路径。- bwrap 未安装时降级:代码里
BWRAP = shutil.which("bwrap")为 None 时降级为「目录 + chdir」弱隔离(sandbox: false标记),不要直接报错。多路径兜底:PATH →BWRAP_PATH环境变量 → 已知安装位置。 - 账号名必须 sanitize:username 可能含中文/特殊字符,目录名只保留
[a-zA-Z0-9_-],防路径穿越。 - 路径穿越校验:所有文件读写/列表操作都要
abspath后校验在 deploy_dir 内,拒绝..和绝对路径。