server/lib/tests/the_provenance_gate_stands.rs
//! **W17 — a signature that means something.** The four proofs that the
//! promotion boundary now depends on an operator-declared trust root.
//!
//! What was MEASURED on `main` at `bb0302e`, before any line of this landed:
//!
//! * `AttestTrust::from_der_root_files` read each declared path and pushed the
//! BYTES. A zero-byte file, a PEM somebody forgot to convert, a path that
//! resolved to a directory listing — each became a "root" that could never
//! match anything, the boot succeeded, and the estate verified against fewer
//! roots than it had declared. The plugin host has refused exactly this since
//! `holger-plugin-8` (*"refusing rather than verifying against fewer roots than
//! the operator declared"*); the evidence plane had the rule in prose only.
//! * NO shipped row carried a gate named `provenance`. `catalog_from_config`
//! registered `osv`, and `attested` only when `promotion.require_attestation`
//! named a predicate type. A config writing `gates: ["provenance"]` was a BOOT
//! FAILURE on all five server rows — the unit test in `promote/gate.rs`
//! literally used `"provenance"` as its example of a name no build carries.
//!
//! So `attest.rs` verified against roots nobody checked were roots, and the
//! promotion path never asked it anything. Both halves are here.
use holger_server_lib::attest::{ArtifactDigest, AttestTrust, Statement};
use holger_server_lib::errcode;
use holger_server_lib::promote::{
Gate, GateCatalog, PromotedArtifact, PromotionContext, ProvenanceGate, ProvenancePolicy,
};
use std::path::PathBuf;
use std::sync::Arc;
use traits::ArtifactId;
// ── fixtures ─────────────────────────────────────────────────────────────────
/// A REAL DER certificate, minted by `rcgen`, so the positive case is not
/// agreeing with the same structural check the negative cases exercise.
fn real_der_cert() -> Vec<u8> {
let c = rcgen::generate_simple_self_signed(vec!["holger-test-root".to_string()])
.expect("rcgen mints a self-signed certificate");
c.cert.der().to_vec()
}
fn id() -> ArtifactId {
ArtifactId {
namespace: None,
name: "serde".into(),
version: "1.0.0".into(),
}
}
/// An in-toto Statement v1 about `digest`, parsed from real JSON so the test
/// exercises the same door evidence arrives through.
fn slsa_statement_about(digest: &ArtifactDigest) -> Statement {
let json = format!(
r#"{{"_type":"https://in-toto.io/Statement/v1",
"subject":[{{"name":"serde-1.0.0.crate","digest":{{"{}":"{}"}}}}],
"predicateType":"https://slsa.dev/provenance/v1",
"predicate":{{"buildDefinition":{{"buildType":"https://nordisk.dev/nornir/build-thing/variant/mvp-server"}}}}}}"#,
digest.alg(),
digest.hex()
);
Statement::parse(json.as_bytes()).expect("a well-formed in-toto Statement v1")
}
fn art<'a>(id: &'a ArtifactId, bytes: &'a [u8], att: &'a [Statement]) -> PromotedArtifact<'a> {
PromotedArtifact {
id,
ecosystem: "crates.io",
digest: ArtifactDigest::of_sha256(bytes),
bytes,
attestations: att,
}
}
fn hop<'a>(set: &'a [PromotedArtifact<'a>]) -> PromotionContext<'a> {
PromotionContext {
principal: "releaser",
from_repo: "rust-dev",
to_repo: "rust-prod",
hop: None,
set,
}
}
// ── 1. the trust root ────────────────────────────────────────────────────────
/// ★ A path the operator NAMED and this server cannot use is a refusal carrying
/// `holger-attest-10`, and it takes the WHOLE load down with it.
///
/// The last assertion is the one that matters. Loading the good roots and
/// dropping the bad one is the failure this test exists to forbid: the operator
/// declared N anchors, the process would run with N-1, and nothing anywhere
/// would print the difference. "Fewer roots than declared" is a smaller trust
/// set arrived at by accident, which is precisely an empty trust set's smaller
/// cousin.
#[test]
fn an_unreadable_declared_root_is_a_named_refusal_not_an_empty_trust_set() {
let dir = tempfile::tempdir().expect("tempdir");
let good = dir.path().join("root.der");
std::fs::write(&good, real_der_cert()).expect("write the good root");
// The positive case first, so every refusal below is a refusal OF something
// that would otherwise have worked.
let t = AttestTrust::from_der_root_files(std::slice::from_ref(&good))
.expect("a real DER root loads");
assert_eq!(
t.roots().len(),
1,
"the declared root must actually be in the trust set"
);
let empty = dir.path().join("empty.der");
std::fs::write(&empty, b"").expect("write an empty file");
let pem = dir.path().join("root.pem");
std::fs::write(
&pem,
b"-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----\n",
)
.expect("write a PEM file");
let truncated = dir.path().join("cut.der");
let mut cut = real_der_cert();
cut.truncate(cut.len() / 2);
std::fs::write(&truncated, &cut).expect("write a truncated DER");
let absent = dir.path().join("not-here.der");
for (what, path) in [
("an EMPTY file", empty.clone()),
("a PEM file", pem.clone()),
("a TRUNCATED certificate", truncated.clone()),
("an ABSENT path", absent.clone()),
] {
let err = AttestTrust::from_der_root_files(&[path])
.expect_err(&format!("{what} must not become a trust anchor"));
assert_eq!(
err.code().code,
"holger-attest-10",
"{what} must raise the FROZEN trust-root code, not {}: {err}",
err.code().code
);
let tagged = err.tagged();
assert_eq!(
errcode::parse(&tagged).map(|c| c.code),
Some("holger-attest-10"),
"the refusal must be recoverable from the message it rides in: {tagged}"
);
}
// ★ THE ONE THAT MATTERS: one bad root poisons the whole declaration.
let err = AttestTrust::from_der_root_files(&[good.clone(), empty.clone()])
.expect_err("a good root beside a bad one must refuse, not load 1 of 2");
assert_eq!(err.code().code, "holger-attest-10");
assert!(
err.to_string().contains("empty.der"),
"the refusal must NAME the path that failed: {err}"
);
// And the order is irrelevant — a bad root first must not be forgiven by a
// good one after it.
assert!(
AttestTrust::from_der_root_files(&[empty, good]).is_err(),
"the refusal must not depend on the order the operator wrote the paths in"
);
// A `PathBuf` that is a DIRECTORY reads as an I/O failure on every platform
// holger ships to; it must still be the trust-root code, never a bare store
// I/O error the operator will go hunting a full disk for.
let as_dir: PathBuf = dir.path().to_path_buf();
let err =
AttestTrust::from_der_root_files(&[as_dir]).expect_err("a directory is not a certificate");
assert_eq!(err.code().code, "holger-attest-10", "{err}");
}
// ── 2. the gate refuses by name ──────────────────────────────────────────────
/// ★ With a root DECLARED and evidence that does not chain to it, the promotion
/// is refused by name and the refusal names the bytes.
///
/// Everything in the evidence plane got there through `AttestStore::accept`,
/// which verifies the DSSE signature against the declared roots and the enrolled
/// signer before a byte is written. So "no statement filed about this digest" IS
/// "nothing chains these bytes to the declared root" — the gate enforces the
/// consequence, which is the half that had no caller.
#[test]
fn a_promotion_whose_evidence_does_not_chain_to_the_declared_root_is_refused_by_name() {
let gate = ProvenanceGate::new(ProvenancePolicy::Require, 1);
let a = id();
// (a) No evidence at all.
let none: [Statement; 0] = [];
let set = [art(&a, b"the promoted bytes", &none)];
let v = gate.check(&hop(&set)).expect("the gate decides");
assert!(
!v.is_allowed(),
"an artifact with no provenance must not be promoted"
);
let reason = format!("{v:?}");
assert_eq!(
errcode::parse(&reason).map(|c| c.code),
Some("holger-promote-10"),
"the refusal must carry the frozen code: {reason}"
);
assert!(
reason.contains(ArtifactDigest::of_sha256(b"the promoted bytes").hex()),
"the refusal must name the BYTES, not just the coordinate: {reason}"
);
assert_eq!(
v.subject(),
Some("__serde__1.0.0"),
"the refusal must name the subject: {v:?}"
);
// (b) Evidence about the RIGHT bytes: allowed.
let mine = [slsa_statement_about(&ArtifactDigest::of_sha256(
b"the promoted bytes",
))];
let set = [art(&a, b"the promoted bytes", &mine)];
assert!(
gate.check(&hop(&set))
.expect("the gate decides")
.is_allowed(),
"a verified provenance statement about these bytes must let the hop through"
);
// (c) ★ Evidence about SOMEBODY ELSE'S bytes: still refused. A genuine
// signature over a different artifact is not evidence for this one — the
// same attack `holger-attest-3` names, arriving one layer up.
let theirs = [slsa_statement_about(&ArtifactDigest::of_sha256(
b"a different build",
))];
let set = [art(&a, b"the promoted bytes", &theirs)];
let v = gate.check(&hop(&set)).expect("the gate decides");
assert!(
!v.is_allowed(),
"a statement about other bytes must not promote these ones: {v:?}"
);
// (d) A set of two where only ONE is unattested: the whole hop is refused,
// and the refusal names the offender rather than the repository.
let b = ArtifactId {
namespace: None,
name: "rand".into(),
version: "0.8.5".into(),
};
let ok = [slsa_statement_about(&ArtifactDigest::of_sha256(b"good"))];
let set = [art(&a, b"good", &ok), art(&b, b"bad", &none)];
let v = gate.check(&hop(&set)).expect("the gate decides");
assert!(!v.is_allowed());
assert_eq!(v.subject(), Some("__rand__0.8.5"), "{v:?}");
// Through a resolved GateSet, which is how the server reaches it: the
// Decision records that the gate RAN, so an allow is evidence of a check
// rather than evidence that nothing was configured.
let cat = GateCatalog::new().with(Arc::new(ProvenanceGate::new(ProvenancePolicy::Require, 1)));
let gs = cat
.resolve(&["provenance".to_string()])
.expect("the name resolves on this build");
let set = [art(&a, b"unsigned", &none)];
let d = gs.evaluate(&hop(&set));
assert!(!d.is_allowed());
assert!(
!d.undecided(),
"this is a policy refusal, not a broken gate: {}",
d.summary()
);
assert_eq!(d.gates_run, vec!["provenance"]);
}
// ── 3. an estate with no root ────────────────────────────────────────────────
/// ★ An estate that declares NO trust root must say so — and must never print
/// the sentence an estate that chose `Off` prints.
///
/// This is the `Warn` default's whole defence. `Warn` refuses nothing, so the
/// only thing separating it from `Off` is that it SAYS what `Require` would have
/// refused, by frozen code, on every hop. If the two were indistinguishable,
/// an estate that never got round to signing anything would read exactly like
/// one that considered the question and declined — the defect `promote/gate.rs`
/// was written against, wearing a new coat.
#[test]
fn an_estate_that_declares_no_root_says_so_and_does_not_verify_everything() {
let a = id();
let none: [Statement; 0] = [];
let set = [art(&a, b"never signed", &none)];
// WARN, no root: the hop proceeds and the finding is on the record.
let warn = ProvenanceGate::new(ProvenancePolicy::Warn, 0);
assert_eq!(warn.declared_roots(), 0);
assert!(
warn.check(&hop(&set))
.expect("the gate decides")
.is_allowed(),
"Warn must not refuse; that is the whole difference from Require"
);
let findings = warn.findings(&hop(&set));
assert!(
!findings.is_empty(),
"Warn that finds nothing to warn about has silently become Off"
);
assert_eq!(
errcode::parse(&findings.join(" ")).map(|c| c.code),
Some("holger-attest-11"),
"the finding must carry the frozen no-root code: {findings:?}"
);
let warn_posture = warn.posture();
assert!(
warn_posture.contains("NO attestation trust root"),
"the posture must state the absence in words: {warn_posture}"
);
assert!(
warn_posture.contains("holger-attest-11"),
"and carry the code: {warn_posture}"
);
// OFF, no root: also allows — and says something DIFFERENT.
let off = ProvenanceGate::new(ProvenancePolicy::Off, 0);
assert!(off
.check(&hop(&set))
.expect("the gate decides")
.is_allowed());
assert!(
off.findings(&hop(&set)).is_empty(),
"Off has nothing to find; it was told not to look"
);
let off_posture = off.posture();
assert_ne!(
warn_posture, off_posture,
"an absent key must NEVER read as `provenance: Off`"
);
assert!(
off_posture.contains("DECLARED"),
"Off must read as a decision on the record: {off_posture}"
);
assert!(
!off_posture.contains("holger-attest-11"),
"Off is not a finding; it must not borrow the finding's code: {off_posture}"
);
// REQUIRE, no root: refuses EVERY hop, and blames the server rather than the
// artifact. A `Require` that cannot verify is never a `Require` that passes.
let req = ProvenanceGate::new(ProvenancePolicy::Require, 0);
let v = req.check(&hop(&set)).expect("the gate decides");
assert!(
!v.is_allowed(),
"Require with nothing to chain to must refuse"
);
assert_eq!(
v.subject(),
None,
"with no root declared the fault is the server's, not any one artifact's: {v:?}"
);
let reason = format!("{v:?}");
assert_eq!(
errcode::parse(&reason).map(|c| c.code),
Some("holger-attest-11"),
"not holger-promote-10: the fix is a config key, not a re-signed build: {reason}"
);
// ★ And with no root declared, a FULLY ATTESTED artifact is not waved
// through either. Nothing could have been verified, so nothing was.
let att = [slsa_statement_about(&ArtifactDigest::of_sha256(
b"never signed",
))];
let set = [art(&a, b"never signed", &att)];
assert!(
!req.check(&hop(&set))
.expect("the gate decides")
.is_allowed(),
"with no declared root there is nothing for a statement to chain TO, so a \
statement must not be mistaken for evidence"
);
assert!(
ProvenancePolicy::default() == ProvenancePolicy::Warn,
"the defended default is Warn; see ProvenancePolicy's docs"
);
}