xtask/src/k8s.rs
//! **`cargo xtask k8s`**: Holger's Kubernetes flavour (lane B4, A31 §7.3, §7.7).
//!
//! * `image`: build `holger` and `holger-scan-worker` static (musl, release) in
//! the toolchain container, then mint both images FROM scratch:
//! `holger.rs/nordisk/holger:<ver>` (distribution/Dockerfile) and
//! `holger.rs/nordisk/holger-scan-worker:<ver>`
//! (distribution/scan-worker/Containerfile). No registry is asked
//! (`--pull=never`, `--network=none`).
//! * `chart`: `helm lint` and `helm template` of deploy/helm/holger, and the
//! rendered pod checked (two containers, the shared emptyDir, no token, a
//! read-only root, no privilege escalation, the remote engine in the RON).
//! * `test`: **the container test.** The chart rendered and played by podman
//! (`podman kube play`): the server and its scan-worker sidecar in one pod,
//! `/readyz` goes ready, goes 503 naming the sandbox when the sidecar stops,
//! and ready again when it starts. Then **Holger serves its own image**: a
//! local Holger (the static binary with the OCI format, holger.rs's shape, as
//! a local process) takes the worker image by `podman push` and hands it back
//! to `podman pull`, the same image. Never the live holger.rs.
//! * `publish`: the release step's plan, printed: the two pushes to
//! holger.rs's own OCI registry. It pushes nothing (the release does).
//!
//! The images come from holger.rs ITSELF (owner decision, 2026-10-26): Holger
//! serves its own OCI images.
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use anyhow::{bail, Context, Result};
/// Holger's own OCI registry: holger.rs, the `nordisk` repository.
pub const REGISTRY: &str = "holger.rs/nordisk";
/// The static target.
pub const MUSL: &str = "x86_64-unknown-linux-musl";
/// The chart.
pub const CHART: &str = "deploy/helm/holger";
/// The container flavour's features: the default (the upstream) without
/// `systemd` (the kubelet is the process manager). A test holds it to the
/// manifest's default.
pub const K8S_FEATURES: &str = "downstream-core,fmt-raw,fmt-rust,fmt-npm,fmt-maven,fmt-python,fmt-docker,fmt-go,fmt-helm,fmt-deb,fmt-rpm,fmt-znippy,fmt-build-thing,secure_update,sign,sync,fetch,export-push";
/// The registry the test stands up in holger.rs's shape: the container flavour
/// plus every format (`all-formats`, the OCI one among them), as holger.rs serves its images.
pub const REGISTRY_FEATURES: &str = "downstream-core,fmt-raw,fmt-rust,fmt-npm,fmt-maven,fmt-python,fmt-docker,fmt-go,fmt-helm,fmt-deb,fmt-rpm,fmt-znippy,fmt-build-thing,secure_update,sign,sync,fetch,export-push,all-formats";
/// Where `build` keeps the registry variant (the server's own path is reused by
/// the next build).
pub const REGISTRY_BIN: &str = "k8s/holger-oci";
/// The test pod's release name: every container it makes is `b4-k8s-…`.
const RELEASE: &str = "b4-k8s";
/// The server's version, from the root workspace (the chart's appVersion).
pub fn version(root: &Path) -> Result<String> {
let text = std::fs::read_to_string(root.join("Cargo.toml"))?;
let v: toml::Value = toml::from_str(&text)?;
v.get("workspace")
.and_then(|w| w.get("package"))
.and_then(|p| p.get("version"))
.and_then(|v| v.as_str())
.map(str::to_string)
.context("the root Cargo.toml has no [workspace.package] version")
}
/// The server image.
pub fn server_ref(ver: &str) -> String {
format!("{REGISTRY}/holger:{ver}")
}
/// The scan-worker sidecar image.
pub fn worker_ref(ver: &str) -> String {
format!("{REGISTRY}/holger-scan-worker:{ver}")
}
/// What the release pushes, in order.
pub fn publish_plan(ver: &str) -> Vec<String> {
[server_ref(ver), worker_ref(ver)]
.into_iter()
.map(|r| format!("podman push {r} docker://{r}"))
.collect()
}
fn run(cmd: &mut Command) -> Result<()> {
let shown = format!("{cmd:?}");
let st = cmd.status().with_context(|| format!("starting {shown}"))?;
if !st.success() {
bail!("{shown} exited {st}");
}
Ok(())
}
fn output(cmd: &mut Command) -> Result<String> {
let shown = format!("{cmd:?}");
let o = cmd
.stderr(Stdio::inherit())
.output()
.with_context(|| format!("starting {shown}"))?;
if !o.status.success() {
bail!("{shown} exited {}", o.status);
}
Ok(String::from_utf8_lossy(&o.stdout).into_owned())
}
fn target_dir(root: &Path) -> PathBuf {
std::env::var_os("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| root.join("target"))
}
/// Build both static binaries in the toolchain container; their paths.
pub fn build(root: &Path) -> Result<(PathBuf, PathBuf)> {
let parent = root.parent().unwrap_or(root);
let target = target_dir(root);
std::fs::create_dir_all(&target)?;
let registry = std::env::var_os("CARGO_REGISTRY_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| {
PathBuf::from(std::env::var_os("HOME").unwrap_or_default()).join(".cargo/registry")
});
let toolchain = std::env::var("TOOLCHAIN_IMAGE")
.unwrap_or_else(|_| "localhost/nordisk-cargo-toolchain:live".into());
let out = target.join(MUSL).join("release");
for (pkg, bin, features) in [
("holger", "holger", Some(REGISTRY_FEATURES)),
("holger", "holger", Some(K8S_FEATURES)),
("holger-scan-worker", "holger-scan-worker", None),
] {
let mut flavour: Vec<String> = Vec::new();
if let Some(f) = features {
flavour = vec![
"--no-default-features".into(),
"--features".into(),
f.into(),
];
}
run(Command::new("podman")
.args(["run", "--rm", "--pids-limit=-1"])
.arg(format!("--volume={0}:{0}", parent.display()))
.arg(format!("--volume={0}:{0}", target.display()))
.arg(format!(
"--volume={}:/usr/local/cargo/registry",
registry.display()
))
.arg(format!("--env=CARGO_TARGET_DIR={}", target.display()))
.arg(format!("--workdir={}", root.display()))
.arg(&toolchain)
.args([
"cargo",
"build",
"--release",
"--locked",
"-p",
pkg,
"--bin",
bin,
"--target",
MUSL,
])
.args(&flavour))?;
if features == Some(REGISTRY_FEATURES) {
let keep = target.join(REGISTRY_BIN);
std::fs::create_dir_all(keep.parent().unwrap())?;
std::fs::copy(out.join("holger"), &keep)?;
}
}
Ok((out.join("holger"), out.join("holger-scan-worker")))
}
const PASSWD: &str = "nonroot:x:65532:65532:nonroot:/tmp:/sbin/nologin\nscan:x:65533:65532:scan:/scratch:/sbin/nologin\n";
const GROUP: &str = "nonroot:x:65532:\n";
/// Mint one image from `containerfile` with `bin` beside it.
fn mint(root: &Path, name: &str, containerfile: &str, bin: &Path, tag: &str) -> Result<()> {
let ctx = target_dir(root).join(format!("k8s-context-{name}"));
let _ = std::fs::remove_dir_all(&ctx);
std::fs::create_dir_all(&ctx)?;
let bytes = std::fs::read(bin).with_context(|| bin.display().to_string())?;
if !is_static_elf(&bytes) {
bail!(
"{} is not a static ELF binary: FROM scratch has no loader",
bin.display()
);
}
std::fs::write(ctx.join(name), &bytes)?;
std::fs::copy(root.join(containerfile), ctx.join("Containerfile"))?;
std::fs::write(ctx.join("passwd"), PASSWD)?;
std::fs::write(ctx.join("group"), GROUP)?;
run(Command::new("podman")
.args([
"build",
"--pull=never",
"--network=none",
"--layers=false",
"-f",
])
.arg(ctx.join("Containerfile"))
.args(["-t", tag])
.arg(&ctx))?;
println!("minted {tag}");
Ok(())
}
/// A static ELF: no `PT_INTERP` program header (no dynamic loader asked for).
pub fn is_static_elf(b: &[u8]) -> bool {
if b.len() < 64 || !b.starts_with(b"\x7fELF") || b[4] != 2 {
return false;
}
let u16_at = |o: usize| u16::from_le_bytes([b[o], b[o + 1]]) as usize;
let u64_at = |o: usize| u64::from_le_bytes(b[o..o + 8].try_into().unwrap()) as usize;
let (phoff, phentsize, phnum) = (u64_at(32), u16_at(54), u16_at(56));
(0..phnum).all(|i| {
let o = phoff + i * phentsize;
o + 4 <= b.len() && u32::from_le_bytes(b[o..o + 4].try_into().unwrap()) != 3
})
}
/// `image`: both images.
pub fn image(root: &Path, server: &Path, worker: &Path) -> Result<()> {
let ver = version(root)?;
mint(
root,
"holger",
"distribution/Dockerfile",
server,
&server_ref(&ver),
)?;
mint(
root,
"holger-scan-worker",
"distribution/scan-worker/Containerfile",
worker,
&worker_ref(&ver),
)
}
fn helm() -> String {
std::env::var("HELM").unwrap_or_else(|_| "helm".into())
}
/// `helm template` of the chart with `sets`.
pub fn render(root: &Path, sets: &[&str]) -> Result<String> {
let mut c = Command::new(helm());
c.args(["template", RELEASE]).arg(root.join(CHART));
for s in sets {
c.args(["--set", s]);
}
output(&mut c)
}
/// The rendered documents of one kind.
pub fn docs_of<'a>(rendered: &'a str, kind: &str) -> Vec<&'a str> {
rendered
.split("\n---")
.filter(|d| d.lines().any(|l| l.trim() == format!("kind: {kind}")))
.collect()
}
/// **The rendered pod, checked.** Every line a hardened sidecar pod must have.
pub fn check_rendered(rendered: &str, ver: &str) -> Result<()> {
let deploy = docs_of(rendered, "Deployment");
let [d] = deploy.as_slice() else {
bail!("the chart renders {} Deployments, not one", deploy.len())
};
let want = [
format!("image: \"{}\"", server_ref(ver)),
format!("image: \"{}\"", worker_ref(ver)),
"- name: scan-worker".to_string(),
"- serve".to_string(),
"automountServiceAccountToken: false".to_string(),
"readOnlyRootFilesystem: true".to_string(),
"allowPrivilegeEscalation: false".to_string(),
"runAsNonRoot: true".to_string(),
"path: /readyz".to_string(),
"mountPath: /run/holger-scan".to_string(),
"type: RuntimeDefault".to_string(),
];
for w in &want {
if !d.contains(w.as_str()) {
bail!("the rendered Deployment lacks `{w}`");
}
}
if d.contains("privileged: true") || d.contains("hostPath") || d.contains("hostNetwork") {
bail!("the rendered Deployment asks for the host");
}
let scan_mounts = d.matches("mountPath: /run/holger-scan").count();
if scan_mounts != 2 {
bail!("the shared directory is mounted {scan_mounts} times, not by exactly the server and the sidecar");
}
let cm = docs_of(rendered, "ConfigMap").join("\n");
if !cm.contains("engine: remote") || !cm.contains("/run/holger-scan/worker.sock") {
bail!("the config's sandbox is not the remote engine on the sidecar's socket");
}
let np = docs_of(rendered, "NetworkPolicy").join("\n");
if !np.contains("Egress") {
bail!("no NetworkPolicy closes egress");
}
Ok(())
}
/// `chart`: lint, render, check.
pub fn chart(root: &Path) -> Result<()> {
run(Command::new(helm())
.arg("lint")
.arg(root.join(CHART))
.args(["--set", "tls.cert=c", "--set", "tls.key=k"]))?;
let ver = version(root)?;
let r = render(root, &["tls.cert=c", "tls.key=k"])?;
check_rendered(&r, &ver)?;
let down = render(
root,
&["tls.cert=c", "tls.key=k", "scanWorker.enabled=false"],
)?;
if down.contains("- name: scan-worker") || down.contains("mountPath: /run/holger-scan") {
bail!("a downstream (scanWorker.enabled=false) still renders the sidecar");
}
println!("chart: lint and template green; the pod is hardened, the sidecar is wired");
Ok(())
}
/// The test pod's config: the chart's upstream RON without TLS (loopback only).
fn test_ron(chart_ron: &str) -> String {
let start = chart_ron
.find("ron_tls: Some((")
.expect("the chart's RON names ron_tls");
let end = start + chart_ron[start..].find(")),").expect("ron_tls closes") + 3;
let mut s = chart_ron.to_string();
// The pod binds 0.0.0.0 and the host publishes it on 127.0.0.1 only: clear text is
// written down (HOL-NCSC, `insecure: true`).
s.replace_range(start..end, "ron_tls: None, insecure: true,");
s
}
fn http_get(port: u16, path: &str) -> Option<(u16, String)> {
use std::io::{Read as _, Write as _};
let mut s = std::net::TcpStream::connect_timeout(
&([127, 0, 0, 1], port).into(),
Duration::from_secs(2),
)
.ok()?;
s.set_read_timeout(Some(Duration::from_secs(10))).ok()?;
s.write_all(
format!("GET {path} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n").as_bytes(),
)
.ok()?;
let mut out = String::new();
s.read_to_string(&mut out).ok()?;
let code = out.split_whitespace().nth(1)?.parse().ok()?;
let body = out
.split_once("\r\n\r\n")
.map(|(_, b)| b.to_string())
.unwrap_or_default();
Some((code, body))
}
fn wait_for(port: u16, path: &str, want: u16, secs: u64) -> Result<String> {
let t = Instant::now();
let mut last = None;
while t.elapsed() < Duration::from_secs(secs) {
last = http_get(port, path);
if let Some((c, b)) = &last {
if *c == want {
return Ok(b.clone());
}
}
std::thread::sleep(Duration::from_millis(500));
}
bail!("{path} never answered {want} in {secs} s; last {last:?}")
}
fn free_port() -> Result<u16> {
Ok(std::net::TcpListener::bind("127.0.0.1:0")?
.local_addr()?
.port())
}
fn podman_quiet(args: &[&str]) {
let _ = Command::new("podman")
.args(args)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
/// `test`: the pod plays, `/readyz` follows the sidecar, Holger serves its image.
pub fn test(root: &Path) -> Result<()> {
let ver = version(root)?;
for r in [server_ref(&ver), worker_ref(&ver)] {
if !Command::new("podman")
.args(["image", "exists", &r])
.status()
.is_ok_and(|s| s.success())
{
bail!("{r} is not on this host: run `cargo xtask k8s image` first");
}
}
let values = std::fs::read_to_string(root.join(CHART).join("values.yaml"))?;
let chart_ron = values_ron(&values)?;
let dir = target_dir(root).join("k8s-test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir)?;
let ron_file = dir.join("holger.ron");
std::fs::write(&ron_file, test_ron(&chart_ron))?;
let mut c = Command::new(helm());
c.args(["template", RELEASE]).arg(root.join(CHART));
for s in [
"tls.enabled=false",
"image.pullPolicy=Never",
"scanWorker.image.pullPolicy=Never",
"networkPolicy.enabled=false",
] {
c.args(["--set", s]);
}
c.arg("--set-file")
.arg(format!("config.ron={}", ron_file.display()));
let rendered = output(&mut c)?;
// podman plays the pod's own kinds; the Service and the policy are the cluster's.
let playable: Vec<&str> = ["Secret", "ConfigMap", "PersistentVolumeClaim", "Deployment"]
.iter()
.flat_map(|k| docs_of(&rendered, k))
.collect();
// Kubernetes makes an emptyDir mode 0777; podman hands it to whichever
// container mounts it first (0755). The shared directory is laid here with
// Kubernetes' mode, so the server (65532) and the sidecar (65533) both write.
let share = dir.join("scan-share");
std::fs::create_dir_all(&share)?;
{
use std::os::unix::fs::PermissionsExt as _;
std::fs::set_permissions(&share, std::fs::Permissions::from_mode(0o777))?;
}
let pod = as_podman_share(&playable.join("\n---\n"), &share)?;
let yaml = dir.join("pod.yaml");
std::fs::write(&yaml, pod)?;
let port = free_port()?;
let pod = format!("{RELEASE}-holger-pod");
let down = || {
let _ = Command::new("podman")
.args(["kube", "down", "--force"])
.arg(&yaml)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
};
down();
let r = (|| -> Result<()> {
run(Command::new("podman")
.args(["kube", "play", "--replace", "--publish"])
.arg(format!("127.0.0.1:{port}:8443"))
.arg(&yaml))?;
let body = wait_for(port, "/readyz", 200, 120).context("the pod never went ready")?;
println!("k8s test: /readyz {body:?} with the sidecar up");
run(Command::new("podman")
.args(["stop", "--time=2"])
.arg(format!("{pod}-scan-worker")))?;
let not = wait_for(port, "/readyz", 503, 30)
.context("/readyz stayed ready with the sidecar stopped")?;
if !not.contains("sandbox") {
bail!("/readyz is 503 but does not name the sandbox: {not}");
}
println!("k8s test: /readyz 503 with the sidecar stopped: {not}");
let (hz, _) = http_get(port, "/healthz").context("/healthz")?;
if hz != 200 {
bail!("/healthz answered {hz} while the sidecar was down: liveness is not readiness");
}
run(Command::new("podman")
.arg("start")
.arg(format!("{pod}-scan-worker")))?;
wait_for(port, "/readyz", 200, 60).context("/readyz never came back with the sidecar")?;
println!("k8s test: /readyz ready again with the sidecar back");
Ok(())
})();
if r.is_err() {
let _ = Command::new("podman")
.args(["logs", "--tail=40"])
.arg(format!("{pod}-holger"))
.status();
let _ = Command::new("podman")
.args(["logs", "--tail=40"])
.arg(format!("{pod}-scan-worker"))
.status();
}
down();
r?;
serves_its_own_image(root, &ver, &dir)
}
/// The rendered pod with its shared `scan` emptyDir as a host directory (the
/// podman stand-in for Kubernetes' 0777 emptyDir).
fn as_podman_share(pod: &str, share: &Path) -> Result<String> {
let from = " - name: scan\n emptyDir:\n";
let i = pod
.find(from)
.context("the rendered pod has no `scan` emptyDir")?;
let rest = &pod[i + from.len()..];
let end = rest.find('\n').map(|n| n + 1).unwrap_or(rest.len());
Ok(format!(
"{} - name: scan\n hostPath:\n path: {}\n type: Directory\n{}",
&pod[..i],
share.display(),
&rest[end..]
))
}
/// The `config.ron` block of values.yaml (read as text: the block scalar under
/// `config:` / `ron: |`, indented four spaces).
fn values_ron(values: &str) -> Result<String> {
let mut lines = values.lines().skip_while(|l| l.trim_end() != "config:");
lines.next();
if lines.next().map(str::trim) != Some("ron: |") {
bail!("values.yaml: `config:` is not followed by `ron: |`");
}
let body: Vec<&str> = lines
.take_while(|l| l.is_empty() || l.starts_with(" "))
.collect();
Ok(body
.iter()
.map(|l| l.strip_prefix(" ").unwrap_or(l))
.collect::<Vec<_>>()
.join("\n"))
}
/// **Holger serves its own images** (owner decision 2026-10-26): a local Holger,
/// the image just built, with one writable OCI repository `nordisk` (holger.rs's
/// shape), takes the worker image by `podman push` and hands it back to
/// `podman pull`; the pulled image is the pushed one.
fn serves_its_own_image(root: &Path, ver: &str, dir: &Path) -> Result<()> {
let bin = std::env::var_os("HOLGER_REGISTRY_BIN")
.map(PathBuf::from)
.unwrap_or_else(|| target_dir(root).join(REGISTRY_BIN));
if !bin.is_file() {
bail!(
"{} is not there: `cargo xtask k8s image` builds it (holger with {REGISTRY_FEATURES})",
bin.display()
);
}
let port = free_port()?;
let data = dir.join("registry-data");
std::fs::create_dir_all(&data)?;
let d = data.display();
let ron = format!(
r#"(
repositories: [
(ron_name: "nordisk", ron_repo_type: "oci", ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
ron_data_dir: Some("{d}/nordisk")),
],
exposed_endpoints: [ (ron_name: "default", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{port}"), ron_tls: None) ],
storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{d}") ],
audit: (dir: "{d}/audit", enabled: true),
)"#,
grpc = free_port()?
);
let cfg = dir.join("registry.ron");
std::fs::write(&cfg, ron)?;
// The static binary as a local process: in holger.rs's shape, never holger.rs.
let mut child = Command::new(&bin)
.args(["start", "--config"])
.arg(&cfg)
.env("HOME", dir)
.stdin(Stdio::null())
.stdout(Stdio::null())
.spawn()
.with_context(|| bin.display().to_string())?;
let r = (|| -> Result<()> {
wait_for(port, "/healthz", 200, 60).context("the local Holger registry never answered")?;
let local = format!("localhost:{port}/nordisk/holger-scan-worker:{ver}");
run(Command::new("podman")
.args(["push", "--tls-verify=false"])
.arg(worker_ref(ver))
.arg(format!("docker://{local}")))?;
let pushed = output(
Command::new("podman")
.args(["image", "inspect", "--format={{.Id}}"])
.arg(worker_ref(ver)),
)?;
podman_quiet(&["rmi", "--force", &local]);
run(Command::new("podman")
.args(["pull", "--tls-verify=false"])
.arg(&local))?;
let pulled = output(
Command::new("podman")
.args(["image", "inspect", "--format={{.Id}}"])
.arg(&local),
)?;
if pushed.trim() != pulled.trim() {
bail!(
"the image Holger handed back ({}) is not the one pushed ({})",
pulled.trim(),
pushed.trim()
);
}
podman_quiet(&["rmi", &local]);
println!(
"k8s test: a local Holger served {} to podman pull (image {})",
worker_ref(ver),
pulled.trim()
);
Ok(())
})();
let _ = child.kill();
let _ = child.wait();
r
}
/// The verb.
pub fn main(root: &Path, args: &[String]) -> Result<()> {
let flag = |f: &str| {
args.iter()
.position(|a| a == f)
.and_then(|i| args.get(i + 1))
.map(PathBuf::from)
};
match args.first().map(String::as_str) {
Some("image") => {
let (server, worker) = match (flag("--server"), flag("--worker")) {
(Some(s), Some(w)) => (s, w),
_ => build(root)?,
};
image(root, &server, &worker)
}
Some("chart") => chart(root),
Some("test") => test(root),
Some("publish") => {
let ver = version(root)?;
println!("the release step (not run here; nothing is pushed by this verb):");
for l in publish_plan(&ver) {
println!(" {l}");
}
Ok(())
}
_ => bail!("usage: cargo xtask k8s <image [--server <bin> --worker <bin>] | chart | test | publish>"),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.to_path_buf()
}
/// **The images come from holger.rs itself** (owner decision 2026-10-26):
/// the chart, the plain manifests, the Dockerfile's words and the release's
/// publish step all name holger.rs's own registry, at the server's version.
#[test]
fn the_chart_the_dockerfile_and_the_publish_step_name_holger_rs() {
let root = root();
let ver = version(&root).unwrap();
let values = std::fs::read_to_string(root.join(CHART).join("values.yaml")).unwrap();
assert!(
values.contains("repository: holger.rs/nordisk/holger\n"),
"values.yaml: the server image"
);
assert!(
values.contains("repository: holger.rs/nordisk/holger-scan-worker\n"),
"values.yaml: the sidecar image"
);
let chart = std::fs::read_to_string(root.join(CHART).join("Chart.yaml")).unwrap();
assert!(
chart.contains(&format!("appVersion: \"{ver}\"")),
"Chart.yaml's appVersion is the server's {ver}"
);
let plain = std::fs::read_to_string(root.join("deploy/k8s/deployment.yaml")).unwrap();
assert!(
plain.contains(&format!("image: {}", server_ref(&ver))),
"deploy/k8s names holger.rs at {ver}"
);
let docker = std::fs::read_to_string(root.join("distribution/Dockerfile")).unwrap();
assert!(
docker.contains(&server_ref("<ver>")) || docker.contains("holger.rs/nordisk/holger"),
"the Dockerfile names holger.rs"
);
assert_eq!(
publish_plan(&ver),
vec![
format!("podman push holger.rs/nordisk/holger:{ver} docker://holger.rs/nordisk/holger:{ver}"),
format!("podman push holger.rs/nordisk/holger-scan-worker:{ver} docker://holger.rs/nordisk/holger-scan-worker:{ver}"),
]
);
for f in [
"deploy/helm/holger/values.yaml",
"deploy/helm/holger/values-dev.yaml",
"deploy/helm/holger/values-prod.yaml",
"deploy/helm/holger/Chart.yaml",
"deploy/k8s/deployment.yaml",
"distribution/Dockerfile",
] {
let t = std::fs::read_to_string(root.join(f)).unwrap();
assert!(
!t.to_lowercase().contains("codeberg"),
"{f} names the old forge"
);
assert!(
!t.contains(":latest") && !t.contains("tag: \"latest\""),
"{f} names a moving tag"
);
assert!(
!t.lines().any(|l| l.trim_start().starts_with("email:")),
"{f} carries an e-mail address"
);
}
}
/// The stale Dockerfile is fixed: it is the FROM scratch runtime of the
/// static binary (built by `cargo xtask k8s image`), non-root, with no shell.
#[test]
fn the_dockerfile_is_from_scratch_non_root_and_names_the_real_binary() {
let d = std::fs::read_to_string(root().join("distribution/Dockerfile")).unwrap();
let code: Vec<&str> = d
.lines()
.filter(|l| !l.trim_start().starts_with('#') && !l.trim().is_empty())
.collect();
assert_eq!(
code.iter()
.filter(|l| l.starts_with("FROM "))
.collect::<Vec<_>>(),
vec![&"FROM scratch"],
"{code:?}"
);
assert!(code.iter().any(|l| l.starts_with("USER 65532")), "{code:?}");
assert!(
code.iter().any(|l| l.contains("ENTRYPOINT [\"/holger\"]")),
"the binary is `holger`, not the alias: {code:?}"
);
assert!(
!d.contains("holger-server"),
"the alias binary needs a feature nobody builds"
);
}
/// The container flavour is the default without `systemd`, nothing more and
/// nothing less.
#[test]
fn the_container_flavour_is_the_default_without_systemd() {
let cli: toml::Value =
toml::from_str(&std::fs::read_to_string(root().join("server/cli/Cargo.toml")).unwrap())
.unwrap();
let mut default: Vec<&str> = cli["features"]["default"]
.as_array()
.unwrap()
.iter()
.filter_map(|f| f.as_str())
.collect();
assert!(default.contains(&"systemd"), "{default:?}");
default.retain(|f| *f != "systemd");
assert_eq!(K8S_FEATURES.split(',').collect::<Vec<_>>(), default);
}
#[test]
fn the_podman_stand_in_swaps_only_the_shared_emptydir() {
let pod = " volumes:\n - name: tmp\n emptyDir:\n sizeLimit: 256Mi\n - name: scan\n emptyDir:\n sizeLimit: 4Gi\n - name: scratch\n emptyDir:\n sizeLimit: 1Gi\n";
let out = as_podman_share(pod, Path::new("/t/share")).unwrap();
assert!(out.contains(" - name: scan\n hostPath:\n path: /t/share\n type: Directory\n - name: scratch\n emptyDir:"), "{out}");
assert_eq!(
out.matches("emptyDir").count(),
2,
"tmp and scratch stay: {out}"
);
}
#[test]
fn the_test_config_is_the_charts_without_tls() {
let values = std::fs::read_to_string(root().join(CHART).join("values.yaml")).unwrap();
let ron = values_ron(&values).unwrap();
assert!(
ron.contains("engine: remote") && ron.contains("ron_tls: Some(("),
"{ron}"
);
let t = test_ron(&ron);
assert!(
t.contains("ron_tls: None,") && !t.contains("tls.crt"),
"{t}"
);
assert!(t.contains("engine: remote"), "the rest stays: {t}");
}
#[test]
fn a_static_binary_is_told_from_a_dynamic_one() {
let me = std::fs::read(std::env::current_exe().unwrap()).unwrap();
// The test binary is linked against glibc: it asks for a loader.
assert!(!is_static_elf(&me), "a glibc test binary is dynamic");
assert!(!is_static_elf(b"#!/bin/sh\n"), "a script is not an ELF");
}
/// The rendered pod, when helm is on this host (the `chart` verb requires it).
#[test]
fn the_rendered_pod_runs_the_sidecar_hardened() {
if Command::new(helm())
.arg("version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| !s.success())
.unwrap_or(true)
{
eprintln!("helm is not on this host; `cargo xtask k8s chart` checks the rendered pod");
return;
}
let root = root();
let r = render(&root, &["tls.cert=c", "tls.key=k"]).unwrap();
check_rendered(&r, &version(&root).unwrap()).unwrap();
}
#[test]
fn a_pod_without_the_sidecar_or_with_a_privileged_container_is_refused() {
let ver = "9.9.9";
let good = format!(
"kind: Deployment\nautomountServiceAccountToken: false\nreadOnlyRootFilesystem: true\nallowPrivilegeEscalation: false\nrunAsNonRoot: true\ntype: RuntimeDefault\npath: /readyz\nimage: \"{}\"\nimage: \"{}\"\n- name: scan-worker\n- serve\nmountPath: /run/holger-scan\nmountPath: /run/holger-scan\n---\nkind: ConfigMap\nengine: remote /run/holger-scan/worker.sock\n---\nkind: NetworkPolicy\npolicyTypes: [\"Ingress\", \"Egress\"]\n",
server_ref(ver),
worker_ref(ver)
);
check_rendered(&good, ver).unwrap();
assert!(check_rendered(&good.replace("- name: scan-worker", ""), ver).is_err());
assert!(check_rendered(
&good.replace("runAsNonRoot: true", "runAsNonRoot: true\nprivileged: true"),
ver
)
.is_err());
assert!(check_rendered(&good.replace("engine: remote", "engine: podman"), ver).is_err());
}
}