gunnar-server 4.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! **Gunnar as a Heimdall user** (Heimdall milestone 5, feature `heimdall`).
//!
//! A pack this box FETCHES (a mirror import or sync) is bytes an upstream
//! chose, and a pack a client PUSHES is bytes the pusher chose. With the gate
//! installed, [`crate::mirror_fetch`] and the receive-pack path in
//! [`crate::transport`] hand every such pack to [`gunnar_heimdall::PackGate`]
//! first: the pack is parsed by the
//! static `gunnar-pack-check` inside a Heimdall container (heimdall-container's
//! protected runner: empty netns, read-only root, Landlock, seccomp, cgroup
//! limits) and reaches the ingest only when the signed result checks out.
//!
//! # Switching it on
//!
//! `GUNNAR_HEIMDALL_CHECKER=<path of the static gunnar-pack-check>`. At start
//! the server writes the checker image into `<data>/heimdall/layout` (the
//! digest is the binary's, deterministic) and starts the runner (its shim:
//! `HEIMDALL_CONTAINER_SHIM`, or `heimdall-container-shim` next to the server).
//!
//! # Fail closed
//!
//! A gate that is asked for and cannot start (no user namespaces, no
//! Landlock, a missing checker or shim) is NOT skipped: it is installed as a
//! closed gate whose runner refuses every job with the reason, so every
//! fetched or pushed pack is refused by name and the log says why at start.

use std::path::Path;
use std::sync::Arc;

use gunnar_heimdall::PackGate;
use heimdall_container::{HeimdallConfig, HeimdallRunner};
use heimdall_core::{
    BootKey, Features, Job, JobRunner, Protection, RunError, SignedResult, VerifyingKey,
};

use crate::stores::ServerStores;

/// The environment variable that switches the gate on.
pub const CHECKER_ENV: &str = "GUNNAR_HEIMDALL_CHECKER";

/// A runner that refuses every job: the gate was asked for and could not start.
struct Closed {
    why: String,
    key: BootKey,
}

impl JobRunner for Closed {
    fn run(&self, _job: Job) -> Result<SignedResult, RunError> {
        Err(RunError::Setup {
            reason: format!("the Heimdall gate did not start: {}", self.why),
        })
    }
    fn verifying_key(&self) -> VerifyingKey {
        self.key.verifying_key()
    }
    fn features(&self) -> Features {
        Features {
            runner: "closed".into(),
            container: false,
            seccomp: Protection::None,
            landlock: false,
            broker: false,
            wasm: false,
            image_verified_each_start: false,
        }
    }
}

/// The gate for the checker at `checker`, under `<data>/heimdall`.
pub fn build(data: &Path, checker: &Path) -> Result<PackGate, String> {
    let base = data.join("heimdall");
    let layout = base.join("layout");
    let image = gunnar_heimdall::image::write_checker_image(
        &layout,
        checker,
        gunnar_heimdall::image::native_oci_arch(),
    )
    .map_err(|e| format!("writing the checker image from {}: {e}", checker.display()))?;
    let runner = HeimdallRunner::new(HeimdallConfig::new(&layout, base.join("work")))
        .map_err(|e| e.to_string())?;
    Ok(PackGate::new(
        Arc::new(runner),
        image,
        base.join("spool"),
        gix_hash::Kind::Sha1,
    ))
}

/// The closed gate (every pack refused with `why`).
pub fn closed(data: &Path, why: String) -> PackGate {
    let image = heimdall_core::ImageRef {
        name: gunnar_heimdall::image::IMAGE_NAME.into(),
        digest: format!("sha256:{}", "0".repeat(64)),
    };
    let runner = Closed {
        why,
        key: BootKey::generate(),
    };
    PackGate::new(
        Arc::new(runner),
        image,
        data.join("heimdall/spool"),
        gix_hash::Kind::Sha1,
    )
}

/// At start: install the gate when `GUNNAR_HEIMDALL_CHECKER` asks for it.
pub fn install(stores: &ServerStores) {
    let Some(checker) = std::env::var_os(CHECKER_ENV) else {
        return;
    };
    let gate = match build(stores.root(), Path::new(&checker)) {
        Ok(g) => {
            tracing::info!(target: gunnar_core::observe::TARGET_HEIMDALL, "Heimdall pack gate on: every fetched pack is checked in a Heimdall job first");
            g
        }
        Err(why) => {
            tracing::error!(target: gunnar_core::observe::TARGET_HEIMDALL, "ALERT Heimdall pack gate asked for ({CHECKER_ENV}) and not started: {why}; every fetched or pushed pack is refused");
            closed(stores.root(), why)
        }
    };
    stores.set_pack_gate(Arc::new(gate));
}