server/lib/tests/tier1_sources_steps_rules.rs
//! **Tier 1's RON: `sources:`, `steps:` and `rules:`, on a real server** (Holger G0,
//! owner order 2026-10-23).
//!
//! * Two upload sources with different gates: the SAME package is refused from one and
//! passes from the other, and the refusal names the rule and the source.
//! * The floor (`rules: (require_signed: true)`) refuses an unsigned package from every
//! source.
//! * A source cannot loosen the floor: the config is refused at load, by name.
//! * A source can tighten the floor: its gate refuses what the floor lets in.
//! * The artifact's source and effective policy are in GetArtifactState and in the audit
//! trail.
//! * Unknown keys in every block, and `steps:` beside `promotion:`, are refused by name.
// A server's test, never a gateway build's (lane HOL-CLEANUP): a gateway build is a
// downstream, and its sources fetch and its steps scan, which a downstream refuses (holger-downstream-1).
#![cfg(not(feature = "gateway"))]
use std::io::Write;
use traits::test_support::hold_port;
use holger_server_lib::grpc::holger_proto::{
AdvanceArtifactRequest, ArtifactId as ProtoId, GetArtifactStateRequest, IntakeVerb,
};
use holger_server_lib::promote::duties::client;
use holger_server_lib::promote::intake::ArtifactState;
use holger_server_lib::{read_ron_config, wire_holger, ArtifactId, Holger};
const PRINCIPALS: [&str; 5] = ["ida", "ci", "bob", "adm", "aud"];
fn token(who: &str) -> String {
format!("{who}-token-of-sufficient-length-for-holger")
}
struct Running {
data: tempfile::TempDir,
_cfg: tempfile::NamedTempFile,
holger: Holger,
base: String,
grpc: String,
}
/// The whole config: four raw intake repositories, the principals, and the tier 1 blocks.
fn ron(d: &str, grpc: u16, http: u16, tier1: &str) -> String {
let repo = |name: &str| {
format!(
r#"(ron_name: "{name}", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "door")),
ron_data_dir: Some("{d}/{name}")),"#
)
};
let methods: Vec<String> = PRINCIPALS
.iter()
.map(|p| {
format!(
r#"StaticToken(subject: "{p}", token: Some("{}"))"#,
token(p)
)
})
.collect();
format!(
r#"(
repositories: [ {} {} {} {} ],
exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{http}")) ],
storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{d}") ],
audit: (dir: "{d}/audit", enabled: true),
auth: Some((
methods: [ {} ],
duties: {{ "ida": [Importer], "ci": [Importer], "bob": [SecurityApprover], "adm": [RepositoryAdmin], "aud": [Auditor] }},
)),
{tier1}
)"#,
repo("incoming"),
repo("quarantine"),
repo("approved"),
repo("production"),
methods.join(", "),
)
}
fn load(tier1: &str) -> anyhow::Result<Holger> {
let data = tempfile::tempdir().unwrap();
let mut cfg = tempfile::NamedTempFile::new().unwrap();
cfg.write_all(ron(&data.path().display().to_string(), 1, 2, tier1).as_bytes())
.unwrap();
read_ron_config(cfg.path())
}
fn refused(tier1: &str) -> String {
match load(tier1) {
Ok(_) => panic!("must be refused: {tier1}"),
Err(e) => format!("{e:#}"),
}
}
fn start(tier1: &str) -> Running {
let mut last = String::new();
for _ in 0..8 {
let (grpc, http) = (hold_port(), hold_port());
let data = tempfile::tempdir().unwrap();
let mut cfg = tempfile::NamedTempFile::new().unwrap();
cfg.write_all(ron(&data.path().display().to_string(), grpc, http, tier1).as_bytes())
.unwrap();
let go = || -> Result<Holger, String> {
let mut h = read_ron_config(cfg.path()).map_err(|e| format!("config: {e:#}"))?;
h.instantiate_backends()
.map_err(|e| format!("backends: {e:#}"))?;
wire_holger(&mut h).map_err(|e| format!("wire: {e:#}"))?;
h.start().map_err(|e| format!("start: {e:#}"))?;
Ok(h)
};
match go() {
Ok(holger) => {
return Running {
data,
_cfg: cfg,
holger,
base: format!("http://127.0.0.1:{http}"),
grpc: format!("127.0.0.1:{grpc}"),
}
}
Err(e) if e.starts_with("config") => panic!("{e}"),
Err(e) => last = e,
}
}
panic!("server never started; last error: {last}");
}
fn wheel() -> Vec<u8> {
zip_entry::fixture::zip(&[
("demo_pkg/__init__.py", b"".as_slice()),
(
"demo_pkg-1.0.0.dist-info/METADATA",
b"Metadata-Version: 2.1\nName: demo-pkg\nVersion: 1.0.0\nLicense-Expression: MIT\n"
.as_slice(),
),
])
}
const WHEEL: &str = "demo_pkg-1.0.0-py3-none-any.whl";
fn id(path: &str) -> ArtifactId {
ArtifactId {
namespace: None,
name: path.into(),
version: String::new(),
}
}
async fn wait_healthy(c: &reqwest::Client, base: &str) {
for _ in 0..100 {
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 put(r: &Running, who: &str, path: &str) -> (u16, String) {
put_bytes(r, who, path, wheel()).await
}
async fn put_bytes(r: &Running, who: &str, path: &str, body: Vec<u8>) -> (u16, String) {
let _ = rustls::crypto::ring::default_provider().install_default();
let c = reqwest::Client::new();
wait_healthy(&c, &r.base).await;
let resp = c
.put(format!("{}/incoming/{path}", r.base))
.header("Authorization", format!("Bearer {}", token(who)))
.body(body)
.send()
.await
.unwrap();
(
resp.status().as_u16(),
resp.text().await.unwrap_or_default(),
)
}
/// INGESTED → SCANNED by the approver, through the real door
/// (`PromotionService.AdvanceArtifact` over gRPC): the source's gates refuse there.
async fn scan(r: &Running, path: &str) -> Result<(), String> {
let req = AdvanceArtifactRequest {
artifact: Some(ProtoId {
namespace: String::new(),
name: path.into(),
version: String::new(),
}),
verb: IntakeVerb::Advance as i32,
..Default::default()
};
let a = client::advance(&r.grpc, Some(&token("bob")), req).await?;
if a.state == ArtifactState::Scanned.name() {
Ok(())
} else {
Err(format!("the door answered {} for {path}", a.state))
}
}
fn audit_text(r: &Running) -> String {
let mut out = String::new();
for e in std::fs::read_dir(r.data.path().join("audit"))
.unwrap()
.flatten()
{
out.push_str(&String::from_utf8_lossy(
&std::fs::read(e.path()).unwrap_or_default(),
));
}
out
}
#[tokio::test(flavor = "multi_thread")]
async fn the_same_package_is_refused_from_one_source_and_passed_from_the_other() {
let r = start(
r#"sources: [
(name: "team", kind: upload, gates: (provenance: prefer)),
(name: "vendor", kind: upload, importers: ["ci"], gates: (license: (deny: ["MIT"]))),
],
steps: (unknown_license: Pass),
rules: (sandbox: off, require_signed: false, require_provenance: false),"#,
);
let (team, vendor) = (format!("team/{WHEEL}"), format!("vendor/{WHEEL}"));
assert_eq!(put(&r, "ida", &team).await.0, 201, "team ingests");
let (code, body) = put(&r, "ida", &vendor).await;
assert!(
code >= 400 && body.contains("may not upload as source 'vendor'"),
"{code} {body}"
);
assert_eq!(
put(&r, "ci", &vendor).await.0,
201,
"vendor's own importer ingests"
);
let (code, body) = put(&r, "ida", &format!("nobody/{WHEEL}")).await;
assert!(
code >= 400 && body.contains("names no upload source"),
"{code} {body}"
);
scan(&r, &team)
.await
.expect("the team's gates pass the MIT wheel");
let why = scan(&r, &vendor)
.await
.expect_err("the vendor's gate refuses MIT");
assert!(
why.contains("deny_licenses") && why.contains("source 'vendor'") && why.contains("MIT"),
"{why}"
);
let chain = r.holger.intake().unwrap();
assert_eq!(chain.state(&id(&team)), ArtifactState::Scanned);
assert_eq!(
chain.state(&id(&vendor)),
ArtifactState::Ingested,
"a refusal moves nothing"
);
// The source and the effective policy are in GetArtifactState …
let s = client::state(
&r.grpc,
Some(&token("aud")),
GetArtifactStateRequest {
artifact: Some(ProtoId {
namespace: String::new(),
name: vendor.clone(),
version: String::new(),
}),
},
)
.await
.expect("an auditor reads the state");
assert_eq!(s.source, "vendor");
let p: serde_json::Value =
serde_json::from_str(&s.effective_policy).expect("the policy is JSON");
assert_eq!(p["source"], "vendor");
assert!(
p["deny_licenses"]
.as_array()
.unwrap()
.iter()
.any(|b| b["value"] == "MIT" && b["by"] == "source 'vendor'"),
"{p}"
);
// … and in the audit trail, with the rule that refused it.
let audit = audit_text(&r);
assert!(
audit.contains("source=vendor"),
"the ingest names its source and policy"
);
assert!(
audit.contains("deny_licenses") && audit.contains("source 'vendor'"),
"the refusal names its rule"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn the_floor_refuses_an_unsigned_package_from_every_source() {
let r = start(
r#"sources: [ (name: "team", kind: upload), (name: "vendor", kind: upload, gates: (cve: (max: medium))) ],
steps: (unknown_license: Pass),
rules: (sandbox: off, require_signed: true),"#,
);
for src in ["team", "vendor"] {
let path = format!("{src}/{WHEEL}");
assert_eq!(put(&r, "ida", &path).await.0, 201);
let why = scan(&r, &path).await.expect_err("unsigned is refused");
assert!(
why.contains("require_signed: true") && why.contains(&format!("source '{src}'")),
"{src}: {why}"
);
}
}
#[tokio::test(flavor = "multi_thread")]
async fn a_source_can_tighten_the_floor() {
let r = start(
r#"sources: [ (name: "team", kind: upload), (name: "strict", kind: upload, gates: (block_formats: ["whl"], sbom: require)) ],
steps: (unknown_license: Pass),
rules: (sandbox: off, require_signed: false, require_provenance: false),"#,
);
assert_eq!(
put(&r, "ida", &format!("team/{WHEEL}")).await.0,
201,
"the floor lets a wheel in"
);
let (code, body) = put(&r, "ida", &format!("strict/{WHEEL}")).await;
assert!(code >= 400, "{code}");
assert!(
body.contains("block_formats") && body.contains("source 'strict'"),
"{body}"
);
}
/// The wheel with a planted GitHub token (assembled at run time, never whole in
/// this file) and a planted ACME token for an operator's own rule.
fn leaky_wheel() -> (Vec<u8>, String) {
let gh = ["ghp_", "R8kLm2Qx9Tz4Vb7Nc1Wd5Ys3Hf6Jg0Pa2Ue8"].concat();
let acme = ["acme_", "0123456789abcdef"].concat();
let code = format!("TOKEN = '{gh}'\nOTHER = '{acme}'\n");
let w = zip_entry::fixture::zip(&[
("demo_pkg/__init__.py", code.as_bytes()),
(
"demo_pkg-1.0.0.dist-info/METADATA",
b"Metadata-Version: 2.1\nName: demo-pkg\nVersion: 1.0.0\nLicense-Expression: MIT\n"
.as_slice(),
),
]);
(w, gh)
}
#[tokio::test(flavor = "multi_thread")]
async fn a_planted_secret_is_refused_where_the_source_blocks_and_recorded_where_it_warns() {
let r = start(
r#"sources: [ (name: "team", kind: upload), (name: "vendor", kind: upload, gates: (secrets: block)) ],
steps: (unknown_license: Pass),
rules: (sandbox: off, require_signed: false, require_provenance: false, secrets: warn),"#,
);
let (w, gh) = leaky_wheel();
let (team, vendor) = (format!("team/{WHEEL}"), format!("vendor/{WHEEL}"));
assert_eq!(put_bytes(&r, "ida", &team, w.clone()).await.0, 201);
assert_eq!(put_bytes(&r, "ida", &vendor, w).await.0, 201);
scan(&r, &team)
.await
.expect("the floor only warns about secrets");
let why = scan(&r, &vendor)
.await
.expect_err("the vendor's gate blocks secrets");
assert!(
why.contains("secrets: block")
&& why.contains("source 'vendor'")
&& why.contains("demo_pkg/__init__.py:1"),
"{why}"
);
assert!(
!why.contains(&gh[..12]),
"the refusal never holds the secret: {why}"
);
let chain = r.holger.intake().unwrap();
assert_eq!(chain.state(&id(&team)), ArtifactState::Scanned);
assert_eq!(chain.state(&id(&vendor)), ArtifactState::Ingested);
let audit = audit_text(&r);
assert!(
audit.contains("secrets=block(source 'vendor')"),
"the ingest names the secrets rule"
);
assert!(
!audit.contains(&gh[..12]),
"the audit trail never holds the secret"
);
// The team's warning is on its stored evidence, by rule and path.
let ev = std::fs::read_dir(r.data.path().join("holger-scan-evidence"))
.map(|d| d.flatten().map(|e| e.path()).collect::<Vec<_>>())
.unwrap_or_default();
let mut text = String::new();
fn walk(p: &std::path::Path, out: &mut String) {
if p.is_dir() {
for e in std::fs::read_dir(p).unwrap().flatten() {
walk(&e.path(), out);
}
} else {
out.push_str(&String::from_utf8_lossy(
&std::fs::read(p).unwrap_or_default(),
));
}
}
for p in ev {
walk(&p, &mut text);
}
assert!(
text.contains("secret:github-token"),
"the evidence names the rule"
);
assert!(
!text.contains(&gh[..12]),
"the evidence never holds the secret"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn the_default_floor_blocks_secrets_and_an_operators_rule_file_is_data() {
let rules_dir = tempfile::tempdir().unwrap();
let file = rules_dir.path().join("site-secrets.ron");
std::fs::write(
&file,
r#"(schema: "holger.secret-rules/v1", version: "site-1",
rules: [(id: "acme-token", title: "an ACME token", severity: critical, pattern: "acme_[a-z0-9]{16}")])"#,
)
.unwrap();
let r = start(&format!(
r#"sources: [ (name: "team", kind: upload) ],
steps: (unknown_license: Pass, secrets: (rules: "{}")),
rules: (sandbox: off, require_signed: false, require_provenance: false),"#,
file.display()
));
let path = format!("team/{WHEEL}");
assert_eq!(put_bytes(&r, "ida", &path, leaky_wheel().0).await.0, 201);
let why = scan(&r, &path)
.await
.expect_err("the floor's default is `secrets: block`");
assert!(
why.contains("secrets: block") && why.contains("(floor)") && why.contains("an ACME token"),
"{why}"
);
assert!(
!why.contains("GitHub"),
"the site's file replaces the built-in rules: {why}"
);
}
#[test]
fn a_source_cannot_loosen_the_floor() {
let e = refused(
r#"sources: [ (name: "pypi", kind: upload, gates: (provenance: off)) ],
steps: (),
rules: (sandbox: off, require_signed: true),"#,
);
assert!(
e.contains("source 'pypi'") && e.contains("loosens") && e.contains("require_signed: true"),
"{e}"
);
let e = refused(r#"sources: [ (name: "s", kind: upload, gates: (malware: off)) ], steps: (),"#);
assert!(
e.contains("malware: on"),
"the default floor holds too: {e}"
);
}
#[test]
fn the_tier1_blocks_refuse_what_they_cannot_mean_by_name() {
for (tier1, want) in [
(
r#"sources: [ (name: "a", kind: upload, gatez: ()) ], steps: (),"#,
"gatez",
),
(r#"steps: (aprovals: 2),"#, "aprovals"),
(
r#"steps: (), rules: (sandbox: off, requre_signed: true),"#,
"requre_signed",
),
(r#"steps: (gates: (cev: (max: low))),"#, "cev"),
// A remote kind pulls into the steps, which only a `jobs` build carries: without it
// the source is refused by name for the build it needs, before its fields are read.
(
r#"sources: [ (name: "p", kind: npm) ], steps: (),"#,
if cfg!(feature = "jobs") {
"needs `url`"
} else {
"needs the `online` and `jobs` build"
},
),
(
r#"sources: [ (name: "c", kind: crates, url: "https://index.crates.io/", serves: ["cargo"]) ], steps: (),"#,
"keep one",
),
(r#"rules: (sandbox: off),"#, "no `steps:`"),
(
r#"steps: (), promotion: Some((ladder_name: "x", stages: [])),"#,
"both `steps:` and `promotion:`",
),
(
r#"sources: [ (name: "up", kind: holger, url: "http://h:50051") ], steps: (),"#,
"allow_unchecked_sources",
),
] {
let e = refused(tier1);
assert!(e.contains(want), "{tier1}\n wants {want:?}\n got {e}");
}
}
#[test]
fn the_steps_lower_onto_the_intake_chain() {
let h = load("steps: (approvals: 2),").expect("loads");
let p = h.promotion.as_ref().expect("steps became promotion");
let i = p.intake.as_ref().expect("with its intake block");
assert_eq!(
i.repos(),
["incoming", "quarantine", "approved", "production"]
);
assert_eq!(i.approvals, 2);
let stages: Vec<&str> = p.stages.iter().map(|s| s.repo.as_str()).collect();
assert_eq!(stages, ["incoming", "quarantine", "approved", "production"]);
}
/// **`python` and `oci` sources pull INTO the steps** (the pull-through lane's remote
/// kinds): they lower onto `pull_through`, keep their gates and auto-approval, and the
/// default floor's `allow_unchecked_sources: false` accepts them.
#[cfg(feature = "jobs")]
#[test]
fn python_and_oci_sources_lower_onto_the_pull_through() {
let h = load(
r#"sources: [
(name: "pypi", kind: python, url: "https://pypi.org/simple/", gates: (cve: (max: medium)),
auto_approve: when(provenance: require, cve_max: low)),
(name: "cgr", kind: oci, url: "https://cgr.dev", platforms: ["linux/amd64"],
gates: (provenance: require(identity: (issuer: "https://token.actions.githubusercontent.com", subject: "repo:chainguard-images/images"), slsa_min: 2))),
],
steps: (),
rules: (sandbox: off, allow_auto_approve: true),"#,
)
.expect("the remote kinds load");
let pt = h
.pull_through
.as_ref()
.expect("lowered onto the pull-through");
let names: Vec<&str> = pt.sources.iter().map(|s| s.name.as_str()).collect();
assert_eq!(names, ["pypi", "cgr"]);
assert!(
pt.rules.allow_auto_approve && pt.rules.require_signed,
"the floor came along"
);
let pypi = &pt.sources[0];
assert_eq!(
pypi.gates.cve.map(|c| c.max),
Some(holger_server_lib::scanner::FindingSeverity::Medium)
);
assert!(
format!("{:?}", pypi.auto_approve).contains("Low"),
"{:?}",
pypi.auto_approve
);
assert_eq!(pt.sources[1].platforms, ["linux/amd64"]);
let pol = h.source_policies.as_ref().unwrap();
assert_eq!(
pol.resolve("pull-through:pypi", "x").unwrap().source,
"pypi"
);
assert_eq!(pol.remote("cgr").unwrap().slsa_min.value, 2);
// The floor forbids auto-approval unless it says otherwise.
let e = refused(
r#"sources: [ (name: "pypi", kind: python, url: "https://pypi.org/simple/", auto_approve: when(cve_max: low)) ], steps: (),"#,
);
assert!(e.contains("allow_auto_approve"), "{e}");
}
/// ★ A29 row 11 (owner): **a container image is NEVER auto-approved.** An `oci` source in
/// `sources:` that asks `auto_approve: when(...)` beside a permissive floor is refused by
/// name when the config is READ, so no view of the loaded config (the effective sources,
/// the security page) ever shows an image source that approves itself.
#[cfg(feature = "jobs")]
#[test]
fn an_oci_source_asking_auto_approval_is_refused_at_load() {
let e = refused(
r#"sources: [
(name: "cgr", kind: oci, url: "https://cgr.dev", platforms: ["linux/amd64"],
auto_approve: when(provenance: require, cve_max: low)),
],
steps: (),
rules: (sandbox: off, allow_auto_approve: true),"#,
);
assert!(
e.contains("never auto-approved") && e.contains("cgr"),
"the refusal names the rule and the source: {e}"
);
}
/// **`npm`, `maven` and `crates` (with `url`) pull INTO the steps too** (lane
/// HOLGER-PT2): they lower onto `pull_through` with the floor's keys and builders,
/// and npm is no longer refused by name.
#[cfg(all(feature = "jobs", feature = "online"))]
#[test]
fn npm_maven_and_crates_sources_lower_onto_the_pull_through() {
let h = load(
r#"sources: [
(name: "npmjs", kind: npm, url: "https://registry.npmjs.org/"),
(name: "cg-js", kind: npm, url: "https://libraries.cgr.dev/javascript/",
credential: (username: "id", password_file: "/dev/null")),
(name: "central", kind: maven, url: "https://repo1.maven.org/maven2/"),
(name: "crates-io", kind: crates, url: "https://index.crates.io/"),
],
steps: (),
rules: (sandbox: off, keys: [(id: "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U", alg: ecdsa_p256,
public_key: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY6Ya7W++7aUPzvMTrezH6Ycx3c+HOKYCcNGybJZSCJq/fd7Qa8uuAKtdIkUQtQiEKERhAmE5lMMJhP8OkDOa2g==")]),"#,
)
.expect("the remote kinds load");
let pt = h
.pull_through
.as_ref()
.expect("lowered onto the pull-through");
let kinds: Vec<&str> = pt.sources.iter().map(|s| s.kind.name()).collect();
assert_eq!(kinds, ["npm", "npm", "maven", "crates"]);
assert_eq!(pt.rules.keys.len(), 1, "the floor's keys came along");
// A `crates` source with `url` is not the old rust-remote: no repository was made for it.
assert!(!h.repositories.iter().any(|r| r.ron_name == "crates-io"));
}
/// **`apk` with `url` pulls INTO the steps** (lane T8): it lowers onto the
/// pull-through as kind `apk`, its `apk_keys` gate carried; `apk` without `url` is
/// still the upload source whose index waits in the inbox.
#[cfg(all(feature = "jobs", feature = "online"))]
#[test]
fn an_apk_source_with_a_url_lowers_onto_the_pull_through() {
let h = load(
r#"sources: [
(name: "wolfi", kind: apk, url: "https://packages.wolfi.dev/os",
gates: (provenance: require(apk_keys: ["wolfi-signing.rsa.pub"]))),
(name: "pushed", kind: apk),
],
steps: (),
rules: (sandbox: off),"#,
)
.expect("an apk pull-through loads");
let pt = h
.pull_through
.as_ref()
.expect("lowered onto the pull-through");
assert_eq!(pt.sources.len(), 1, "only the source with a url pulls");
assert_eq!(
(pt.sources[0].name.as_str(), pt.sources[0].kind.name()),
("wolfi", "apk")
);
let g = format!("{:?}", pt.sources[0].gates.provenance);
assert!(g.contains("wolfi-signing.rsa.pub"), "{g}");
let e = refused(
r#"sources: [ (name: "w", kind: apk, url: "https://packages.wolfi.dev/os", importers: ["ida"]) ], steps: (),"#,
);
assert!(
e.contains("importers"),
"a pulled source has no importers: {e}"
);
}