server/lib/tests/open_appliance_loads_no_paid_module.rs
//! **Three censuses against a booted holger, not against a build.**
//!
//! A cargo feature's absence is provable in the symbol table: gunnar proves its
//! metered half absent twice, once by the resolved dependency graph and once by
//! the symbol families in the built binary. An uninstalled module's absence is
//! only *"a file that is not there"*, which is weaker — and the open build's
//! whole claim now rests on a module simply not being installed.
//!
//! This file recovers that strength, and the capability grant is where:
//!
//! 1. **Catalog census.** Every loaded module is named. There is no anonymous
//! module — a module that loaded is a module with a row, and the row carries
//! the blake3 digest of the bytes the signature covered rather than the file
//! name an operator chose.
//! 2. **Grant census.** The effective grant table contains none of
//! `principal.read`, `digest.read`, `clock.read`, `audit.write`. This is the
//! strong one: it proves *"and none can be"* from CONFIGURATION, without
//! looking at a directory at all.
//! 3. **Linker census.** The host functions actually defined on this boot's
//! wasmtime `Linker` equal the granted set. A module wanting
//! `host_audit_write` fails INSTANTIATION because the import is not defined —
//! not merely refused at the call. That is a property of *this boot*, not of
//! *this build*, and is arguably stronger than a symbol-table proof.
//!
//! The censuses are built by `Holger::module_census`, which is the same code the
//! serve path runs on the way to `AdminService.ServerProfile`. Nothing here is a
//! stand-in for it.
use std::path::{Path, PathBuf};
use holger_server_lib::{read_ron_config, Holger};
/// The capabilities that make a metering plugin able to do harm: read who is
/// calling, read which exact bytes, learn the time, and write into the audit
/// chain. The OPEN appliance grants none of them, and this list is why the grant
/// census is worth running.
const NEVER_IN_THE_OPEN_BUILD: &[&str] =
&["principal.read", "digest.read", "clock.read", "audit.write"];
const OPEN_GUEST: &str = "holger_plugin_allocation_wasm.wasm";
/// The REAL module, built on demand by the shared guard helper (`traits::test_support::wasm_guest`:
/// host cargo, `<CARGO_TARGET_DIR>/wasm-guests`, no container engine), so the census runs in
/// every gate; it still never skips (a missing wasm32 target or a failed build is the failure).
fn built_module(name: &str) -> PathBuf {
let package = name.trim_end_matches(".wasm").replace('_', "-");
let p = traits::test_support::wasm_guest(&package);
assert!(p.is_file(), "{} was not built", p.display());
p
}
/// **The open guest, staged SIGNED** (lane E4: a meter-guest build loads no module
/// unverified): copied into `plugins`, its `.p7s` written under a freshly minted CA,
/// whose DER root is returned for `ron_plugin_trust_roots`.
fn staged_signed(plugins: &Path) -> PathBuf {
use znippy_common::sign::{dev, ArchiveSigner};
let module = plugins.join(OPEN_GUEST);
std::fs::copy(built_module(OPEN_GUEST), &module).unwrap();
let (key, der) = dev::mint_ca("holger open appliance test CA").expect("mint CA");
let signer =
dev::new_p256_signer(&key, &der, "holger open appliance publisher").expect("signer");
let digest: [u8; 32] =
*znippy_common::blake3::hash(&std::fs::read(&module).unwrap()).as_bytes();
std::fs::write(
PathBuf::from(format!("{}.p7s", module.display())),
signer.sign_digest(&digest).expect("sign"),
)
.unwrap();
let root = plugins.parent().unwrap().join("plugin-root.der");
std::fs::write(&root, der).unwrap();
root
}
/// A holger whose plugin directory holds the open guest and whose config grants
/// it exactly `usage.read` and `store.list` — the open appliance, as shipped.
fn open_appliance(tmp: &Path) -> Holger {
let plugins = tmp.join("plugins");
std::fs::create_dir_all(&plugins).unwrap();
let root = staged_signed(&plugins);
let ron = format!(
r#"(
repositories: [],
exposed_endpoints: [ (ron_name: "grpc", ron_url: "127.0.0.1:1") ],
storage_endpoints: [],
audit: (dir: "{audit}", enabled: false),
ron_plugin_dir: Some("{dir}"),
ron_plugin_trust_roots: ["{root}"],
ron_plugin_grants: [
( module: "{module}", grants: ["usage.read", "store.list"] ),
],
)"#,
// ABSOLUTE, so the data root (and with it the W14 floor at
// `<data_root>/plugins/floor`) lands in this test's tempdir, not the CWD.
audit = tmp.join("audit").display(),
dir = plugins.display(),
root = root.display(),
module = OPEN_GUEST,
);
let path = tmp.join("holger.ron");
std::fs::write(&path, ron).unwrap();
read_ron_config(&path).expect("the RON parses")
}
#[test]
fn the_catalog_census_names_every_loaded_module() {
let tmp = tempfile::tempdir().unwrap();
let h = open_appliance(tmp.path());
let census = h.module_census().expect("the census builds");
// Without `wasm-plugins` there is no loader, so there is nothing to name and
// the claim is vacuous. Say so rather than passing quietly.
if cfg!(not(feature = "wasm-plugins")) {
assert!(
census.modules.iter().all(|m| m.kind != "meter"),
"a meter appeared in a build with no wasm host"
);
return;
}
let meters: Vec<_> = census
.modules
.iter()
.filter(|m| m.kind == "meter")
.collect();
assert_eq!(meters.len(), 1, "census: {:?}", census.modules);
let m = meters[0];
assert_eq!(m.name, "allocation");
assert_eq!(m.origin, OPEN_GUEST);
// Named by DIGEST, not by the file name an operator chose.
assert_eq!(
m.digest.len(),
64,
"a blake3-256 digest is 64 hex characters: {}",
m.digest
);
assert!(m.digest.chars().all(|c| c.is_ascii_hexdigit()));
// Sorted by the capability enum's own order, which is one writer and not the
// order an operator happened to type.
assert_eq!(
m.granted,
vec!["store.list".to_string(), "usage.read".to_string()]
);
// No anonymous module: every row carries a name and an origin.
for row in &census.modules {
assert!(!row.name.is_empty(), "a module with no name: {row:?}");
assert!(!row.origin.is_empty(), "a module with no origin: {row:?}");
}
}
#[test]
fn the_grant_census_holds_none_of_the_four() {
let tmp = tempfile::tempdir().unwrap();
let h = open_appliance(tmp.path());
let census = h.module_census().expect("the census builds");
for forbidden in NEVER_IN_THE_OPEN_BUILD {
assert!(
!census.granted_capabilities.iter().any(|g| g == forbidden),
"`{forbidden}` is granted on this boot: {:?}",
census.granted_capabilities
);
}
// Not vacuous: something IS granted, so the absence above is a real absence
// and not an empty table.
if cfg!(feature = "wasm-plugins") {
assert_eq!(
census.granted_capabilities,
vec!["store.list".to_string(), "usage.read".to_string()]
);
}
}
#[test]
#[cfg(feature = "wasm-plugins")]
fn the_linker_census_equals_the_granted_set() {
let tmp = tempfile::tempdir().unwrap();
let h = open_appliance(tmp.path());
let census = h.module_census().expect("the census builds");
let mut defined = census.linker_defines.clone();
defined.sort();
assert_eq!(
defined,
vec!["host_store_list".to_string(), "host_usage_read".to_string()],
"this boot's linkers define something the grant did not unlock"
);
// A module reaching for any of these fails instantiation, because the import
// is not defined on the linker at all.
for absent in [
"host_audit_write",
"host_clock",
"host_store_put",
"host_store_get",
] {
assert!(
!census.linker_defines.iter().any(|d| d == absent),
"{absent} is defined on this boot's linker"
);
}
}
/// The negative control. A guard that cannot fail is not a guard, so the same
/// boot with a wider grant must produce a census that the assertions above would
/// reject — and it does, which is what makes their passing mean something.
#[test]
#[cfg(feature = "wasm-plugins")]
fn the_censuses_would_catch_a_paid_grant() {
let tmp = tempfile::tempdir().unwrap();
let plugins = tmp.path().join("plugins");
std::fs::create_dir_all(&plugins).unwrap();
let root = staged_signed(&plugins);
let ron = format!(
r#"(
repositories: [],
exposed_endpoints: [ (ron_name: "grpc", ron_url: "127.0.0.1:1") ],
storage_endpoints: [],
audit: (dir: "{audit}", enabled: false),
ron_plugin_dir: Some("{dir}"),
ron_plugin_trust_roots: ["{root}"],
ron_plugin_grants: [
( module: "{module}", grants: ["usage.read", "store.list", "audit.write", "clock.read"] ),
],
)"#,
audit = tmp.path().join("audit").display(),
dir = plugins.display(),
root = root.display(),
module = OPEN_GUEST,
);
let path = tmp.path().join("holger.ron");
std::fs::write(&path, ron).unwrap();
let h = read_ron_config(&path).expect("the RON parses");
let census = h.module_census().expect("the census builds");
// The GRANT census fires: the operator granted two of the four.
assert!(
census
.granted_capabilities
.iter()
.any(|g| g == "audit.write"),
"the grant census did not see a grant that was made"
);
// The LINKER census does NOT fire, and that distinction is the whole design:
// the linker is built from what the module IMPORTS intersected with the
// grant, so an over-grant does not widen a module's reach. The two censuses
// therefore answer different questions and neither replaces the other.
let mut defined = census.linker_defines.clone();
defined.sort();
assert_eq!(
defined,
vec!["host_store_list".to_string(), "host_usage_read".to_string()],
"an over-grant widened the linker"
);
}
/// A module with no entry in the grant file gets nothing, and the open guest —
/// which declares two capabilities — is then refused BY NAME at boot rather than
/// loaded with a smaller reach.
#[test]
#[cfg(feature = "wasm-plugins")]
fn a_module_dropped_in_without_a_decision_stops_the_boot() {
let tmp = tempfile::tempdir().unwrap();
let plugins = tmp.path().join("plugins");
std::fs::create_dir_all(&plugins).unwrap();
let root = staged_signed(&plugins);
let ron = format!(
r#"(
repositories: [],
exposed_endpoints: [ (ron_name: "grpc", ron_url: "127.0.0.1:1") ],
storage_endpoints: [],
audit: (dir: "{audit}", enabled: false),
ron_plugin_dir: Some("{dir}"),
ron_plugin_trust_roots: ["{root}"],
)"#,
audit = tmp.path().join("audit").display(),
dir = plugins.display(),
root = root.display(),
);
let path = tmp.path().join("holger.ron");
std::fs::write(&path, ron).unwrap();
let h = read_ron_config(&path).expect("the RON parses");
let err = h
.module_census()
.expect_err("an ungranted module must not load")
.to_string();
assert!(err.contains("store.list"), "{err}");
assert!(err.contains("granted it []"), "{err}");
assert!(err.contains("NOT DEFINED"), "{err}");
}
/// A capability name nobody spells correctly is a boot error listing the
/// vocabulary, not a typo that silently grants less.
#[test]
fn a_typo_in_the_grant_file_stops_the_boot_and_lists_the_vocabulary() {
let tmp = tempfile::tempdir().unwrap();
let ron = r#"(
repositories: [],
exposed_endpoints: [ (ron_name: "grpc", ron_url: "127.0.0.1:1") ],
storage_endpoints: [],
audit: (dir: "audit", enabled: false),
ron_plugin_grants: [ ( module: "allocation.wasm", grants: ["usage.reed"] ) ],
)"#;
let path = tmp.path().join("holger.ron");
std::fs::write(&path, ron).unwrap();
let h = read_ron_config(&path).expect("the RON parses");
let err = h
.module_census()
.expect_err("a typo is a boot error")
.to_string();
assert!(err.contains("usage.reed"), "{err}");
assert!(err.contains("usage.read"), "{err}");
}