server/lib/tests/keyless_cosign_offline_through_holger.rs
//! **Keyless cosign, offline** (lane HOL-OCI-EVIDENCE item 4, owner 2026-10-31). HOL-REFERRERS
//! tested cosign with keys only: keyless needs a Sigstore, and Holger's side is airgapped.
//!
//! A Sigstore in miniature is minted in the test (`provenance/tests/common`: a Fulcio-like CA
//! issuing a leaf with the OIDC issuer extension and an embedded SCT from a test CT log, and a
//! Rekor-like log key that signs the entry's SET), and pinned in Holger as a signed
//! `holger.sigstore-trust/v1` bundle. An image is signed KEYLESS the way `cosign sign` writes
//! it (the `.sig` tag: a simple-signing payload, the leaf and chain, the Rekor bundle with a
//! `hashedrekord` body), pushed with the image to Holger's scanned `oci` registry, and:
//!
//! * Holger verifies it ON INTAKE, offline, under the provenance floor (`require_signed`, the
//! keyless `identities: [(issuer, subject)]`, the pinned trust root): the image walks
//! SCANNED with the provenance slot passed and waits for its approvals;
//! * a signature by another identity, and an image with no signature, fail the push itself
//! (the identity refusal `holger-provenance-5`, the floor's `require_signed`);
//! * the customer's `cosign verify --certificate-identity … --certificate-oidc-issuer …`
//! passes THROUGH Holger with that test root (`SIGSTORE_ROOT_FILE`, the Rekor and CT keys,
//! `--offline`), and fails for another identity — the ignored real-client test, run by the
//! full CI set's `real-clients` step (cosign 2.4.3 and 3.0.2 in containers).
#![cfg(all(feature = "supply-chain", feature = "jobs", feature = "sign"))]
mod oci_evidence;
use std::path::{Path, PathBuf};
use std::process::Command;
use holger_server_lib::promote::intake::{ArtifactState, EvidenceKind, EvidenceVerdict};
use oci_evidence::keyless::{keyless_sig, push_signed, sigstore, trust, Trust};
use oci_evidence::*;
use sigstore::MiniSigstore;
const ISSUER: &str = "https://token.actions.example.test";
const SUBJECT: &str =
"https://git.example.test/team/app/.github/workflows/release.yaml@refs/heads/main";
const STRANGER: &str =
"https://git.example.test/someone/else/.github/workflows/release.yaml@refs/heads/main";
/// A Holger whose intake requires a keyless signature by (ISSUER, SUBJECT).
fn holger(t: &Trust) -> Running {
start(
&Boot {
oci_repo: true,
approvals: 2,
scan: Some(format!(
r#"Some((required: [], provenance: Some((
rules: (require_signed: true, identities: [(issuer: "{ISSUER}", subject: "{SUBJECT}")], trust_root: Some("{}")),
))))"#,
t.bundle
)),
intake_extra: format!(r#"trust_policy: Some("{}"),"#, t.policy),
..Default::default()
},
None,
)
}
#[test]
fn holger_verifies_a_keyless_signed_image_on_intake_offline() {
let ms = MiniSigstore::new();
let t = trust(&ms);
let r = holger(&t);
let host = format!("127.0.0.1:{}", r.http);
// Signed by the identity the registry's gate names: through to POLICY_CHECKED, provenance passed.
let img = demo_image("keyless");
let sig = keyless_sig(
&ms,
&format!("{host}/oci/demo/keyless"),
&img.digest,
ISSUER,
SUBJECT,
);
let (s, h, b) = push_signed(&r, "demo/keyless", &img, Some(sig.clone()));
assert_eq!(s, 201, "{}", String::from_utf8_lossy(&b));
assert_eq!(
header(&h, "x-holger-intake-state").as_deref(),
Some("policy_checked"),
"{h}"
);
let chain = r.chain();
let id = layout_in(&chain, "oci/demo/keyless/", ArtifactState::PolicyChecked);
let scanned = chain
.history(&id)
.into_iter()
.find(|e| e.to == ArtifactState::Scanned)
.unwrap();
let prov = scanned
.evidence
.iter()
.find(|e| e.kind == EvidenceKind::Provenance)
.expect("a provenance slot");
assert_eq!(prov.verdict, EvidenceVerdict::Pass, "{prov:?}");
approve(&chain, &id, &["bob", "carol"]);
let (s, _, got) = request(
r.http,
"GET",
"/v2/oci/demo/keyless/manifests/v1",
&[("Accept", MANIFEST)],
b"",
);
assert_eq!(s, 200);
assert_eq!(got, img.manifest);
let tag = format!("sha256-{}.sig", img.digest.trim_start_matches("sha256:"));
assert_eq!(
request(
r.http,
"GET",
&format!("/v2/oci/demo/keyless/manifests/{tag}"),
&[("Accept", MANIFEST)],
b""
)
.2,
sig.0,
"the signature is served beside it"
);
// Signed by someone else: the push fails with the identity refusal.
let other = demo_image("stranger");
let sig = keyless_sig(
&ms,
&format!("{host}/oci/demo/stranger"),
&other.digest,
ISSUER,
STRANGER,
);
let (s, _, b) = push_signed(&r, "demo/stranger", &other, Some(sig));
let text = String::from_utf8_lossy(&b).to_string();
assert_eq!(s, 403, "{text}");
assert!(
text.contains("holger-provenance-5"),
"the identity refusal is named: {text}"
);
// Not signed at all: the floor's require_signed.
let bare = demo_image("unsigned");
let (s, _, b) = push_signed(&r, "demo/unsigned", &bare, None);
assert_eq!(s, 403, "{}", String::from_utf8_lossy(&b));
assert_eq!(
request(
r.http,
"GET",
"/v2/oci/demo/unsigned/manifests/v1",
&[("Accept", MANIFEST)],
b""
)
.0,
403
);
}
// ── the customer's cosign, through Holger, with the test root ───────────────
const COSIGN2: &str = "gcr.io/projectsigstore/cosign:v2.4.3";
const COSIGN3: &str = "ghcr.io/sigstore/cosign/cosign:v3.0.2";
fn podman() -> PathBuf {
let path = std::env::var_os("PATH").unwrap_or_default();
std::env::split_paths(&path)
.map(|d| d.join("podman"))
.find(|c| c.is_file())
.expect("podman not on PATH: this test drives REAL cosign in containers")
}
/// `podman run --rm --network host` of `image` with `args`, `/out` mounted. `(succeeded, output)`.
fn run(image: &str, out: &Path, env: &[(&str, &str)], args: &[&str]) -> (bool, String) {
let mut c = Command::new(podman());
c.args(["run", "--rm", "--network", "host", "-v"])
.arg(format!("{}:/out:Z", out.display()));
c.args([
"-e",
"HOME=/out/home",
"-e",
"TUF_ROOT=/out/home/.sigstore/root",
"-e",
"DOCKER_CONFIG=/out/docker",
]);
for (k, v) in env {
c.args(["-e", &format!("{k}={v}")]);
}
c.arg(image).args(args);
let o = c
.output()
.unwrap_or_else(|e| panic!("cannot run podman: {e}"));
(
o.status.success(),
format!(
"{}{}",
String::from_utf8_lossy(&o.stdout),
String::from_utf8_lossy(&o.stderr)
),
)
}
#[ignore = "needs podman and the cosign images: the full CI set's real-clients step runs it"]
#[test]
fn real_cosign_verifies_a_keyless_image_through_holger_with_the_test_root() {
let ms = MiniSigstore::new();
let t = trust(&ms);
let r = holger(&t);
let host = format!("127.0.0.1:{}", r.http);
let img = demo_image("customer");
let sig = keyless_sig(
&ms,
&format!("{host}/oci/demo/customer"),
&img.digest,
ISSUER,
SUBJECT,
);
assert_eq!(push_signed(&r, "demo/customer", &img, Some(sig)).0, 201);
let chain = r.chain();
let id = layout_in(&chain, "oci/demo/customer/", ArtifactState::PolicyChecked);
approve(&chain, &id, &["bob", "carol"]);
assert_eq!(
request(
r.http,
"GET",
"/v2/oci/demo/customer/manifests/v1",
&[("Accept", MANIFEST)],
b""
)
.0,
200
);
// The customer's side: the test root, offline.
let out = tempfile::tempdir().unwrap();
let o = out.path();
std::fs::set_permissions(o, std::os::unix::fs::PermissionsExt::from_mode(0o777)).unwrap();
for d in ["home", "docker"] {
std::fs::create_dir_all(o.join(d)).unwrap();
std::fs::set_permissions(
o.join(d),
std::os::unix::fs::PermissionsExt::from_mode(0o777),
)
.unwrap();
}
std::fs::write(o.join("docker/config.json"), "{}").unwrap();
std::fs::write(
o.join("fulcio.pem"),
sigstore::pem_of_der("CERTIFICATE", &ms.root_der),
)
.unwrap();
std::fs::write(o.join("rekor.pub"), ms.rekor.public_key_pem()).unwrap();
std::fs::write(o.join("ctfe.pub"), ms.ct.public_key_pem()).unwrap();
std::fs::write(o.join("trusted_root.json"), ms.trusted_root_json()).unwrap();
let env = [
("SIGSTORE_ROOT_FILE", "/out/fulcio.pem"),
("SIGSTORE_REKOR_PUBLIC_KEY", "/out/rekor.pub"),
("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", "/out/ctfe.pub"),
];
let image = format!("{host}/oci/demo/customer@{}", img.digest);
let verify = |cosign: &str, identity: &str, extra: &[&str]| {
let mut args = vec![
"verify",
"--certificate-identity",
identity,
"--certificate-oidc-issuer",
ISSUER,
"--offline=true",
"--allow-http-registry",
];
args.extend_from_slice(extra);
args.push(&image);
run(cosign, o, &env, &args)
};
let (ok, text) = verify(COSIGN2, SUBJECT, &[]);
assert!(
ok,
"cosign 2 verify (keyless, through Holger, the test root) failed:\n{text}"
);
assert!(text.contains(&img.digest), "{text}");
let (ok, text) = verify(COSIGN2, STRANGER, &[]);
assert!(!ok, "cosign 2 must refuse another identity:\n{text}");
// cosign 3: the same legacy `.sig`, the test root as a trusted_root.json.
let (ok, text) = verify(
COSIGN3,
SUBJECT,
&[
"--trusted-root",
"/out/trusted_root.json",
"--new-bundle-format=false",
],
);
assert!(
ok,
"cosign 3 verify (keyless, through Holger, the test root) failed:\n{text}"
);
let (ok, text) = verify(
COSIGN3,
STRANGER,
&[
"--trusted-root",
"/out/trusted_root.json",
"--new-bundle-format=false",
],
);
assert!(!ok, "cosign 3 must refuse another identity:\n{text}");
}