Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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}");
}