forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
2.5 KiB
1//! Rootless job sandbox. Each job runs in a libcontainer container, started by a helper process.
2//!
3//! The runner never calls libcontainer itself. libcontainer forks and clones, which is unsafe in
4//! a multi-threaded tokio process, so the runner spawns `forge-runner __sandbox` as a
5//! single-threaded child. That child reads a [`SandboxRequest`] on stdin and runs the job. See
6//! `docs/ci.md`.
7
8pub mod exec;
9pub mod helper;
10pub mod idmap;
11pub mod seccomp;
12pub mod spec;
13
14use std::path::PathBuf;
15
16use serde::{Deserialize, Serialize};
17
18use crate::config::Limits;
19use idmap::IdMap;
20
21pub use helper::{helper_main, job_cgroup_path};
22pub use spec::build_spec;
23
24/// What the runner sends the helper on stdin. It carries the secrets, so it is never written to
25/// disk and never placed in argv. It deliberately has no `Debug` impl, so the env cannot leak
26/// into a log line.
27#[derive(Serialize, Deserialize)]
28pub struct SandboxRequest {
29 pub job_id: String,
30 /// The unpacked image rootfs. It is the overlay `lowerdir`, or the copy source.
31 pub rootfs: PathBuf,
32 /// Overlay upper and work dirs, the bundle, and container state. Deleted after the job.
33 pub job_dir: PathBuf,
34 /// The job workspace, bind mounted rw at `/workspace`.
35 pub workspace: PathBuf,
36 /// The image config `Env` entries as `KEY=VALUE`. They come before the job env.
37 #[serde(default)]
38 pub image_env: Vec<String>,
39 /// The job env, then the secrets, as name/value pairs in the order they are set.
40 pub env: Vec<(String, String)>,
41 /// The job's `run` script, executed as `sh -eu -c <run>`.
42 pub run: String,
43 /// `false` gives the job an empty network namespace. `true` shares the host's.
44 pub network: bool,
45 pub limits: Limits,
46 /// The id mapping the helper enters before anything else. Missing means single mode.
47 #[serde(default)]
48 pub idmap: IdMap,
49}
50
51/// Errors from building or running a sandbox.
52#[derive(Debug, thiserror::Error)]
53pub enum SandboxError {
54 #[error("invalid sandbox request: {0}")]
55 Invalid(String),
56 #[error("{context}: {source}")]
57 Io {
58 context: String,
59 #[source]
60 source: std::io::Error,
61 },
62 #[error("{0}")]
63 Setup(String),
64}
65
66impl SandboxError {
67 /// Wraps an I/O error with a short description of what was being done.
68 pub fn io(context: impl Into<String>) -> impl FnOnce(std::io::Error) -> Self {
69 let context = context.into();
70 move |source| Self::Io { context, source }
71 }
72}