Files
boxmaker/crates/brokerd/src/podman.rs
T
kyleandClaude Opus 5.5 c3aaecdae2 M3b review fixes by the design model: curl --globoff, podman --pull=never
Review findings 1 and 2, both plan defects. curl gains --globoff and a
leading --disable; both podman runs gain --pull=never. The given fetch.rs and
the six golden files change with them. Checked on straylight with a rebuilt
image: a glob URL is one request, a missing image fails at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:25:51 -07:00

113 lines
3.7 KiB
Rust

//! The `podman` argument lists for one call's container and for the `http_fetch` egress proxy.
//!
//! Nothing is passed through a shell: every argument is its own `OsString`, and the tool's
//! arguments go on standard input, never on the command line. Built without running Podman, so a
//! runtime that only builds the list can be tested as a golden file. Spec section 6.
use crate::config::Runner;
use crate::runner::RunSpec;
use proto::{CallId, SessionId};
use std::ffi::OsString;
use std::path::Path;
pub const EGRESS_MOUNT: &str = "/run/egress";
pub const EGRESS_SOCKET: &str = "/run/egress/egress.sock";
pub const TOOLKIT: &str = "/bin/toolkit";
/// The container's name: `boxmaker-<session>-<call>-<n>`, so it says whose call it is.
pub fn container_name(session: &SessionId, call: CallId, n: u64) -> String {
format!("boxmaker-{}-{}-{}", session.as_str(), call.0, n)
}
/// The six hardening flags, common to the tool and the proxy. `pids` and `memory` differ: the tool
/// takes the runner's, the proxy its fixed limits.
fn hardening(pids: u32, memory: &str) -> Vec<OsString> {
[
// A missing image is an error at once, never a pull: a pull is egress, and what runs must
// be exactly the image built for it.
"--pull=never",
"--read-only",
"--cap-drop=all",
"--security-opt=no-new-privileges",
"--userns=keep-id",
]
.map(OsString::from)
.into_iter()
.chain([
OsString::from(format!("--pids-limit={pids}")),
OsString::from(format!("--memory={memory}")),
])
.collect()
}
/// A `--volume=<host>:<container>:<mode>` argument, built with `push` so a directory need not be
/// UTF-8.
fn volume(host: &Path, container: &Path, mode: &str) -> OsString {
let mut arg = OsString::new();
arg.push("--volume=");
arg.push(host);
arg.push(":");
arg.push(container);
arg.push(":");
arg.push(mode);
arg
}
/// The tool's container. `egress` is the call's egress directory, for `http_fetch` only.
pub fn tool_args(
spec: &RunSpec,
runner: &Runner,
name: &str,
egress: Option<&Path>,
) -> Vec<OsString> {
let mut args: Vec<OsString> = [
"run",
"--rm",
"-i",
&format!("--name={name}"),
"--label=boxmaker=tool",
"--network=none",
]
.map(OsString::from)
.into_iter()
.collect();
args.extend(hardening(runner.pids, &runner.memory));
args.push(OsString::from("--tmpfs=/tmp:rw,size=64m,mode=1777"));
for mount in spec.mounts() {
let mode = if mount.writable { "rw" } else { "ro" };
args.push(volume(Path::new(&mount.path), Path::new(&mount.path), mode));
}
if let Some(dir) = egress {
args.push(volume(dir, Path::new(EGRESS_MOUNT), "rw"));
}
args.push(OsString::from(runner.image.as_str()));
args.push(OsString::from(TOOLKIT));
args.push(OsString::from(spec.tool().as_str()));
args
}
/// The egress proxy's container.
pub fn egress_args(runner: &Runner, name: &str, dir: &Path, hosts: &[String]) -> Vec<OsString> {
let mut args: Vec<OsString> = [
"run",
"-d",
"--rm",
&format!("--name={name}-egress"),
"--label=boxmaker=egress",
&format!("--network={}", runner.egress_network),
]
.map(OsString::from)
.into_iter()
.collect();
args.extend(hardening(64, "128m"));
args.push(volume(dir, Path::new(EGRESS_MOUNT), "rw"));
args.push(OsString::from(runner.image.as_str()));
args.push(OsString::from(TOOLKIT));
args.push(OsString::from("egress-proxy"));
args.push(OsString::from("--socket"));
args.push(OsString::from(EGRESS_SOCKET));
args.push(OsString::from("--allow"));
args.push(OsString::from(hosts.join(",")));
args
}