From cacd4436ceddbae62a263ec5a727901bff7c4a99 Mon Sep 17 00:00:00 2001 From: wunianxiao Date: Mon, 22 Jun 2026 11:49:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(sandbox):=20builder=20=E5=AE=B9=E5=99=A8=20?= =?UTF-8?q?--user=20uid:gid=20+=20=E7=A6=BB=E7=BA=BF=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=20cap-drop=20=E5=86=99=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 步骤 9: tool_manager build 迁移到 builder 容器验证通过。 问题: --cap-drop ALL 丢掉 CAP_DAC_OVERRIDE,容器内 root 无法写 bind mount 的非 root 目录(宿主 xiao:1000)。 这是 cap-drop 正确生效的安全行为,非 bug。 修复: - builder 容器 --user uid:gid 以宿主用户身份运行,自然能写自己 的目录,同时保留 cap-drop ALL + no-new-privileges。 - Cargo.toml 改 CARGO_HOME=/tmp/cargo + chmod 777,使任意 uid 都能复用 serde_json 预缓存。 - cargo build --offline + CARGO_NET_OFFLINE=true,配合 --network none 真正离线编译,杜绝恶意代码下载依赖。 端到端: tool_manager create_tool → builder 容器离线编译 → 产物运行成功,审计日志记录正确。 --- docker/Dockerfile.tool-builder | 5 ++++- tools/tool_manager/src/main.rs | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.tool-builder b/docker/Dockerfile.tool-builder index 577138d..9b73538 100644 --- a/docker/Dockerfile.tool-builder +++ b/docker/Dockerfile.tool-builder @@ -1,10 +1,13 @@ # ias-tool-builder — tool_manager 编译 Rust 工具的隔离环境 # 预缓存 serde_json(tool_manager 生成工具的唯一依赖),支持 --network none 离线编译。 +# CARGO_HOME=/tmp/cargo 并 chmod 777,使任意 uid(--user 运行)都能复用缓存。 # 运行时由 tool_manager 挂载 tool_dir 到 /work,cargo 在 /work 内编译,产物写回宿主。 FROM rust:1.88-slim +ENV CARGO_HOME=/tmp/cargo RUN cargo new /tmp/seed && cd /tmp/seed \ && printf 'serde_json = "1.0"\n' >> Cargo.toml \ && cargo build --release \ - && rm -rf /tmp/seed + && rm -rf /tmp/seed \ + && chmod -R 777 /tmp/cargo WORKDIR /work ENTRYPOINT [] diff --git a/tools/tool_manager/src/main.rs b/tools/tool_manager/src/main.rs index 2df91c6..6e26360 100644 --- a/tools/tool_manager/src/main.rs +++ b/tools/tool_manager/src/main.rs @@ -126,6 +126,7 @@ fn build_tool(tool_dir: &Path, tool_name: &str) -> Result<(), String> { .ok() .as_deref() == Some("1"); + let (uid, gid) = current_uid_gid(); // 启用 builder 容器时在隔离环境内离线编译,挂载 tool_dir 到 /work,产物写回宿主; // 否则本地 cargo(受 timeout 限制) @@ -138,11 +139,14 @@ fn build_tool(tool_dir: &Path, tool_name: &str) -> Result<(), String> { .arg("--pids-limit").arg("128") .arg("--cap-drop").arg("ALL") .arg("--security-opt").arg("no-new-privileges") + .arg("--user").arg(format!("{}:{}", uid, gid)) .arg("-v").arg(format!("{}:/work", tool_dir.display())) .arg("-w").arg("/work") + .arg("-e").arg("CARGO_NET_OFFLINE=true") + .arg("-e").arg("CARGO_HOME=/tmp/cargo") .arg("ias-tool-builder") .arg("timeout").arg(BUILD_TIMEOUT_SECS.to_string()) - .arg("cargo").arg("build").arg("--release") + .arg("cargo").arg("build").arg("--release").arg("--offline") .output() } else { Command::new("timeout") @@ -309,6 +313,21 @@ fn truthy(value: Option<&Value>) -> bool { } } +/// 获取当前进程的 uid:gid,用于 builder 容器 --user,使容器内进程能写 bind mount 的宿主目录 +fn current_uid_gid() -> (String, String) { + let uid = String::from_utf8_lossy( + &Command::new("id").arg("-u").output().map(|o| o.stdout).unwrap_or_default(), + ) + .trim() + .to_string(); + let gid = String::from_utf8_lossy( + &Command::new("id").arg("-g").output().map(|o| o.stdout).unwrap_or_default(), + ) + .trim() + .to_string(); + (uid, gid) +} + /// 追加审计日志到 tools 根目录下的 .tool_audit.log fn audit( tools_root: &Path,