server/lib/tests/the_estate_declaration_runs_the_intake_chain.rs
//! **The declaration every Holger estate ships runs the security gateway** (lane W2).
//!
//! The server boots holger-recipe's `estate_ron::medium()` — the very file
//! `cargo xtask estate-media` stages on the `holger` and `holger-keep` media — with
//! its paths moved into a temporary directory and its binds on loopback, and its
//! `{estate}` filled in as the box fills it from the installer's feed. Then, over
//! the real doors:
//!
//! * the console principal (the fed static token) ingests a wheel into `incoming`
//! and walks it to POLICY_CHECKED through the REQUIRED scan (vulnerability
//! against the offline intel database, sbom);
//! * the console never approves its own import;
//! * the console mints two invites into the approver group, two people enrol with
//! a passkey and log in, and their two distinct approvals make it APPROVED
//! (`approvals: 2`);
//! * the console seals it onward to AVAILABLE, production serves the bytes and
//! lists them in the PEP 503 index;
//! * the audit chain verifies, and the korp root the medium stages as PEM is the
//! DER the module verifier reads.
//!
//! The passkey method is added exactly as a paired box adds it from the feed's
//! `passkey_rp_id` (server-cli `paired::passkey_from_feed`).
use std::io::Write;
use traits::test_support::hold_port;
use holger_recipe::estate_ron;
use holger_server_lib::auth::AuthMethodConfig;
use holger_server_lib::{read_ron_config, wire_holger, Holger};
use nornir_auth_webauthn::softkey::SoftKey;
const ESTATE: &str = "test3.holger.rs";
const ORIGIN: &str = "https://test3.holger.rs";
const CONSOLE_TOKEN: &str = "console-bearer-of-sufficient-length-0123456789";
const WHEEL: &str = "demo_pkg-1.0.0-py3-none-any.whl";
struct Running {
data: tempfile::TempDir,
_cfg: tempfile::NamedTempFile,
holger: Holger,
base: String,
}
fn start() -> Running {
let mut last = String::new();
for _ in 0..8 {
let (grpc, http) = (hold_port(), hold_port());
match try_start(grpc, http) {
Ok(r) => return r,
Err(e) => last = e,
}
}
panic!("server never started; last error: {last}");
}
/// The intel database a box gets by import, installed by hand: no advisory for
/// the demo wheel, a fresh snapshot.
fn install_intel(dir: &std::path::Path) {
use holger_intel::chrono::Utc;
use holger_intel::{AdvisorySet, InstalledIntel, LocalIntelDb};
let set = AdvisorySet {
schema: holger_intel::set::SET_SCHEMA.into(),
generated_at: Utc::now(),
sources: vec![],
advisories: vec![],
};
let db = LocalIntelDb::open(dir);
let bytes = set.to_json_bytes().unwrap();
db.stage_generation(1, &bytes).unwrap();
db.activate(&InstalledIntel {
schema: holger_intel::db::INSTALLED_SCHEMA.into(),
bundle_id: "holger-intel-1".into(),
sequence: 1,
supplier: holger_intel::DEFAULT_SUPPLIER.into(),
snapshot_at: Utc::now(),
imported_at: Utc::now(),
candidate_digest: "00".into(),
release_signer_fingerprint: "00".into(),
advisories_file: LocalIntelDb::generation_file(1),
advisories_sha256: holger_server_lib::security::blob_digest_hex(&bytes),
advisories: 0,
})
.unwrap();
}
fn try_start(grpc: u16, http: u16) -> Result<Running, String> {
let data = tempfile::tempdir().unwrap();
let d = data.path().display().to_string();
// The medium's own file, its paths moved under the temporary directory.
let medium = estate_ron::medium();
let ron = medium
.replace(
&format!("\"{}/", holger_recipe::packaging::BOX_HOLGER_DATA),
&format!("\"{d}/holger/"),
)
.replace(
&format!("\"{}/", estate_ron::BOX_APPLIANCE_ROOT),
&format!("\"{d}/"),
)
.replace(
&format!("\"{}/", estate_ron::RELEASE_ROOT),
&format!("\"{d}/"),
)
// A test build scans in-process and runs no clamd; the shipped declaration keeps the
// defaults (`sandbox: required`, ClamAV required). The medium already carries the
// `rules` key, so it is REPLACED, never added beside it (RON refuses a duplicate key).
.replace("rules: (sandbox: required),", "rules: (sandbox: off),")
.replace("required: true)))", "required: false)))")
.replace("\"0.0.0.0:50051\"", &format!("\"127.0.0.1:{grpc}\""))
.replace("\"0.0.0.0:8443\"", &format!("\"127.0.0.1:{http}\""));
assert!(!ron.contains("\"/data"), "every path moved:\n{ron}");
// What the installer feeds and the medium stages.
std::fs::write(
data.path().join(estate_ron::CONSOLE_TOKEN_FILE),
CONSOLE_TOKEN,
)
.unwrap();
std::fs::set_permissions(
data.path().join(estate_ron::CONSOLE_TOKEN_FILE),
<std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o600),
)
.unwrap();
std::fs::write(
data.path().join(estate_ron::KORP_ROOT_STAGED),
estate_ron::KORP_ROOT_PEM,
)
.unwrap();
install_intel(&data.path().join("holger/intel"));
let mut cfg = tempfile::NamedTempFile::new().unwrap();
cfg.write_all(ron.as_bytes()).unwrap();
let mut holger: Holger = read_ron_config(cfg.path()).map_err(|e| format!("config: {e:#}"))?;
// The box: the estate's name from the join reply, the passkey plane from the feed.
holger.resolve_estate(Some(ESTATE));
holger
.auth
.as_mut()
.expect("the estate declares auth")
.methods
.push(AuthMethodConfig::Passkey {
rp_id: ESTATE.into(),
rp_origin: ORIGIN.into(),
rp_name: "holger".into(),
session_key_file: None,
bootstrap_until_first_admin: true,
});
holger
.instantiate_backends()
.map_err(|e| format!("backends: {e:#}"))?;
wire_holger(&mut holger).map_err(|e| format!("wire: {e:#}"))?;
holger.start().map_err(|e| format!("start: {e:#}"))?;
Ok(Running {
data,
_cfg: cfg,
holger,
base: format!("http://127.0.0.1:{http}"),
})
}
async fn wait_healthy(c: &reqwest::Client, base: &str) {
for _ in 0..200 {
if let Ok(r) = c.get(format!("{base}/healthz")).send().await {
if r.status() == 200 {
return;
}
}
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
}
panic!("gateway never became healthy at {base}");
}
async fn post(
c: &reqwest::Client,
base: &str,
path: &str,
bearer: Option<&str>,
body: serde_json::Value,
) -> (u16, serde_json::Value) {
let mut req = c.post(format!("{base}{path}")).json(&body);
if let Some(b) = bearer {
req = req.bearer_auth(b);
}
let r = req.send().await.expect("the door answers");
let status = r.status().as_u16();
let text = r.text().await.unwrap_or_default();
(
status,
serde_json::from_str(&text).unwrap_or(serde_json::Value::String(text)),
)
}
/// An invite into the approver group, minted by `by` (a person's stepped-up session: the
/// console's machine token invites no approver, HOL-NCSC), redeemed by a new person's
/// passkey, and a user-verified login: the approver's session token.
async fn enrol_approver(c: &reqwest::Client, base: &str, by: &str, name: &str, seed: u8) -> String {
let (status, minted) = post(
c,
base,
"/-/auth/passkey/invites",
Some(by),
serde_json::json!({ "display": name, "groups": [estate_ron::APPROVER_GROUP] }),
)
.await;
assert_eq!(status, 200, "the admin mints an approver invite: {minted}");
assert_eq!(
minted["groups"],
serde_json::json!([estate_ron::APPROVER_GROUP])
);
let secret = minted["invite"]
.as_str()
.expect("the invite secret")
.to_owned();
let mut key = SoftKey::new(seed, format!("cred-{name}-0001").as_bytes());
let (status, start) = post(
c,
base,
"/-/auth/passkey/register/start",
None,
serde_json::json!({ "display": name, "invite": secret }),
)
.await;
assert_eq!(status, 200, "{name} starts enrolment: {start}");
let ceremony = start["ceremony_id"].as_str().unwrap().to_owned();
let (client_data, attestation) = key.register(
ESTATE,
ORIGIN,
start["publicKey"]["challenge"].as_str().unwrap(),
true,
);
let (status, reg) = post(
c,
base,
"/-/auth/passkey/register/finish",
None,
serde_json::json!({ "ceremony_id": ceremony, "id": key.credential_id_b64(), "response": { "clientDataJSON": client_data, "attestationObject": attestation } }),
)
.await;
assert_eq!(status, 200, "{name} enrols with the invite: {reg}");
let (_, start) = post(
c,
base,
"/-/auth/passkey/login/start",
None,
serde_json::json!({ "uv": "required" }),
)
.await;
let ceremony = start["ceremony_id"].as_str().unwrap().to_owned();
let (client_data, auth_data, signature) = key.authenticate(
ESTATE,
ORIGIN,
start["publicKey"]["challenge"].as_str().unwrap(),
true,
);
let (status, out) = post(
c,
base,
"/-/auth/passkey/login/finish",
None,
serde_json::json!({ "ceremony_id": ceremony, "id": key.credential_id_b64(), "response": { "clientDataJSON": client_data, "authenticatorData": auth_data, "signature": signature, "userHandle": null } }),
)
.await;
assert_eq!(status, 200, "{name} logs in: {out}");
out["token"].as_str().expect("a session token").to_owned()
}
/// The first admin, enrolled by the console's bearer (the box's first-admin flow), and a
/// user-verified login: the admin's session token.
async fn enrol_first_admin(c: &reqwest::Client, base: &str, name: &str, seed: u8) -> String {
let mut key = SoftKey::new(seed, format!("cred-{name}-0001").as_bytes());
let (status, start) = post(
c,
base,
"/-/auth/passkey/register/start",
Some(CONSOLE_TOKEN),
serde_json::json!({ "display": name, "role": "admin" }),
)
.await;
assert_eq!(status, 200, "the console enrols the first admin: {start}");
let ceremony = start["ceremony_id"].as_str().unwrap().to_owned();
let (client_data, attestation) = key.register(
ESTATE,
ORIGIN,
start["publicKey"]["challenge"].as_str().unwrap(),
true,
);
let (status, reg) = post(
c,
base,
"/-/auth/passkey/register/finish",
None,
serde_json::json!({ "ceremony_id": ceremony, "id": key.credential_id_b64(), "response": { "clientDataJSON": client_data, "attestationObject": attestation } }),
)
.await;
assert_eq!(status, 200, "{name} enrols: {reg}");
let (_, start) = post(
c,
base,
"/-/auth/passkey/login/start",
None,
serde_json::json!({ "uv": "required" }),
)
.await;
let ceremony = start["ceremony_id"].as_str().unwrap().to_owned();
let (client_data, auth_data, signature) = key.authenticate(
ESTATE,
ORIGIN,
start["publicKey"]["challenge"].as_str().unwrap(),
true,
);
let (status, out) = post(
c,
base,
"/-/auth/passkey/login/finish",
None,
serde_json::json!({ "ceremony_id": ceremony, "id": key.credential_id_b64(), "response": { "clientDataJSON": client_data, "authenticatorData": auth_data, "signature": signature, "userHandle": null } }),
)
.await;
assert_eq!(status, 200, "{name} logs in: {out}");
out["token"].as_str().expect("a session token").to_owned()
}
/// `POST /-/intake/advance` as `bearer`: (status, body).
async fn advance(c: &reqwest::Client, base: &str, bearer: &str) -> (u16, String) {
let r = c
.post(format!("{base}/-/intake/advance?name={WHEEL}&verb=advance"))
.bearer_auth(bearer)
.send()
.await
.unwrap();
(r.status().as_u16(), r.text().await.unwrap_or_default())
}
#[tokio::test(flavor = "multi_thread")]
async fn the_shipped_estate_declaration_runs_the_intake_chain_end_to_end() {
let r = start();
let c = tls_roots(reqwest::Client::builder())
.build()
.expect("reqwest client");
wait_healthy(&c, &r.base).await;
// ── what the declaration turned on ──
let crates = r
.holger
.repositories
.iter()
.find(|x| x.ron_name == "crates")
.expect("the crates repository");
assert_eq!(
crates.ron_base_url.as_deref(),
Some("https://repo.test3.holger.rs"),
"the estate's own name, fed"
);
let chain = r
.holger
.intake()
.expect("the declaration declares the intake chain");
let der = std::fs::read(r.data.path().join("holger/plugins/trust/korp-root-ca.der"))
.expect("the staged PEM became DER");
assert_eq!(
holger_server_lib::security::blob_digest_hex(&der),
"6b19bf479c9d00594f1a1504f0ca7d93da81b0e4f03187a3254fa11248570b16",
"the published korp root, the ONE box's [plugin_root] pin"
);
assert_eq!(
r.holger.plugin_trust_roots(),
vec![r.data.path().join("holger/plugins/trust/korp-root-ca.der")]
);
// ── the console imports; nothing serves an INGESTED wheel ──
let wheel = wheel_bytes();
let put = c
.put(format!("{}/incoming/{WHEEL}", r.base))
.bearer_auth(CONSOLE_TOKEN)
.body(wheel.clone())
.send()
.await
.unwrap();
assert_eq!(
put.status(),
201,
"the console ingests: {}",
put.text().await.unwrap_or_default()
);
// The shipped declaration gates reads (HCON-5, `access: (reads: Gated)`): an anonymous
// reader is refused by name, a signed-in one reads.
let anon = c
.get(format!("{}/production/{WHEEL}", r.base))
.send()
.await
.unwrap();
assert_eq!(anon.status(), 401, "the estate medium gates reads");
assert!(
anon.text().await.unwrap().starts_with("[read-gated]"),
"refused by name"
);
let get = c
.get(format!("{}/production/{WHEEL}", r.base))
.bearer_auth(CONSOLE_TOKEN)
.send()
.await
.unwrap();
assert_eq!(get.status(), 404, "production serves nothing INGESTED");
let put = c
.put(format!("{}/production/{WHEEL}", r.base))
.bearer_auth(CONSOLE_TOKEN)
.body(wheel.clone())
.send()
.await
.unwrap();
assert_eq!(put.status(), 403, "nothing is written past the chain");
// ── the required scan at SCANNED, then POLICY_CHECKED ──
for want in ["SCANNED", "POLICY_CHECKED"] {
let (status, body) = advance(&c, &r.base, CONSOLE_TOKEN).await;
assert_eq!(status, 200, "→ {want}: {body}");
assert!(body.contains(want), "→ {want}: {body}");
}
let state = c
.get(format!("{}/-/intake/state?name={WHEEL}", r.base))
.bearer_auth(CONSOLE_TOKEN)
.send()
.await
.unwrap()
.text()
.await
.unwrap();
for kind in ["vulnerability", "sbom"] {
assert!(
state.contains(kind),
"the SCANNED entry carries {kind} evidence: {state}"
);
}
// ── one console login can approve nothing ──
let (status, body) = advance(&c, &r.base, CONSOLE_TOKEN).await;
assert_eq!(status, 403, "the console never approves: {body}");
assert!(
body.starts_with("[holger-duty-1]") || body.starts_with("[holger-duty-2]"),
"{body}"
);
// ── two invited approvers: one is not enough, two are ──
// The console's machine token invites no approver (HOL-NCSC, `holger-auth-6`); it enrols
// the first admin, whose stepped-up session invites them.
let (status, out) = post(
&c,
&r.base,
"/-/auth/passkey/invites",
Some(CONSOLE_TOKEN),
serde_json::json!({ "display": "anna", "groups": [estate_ron::APPROVER_GROUP] }),
)
.await;
assert_eq!(status, 403, "the console mints no approver invite: {out}");
assert!(out.to_string().contains("[holger-auth-6]"), "{out}");
let admin = enrol_first_admin(&c, &r.base, "olga", 0x4f).await;
let anna = enrol_approver(&c, &r.base, &admin, "anna", 0x41).await;
let bert = enrol_approver(&c, &r.base, &admin, "bert", 0x42).await;
let (status, body) = advance(&c, &r.base, &anna).await;
assert_eq!(status, 200, "anna approves: {body}");
assert!(body.contains("POLICY_CHECKED"), "one of two: {body}");
let (status, body) = advance(&c, &r.base, &bert).await;
assert_eq!(status, 200, "bert approves: {body}");
assert!(body.contains("APPROVED"), "two of two: {body}");
// ── the console seals onward to AVAILABLE ──
for want in ["SEALED", "PROMOTED", "AVAILABLE"] {
let (status, body) = advance(&c, &r.base, CONSOLE_TOKEN).await;
assert_eq!(status, 200, "→ {want}: {body}");
}
assert_eq!(
chain
.state(&holger_server_lib::ArtifactId {
namespace: None,
name: WHEEL.into(),
version: String::new()
})
.name(),
"AVAILABLE"
);
let get = c
.get(format!("{}/production/{WHEEL}", r.base))
.bearer_auth(CONSOLE_TOKEN)
.send()
.await
.unwrap();
assert_eq!(get.status(), 200);
assert_eq!(
get.bytes().await.unwrap().as_ref(),
&wheel[..],
"the wheel, byte for byte"
);
let index = c
.get(format!("{}/production/simple/demo-pkg/", r.base))
.bearer_auth(CONSOLE_TOKEN)
.send()
.await
.unwrap();
assert_eq!(index.status(), 200, "the PEP 503 index");
assert!(index.text().await.unwrap().contains(WHEEL));
// ── the audit chain verifies ──
let verify = c
.get(format!("{}/-/audit/verify", r.base))
.bearer_auth(CONSOLE_TOKEN)
.send()
.await
.unwrap();
assert_eq!(verify.status(), 200, "the console audits");
let v: serde_json::Value = verify.json().await.unwrap();
assert_eq!(v["intact"], serde_json::json!(true), "{v}");
}
/// A small but well-formed wheel (stored-only zip with METADATA and WHEEL).
fn wheel_bytes() -> Vec<u8> {
fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for b in data {
crc ^= *b as u32;
for _ in 0..8 {
crc = if crc & 1 != 0 {
(crc >> 1) ^ 0xEDB8_8320
} else {
crc >> 1
};
}
}
!crc
}
fn member(out: &mut Vec<u8>, central: &mut Vec<u8>, name: &str, body: &[u8]) {
let crc = crc32(body);
let offset = out.len() as u32;
out.extend_from_slice(&0x0403_4b50u32.to_le_bytes());
out.extend_from_slice(&[20, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&(body.len() as u32).to_le_bytes());
out.extend_from_slice(&(body.len() as u32).to_le_bytes());
out.extend_from_slice(&(name.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(name.as_bytes());
out.extend_from_slice(body);
central.extend_from_slice(&0x0201_4b50u32.to_le_bytes());
central.extend_from_slice(&[20, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
central.extend_from_slice(&crc.to_le_bytes());
central.extend_from_slice(&(body.len() as u32).to_le_bytes());
central.extend_from_slice(&(body.len() as u32).to_le_bytes());
central.extend_from_slice(&(name.len() as u16).to_le_bytes());
central.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
central.extend_from_slice(&offset.to_le_bytes());
central.extend_from_slice(name.as_bytes());
}
let mut out = Vec::new();
let mut central = Vec::new();
member(
&mut out,
&mut central,
"demo_pkg/__init__.py",
b"__version__ = \"1.0.0\"\n",
);
member(
&mut out,
&mut central,
"demo_pkg-1.0.0.dist-info/METADATA",
b"Metadata-Version: 2.1\nName: demo-pkg\nVersion: 1.0.0\nLicense-Expression: MIT\n",
);
member(
&mut out,
&mut central,
"demo_pkg-1.0.0.dist-info/WHEEL",
b"Wheel-Version: 1.0\nGenerator: holger-test\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
);
let cd_offset = out.len() as u32;
let cd_len = central.len() as u32;
out.extend_from_slice(¢ral);
out.extend_from_slice(&0x0605_4b50u32.to_le_bytes());
out.extend_from_slice(&[0, 0, 0, 0, 3, 0, 3, 0]);
out.extend_from_slice(&cd_len.to_le_bytes());
out.extend_from_slice(&cd_offset.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out
}
/// reqwest 0.13 with the ring provider and the bundled roots, as the other door tests build it.
fn tls_roots(b: reqwest::ClientBuilder) -> reqwest::ClientBuilder {
let _ = rustls::crypto::ring::default_provider().install_default();
b.tls_certs_only(
webpki_root_certs::TLS_SERVER_ROOT_CERTS
.iter()
.filter_map(|c| reqwest::Certificate::from_der(c).ok()),
)
}