123 lines · 3.9 KB · Rust
RawDownload
#![allow(clippy::disallowed_methods, reason = "tooling is exempt")] use std::process::Command; use anyhow::{Context as _, Result, bail};use clap::Parser; /// Runs the Linux Bubblewrap sandboxing NixOS VM tests (see `nix/tests/sandboxing`).////// Each test boots a real kernel under QEMU to exercise bwrap behavior across/// host configurations: a working host (the sandbox must be enforced and the/// full fs x network policy matrix must hold), and degraded hosts (no bwrap,/// setuid-only bwrap, user namespaces disabled) where `Sandbox::can_create`/// must report the specific failure and the consumer must fail closed. They/// therefore only run on Linux with `/dev/kvm` available, and require `nix`/// with flakes enabled.#[derive(Parser)]pub struct SandboxTestsArgs {    /// Names of specific tests to run (e.g. `sandbox-working`). Defaults    /// to all.    tests: Vec<String>,     /// Nix system to build for. Defaults to the host system.    #[arg(long)]    system: Option<String>,     /// Force already-built tests to re-run instead of returning the cached    /// result (passes `--rebuild` to `nix build`).    ///    /// This only works once a test has been built at least once; after changing    /// the test code the derivation changes, so run without `--rebuild` first.    #[arg(long)]    rebuild: bool,     /// List the available tests and exit without running anything.    #[arg(long)]    list: bool,} /// Tests in `nix/tests/sandboxing` are exposed as flake checks with this prefix.const TEST_PREFIX: &str = "sandbox-"; pub fn run_sandbox_tests(args: SandboxTestsArgs) -> Result<()> {    let system = args.system.unwrap_or_else(host_system);    let available = available_tests(&system)?;     if args.list {        for name in &available {            println!("{name}");        }        return Ok(());    }     let selected = if args.tests.is_empty() {        available    } else {        for name in &args.tests {            if !available.contains(name) {                bail!(                    "unknown sandbox test {name:?}; available: {}",                    available.join(", ")                );            }        }        args.tests    };     if selected.is_empty() {        bail!("no sandbox tests found for system `{system}`");    }     let mut command = Command::new("nix");    command.args(["build", "-L", "--keep-going"]);    if args.rebuild {        command.arg("--rebuild");    }    for name in &selected {        command.arg(format!(".#checks.{system}.{name}"));    }     eprintln!("Running sandbox tests: {}", selected.join(", "));    let status = command.status().context("failed to run `nix build`")?;    if !status.success() {        bail!("sandbox tests failed");    }    Ok(())} /// The Nix system double for the host. These tests are Linux-only, so we always/// target a `*-linux` system (overridable via `--system`).fn host_system() -> String {    format!("{}-linux", std::env::consts::ARCH)} /// Query the flake for the names of the sandbox-test checks, so the task stays/// in sync with `nix/tests/sandboxing` without a hardcoded list.fn available_tests(system: &str) -> Result<Vec<String>> {    let output = Command::new("nix")        .args([            "eval",            &format!(".#checks.{system}"),            "--apply",            "checks: builtins.concatStringsSep \"\\n\" (builtins.attrNames checks)",            "--raw",        ])        .output()        .context("failed to run `nix eval` to list checks (is `nix` installed?)")?;    if !output.status.success() {        bail!(            "`nix eval` failed: {}",            String::from_utf8_lossy(&output.stderr).trim()        );    }     let names = String::from_utf8(output.stdout).context("`nix eval` output was not UTF-8")?;    let mut tests: Vec<String> = names        .lines()        .filter(|name| name.starts_with(TEST_PREFIX))        .map(str::to_string)        .collect();    tests.sort();    Ok(tests)}