1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! **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));
}