//! Rootless job sandbox. Each job runs in a libcontainer container, started by a helper process. //! //! The runner never calls libcontainer itself. libcontainer forks and clones, which is unsafe in //! a multi-threaded tokio process, so the runner spawns `forge-runner __sandbox` as a //! single-threaded child. That child reads a [`SandboxRequest`] on stdin and runs the job. See //! `docs/ci.md`. pub mod exec; pub mod helper; pub mod idmap; pub mod seccomp; pub mod spec; use std::path::PathBuf; use serde::{Deserialize, Serialize}; use crate::config::Limits; use idmap::IdMap; pub use helper::{helper_main, job_cgroup_path}; pub use spec::build_spec; /// What the runner sends the helper on stdin. It carries the secrets, so it is never written to /// disk and never placed in argv. It deliberately has no `Debug` impl, so the env cannot leak /// into a log line. #[derive(Serialize, Deserialize)] pub struct SandboxRequest { pub job_id: String, /// The unpacked image rootfs. It is the overlay `lowerdir`, or the copy source. pub rootfs: PathBuf, /// Overlay upper and work dirs, the bundle, and container state. Deleted after the job. pub job_dir: PathBuf, /// The job workspace, bind mounted rw at `/workspace`. pub workspace: PathBuf, /// The image config `Env` entries as `KEY=VALUE`. They come before the job env. #[serde(default)] pub image_env: Vec, /// The job env, then the secrets, as name/value pairs in the order they are set. pub env: Vec<(String, String)>, /// The job's `run` script, executed as `sh -eu -c `. pub run: String, /// `false` gives the job an empty network namespace. `true` shares the host's. pub network: bool, pub limits: Limits, /// The id mapping the helper enters before anything else. Missing means single mode. #[serde(default)] pub idmap: IdMap, } /// Errors from building or running a sandbox. #[derive(Debug, thiserror::Error)] pub enum SandboxError { #[error("invalid sandbox request: {0}")] Invalid(String), #[error("{context}: {source}")] Io { context: String, #[source] source: std::io::Error, }, #[error("{0}")] Setup(String), } impl SandboxError { /// Wraps an I/O error with a short description of what was being done. pub fn io(context: impl Into) -> impl FnOnce(std::io::Error) -> Self { let context = context.into(); move |source| Self::Io { context, source } } }