//! 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---`, 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 { [ // 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=::` 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 { let mut args: Vec = [ "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 { let mut args: Vec = [ "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 }