Repository navigation
Verify keyless cosign signatures with sigstore-verify (spike) - #179
Merged
Merged
Conversation
Keyless cosign signatures (a Fulcio certificate, a Rekor entry and an RFC 3161 timestamp in a v0.3 Sigstore bundle) are verified through the sigstore org's sigstore-verify crate against a Sigstore trusted root the new --cosign-trusted-root flag names. The crate establishes the signing time from the bundle's RFC 3161 timestamps and its Rekor entry's signed entry timestamp, verifies the inclusion proof and checkpoint, chains the certificate at that time and checks the signature; the runtime then requires the statement to be a cosign signature of the pinned digest and the signer to be trusted for the module's repository by the operator policy's new trustSigner rules (context.issuer, context.identity over the Repository hierarchy, default-deny). The sigstore 0.14 crate is replaced: key-based signatures keep their contract (the keys are the trust root, no transparency-log entry is required or read) on sigstore-crypto's VerificationKey, because sigstore-verify's public-key policy refuses a bundle with neither a log entry nor a timestamp, which is what cosign writes with --tlog-upload=false. The operator policy is shared with the verifier as an Arc. reqwest's query and json features, which the removed crate had enabled transitively, are declared. The fixture under testdata/cosign is the real keyless signature this repository's release workflow attached to the published pdb-addon example; the tests verify it offline against the trusted root embedded in sigstore-trust-root and refuse it for another digest, under another trusted root, tampered, and when no trustSigner rule trusts the signer. Spike for #116.
The signatures page gains a keyless section (--cosign-trusted-root, how to obtain a trusted root, what is verified and the trustSigner rule), the flags table its row, the Cedar reference the eighth action, the troubleshooting page the four new refusals, and the trust-model and use-case one-pagers move their revisions. AGENTS.md follows the code: the cosign.rs entry, the Signatures section and the sigstore-rust crates in Key Dependencies in place of sigstore-rs. Part of #116.
A required module met by a runtime that can verify nothing is refused with "the operator policy requires a cosign signature, but the runtime has neither --cosign-key nor --cosign-trusted-root to verify it", and function validate's egress warning reads "neither --cosign-key nor --cosign-trusted-root was given"; the all-or-nothing refusal of a non-OCI source names both flags too. Both sentences named --cosign-key alone, which since the keyless path landed understated the operator's options. Three conformance goldens are re-recorded deliberately for those two sentences and nothing else: SignatureRequired, ManifestNoPolicy-egress and ManifestPermissive-egress. The wording is pinned contract from 1.0.0 on; this lands before it. Part of #116.
testsigstore::Instance (feature testutil) is an in-memory Sigstore instance: a Fulcio certificate authority, a certificate-transparency log and a Rekor log, P-256 keys from fixed seeds, that issues keyless cosign bundles at a fixed past time and publishes the trusted root that verifies them. A bundle carries a ten-minute certificate with the signer's identity, Fulcio's issuer extension and an embedded SCT over the precertificate, and a Rekor dsse 0.0.1 entry with its signed entry timestamp, a one-leaf inclusion proof and a signed checkpoint; no RFC 3161 timestamp. Every certificate it issues has expired, so a signature verifies only because its signing time is authenticated. Unit tests verify and refuse against it beside the real GHCR fixture, and five conformance cases run function validate --resolve against a registry it signed into, under the new signer-policy fixtures: a trusted signer admitted, an untrusted one refused, another instance's root refused (that golden quotes sigstore-verify's wording), a keyless module met by --cosign-key alone, a key-signed module met by --cosign-trusted-root alone. rcgen, x509-cert and time join the testutil feature and the dev-dependencies. Part of #116.
jonasz-lasut
force-pushed
the
spike/sigstore-verify
branch
from
October 27, 2026 07:25
86b8dee to
63a1c45
Compare
This was referenced Oct 27, 2026
Owner
Author
|
/e2e |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spike for #116: keyless cosign signatures verified through the sigstore org's second Rust implementation, sigstore-rust (
sigstore-verify0.15.1), in place of thesigstore0.14 crate.What changes
--cosign-trusted-root <trusted_root.json>(COSIGN_TRUSTED_ROOT) onserveandvalidate: a Sigstore trusted root (Fulcio authorities, Rekor and certificate-transparency keys, timestamp authorities). A bundle carrying a certificate is verified offline against it: the signing time from the bundle's RFC 3161 timestamps and its Rekor entry's signed entry timestamp (a source that is present but fails is fatal; there is no fallback to the clock), the inclusion proof and checkpoint, the certificate chain at that time, then the signature.cosign.rsthen requires the cosign predicate over the pinned digest.trustSigner, the operator policy's eighth action:permit (principal, action == Action::"trustSigner", resource) when { resource in Repository::"…" && context.issuer == "…" && context.identity like "…" }decides, per repository and default-deny, which signer a verified keyless signature may come from. A trusted root alone admits nothing;requireSignaturekeeps deciding which repositories must be signed.VerificationKey: the keys remain the trust root and no transparency-log entry is read. sigstore-verify's public-key policy refuses a bundle with neither a log entry nor a timestamp, which is whatcosign sign --key --tlog-upload=falsewrites (the e2e scenario), so that path is not an option for them.Arc. reqwest'squeryandjsonfeatures are declared: the removed crate had enabled them transitively.cosign initialize), the flags table, the Cedar reference, troubleshooting, the trust-model and use-case one-pagers, AGENTS.md.Verification
cargo test -p function-wasm --lib(103),--test conformance(7, goldens unchanged),--test raw_client,cargo test -p guestfn,cargo test -p function-wasm-engine;cargo clippy --workspace --all-targets -- -D warnings;cargo fmt --all --check;mdbook build. The guest behavioural suite (tests/guests.rs) was not run locally.crates/function/testdata/cosign/is the real keyless signature the release workflow attached to the publishedpdb-addonexample; its certificate expired on 2026-10-07 at 11:25 UTC. The tests verify it offline against the trusted root embedded insigstore-trust-root, and refuse it for another digest, under the staging root, tampered, and when no rule trusts the signer.ghcr.io/jonasz-lasut/wasmfn/examples/policy-gate@sha256:89ed3c52…:function validate composition.yaml --sandbox-policy-file policy.cedar --cosign-trusted-root trusted_root.json --resolveadmitted the module under a rule trustingpublish-pkg.yml@refs/tags/v*; under a rule trusting*@refs/heads/mainit refused withkeyless signature by https://github.com/jonasz-lasut/function-wasm/.github/workflows/publish-pkg.yml@refs/tags/v0.6.0 (issuer https://token.actions.githubusercontent.com) is not trusted for ghcr.io/jonasz-lasut/wasmfn/examples/policy-gate: no trustSigner rule in the operator policy permits it; without the flag it fell back to the existing no-key refusal.Open before this leaves spike status
/e2ehas not run on this branch.Since the first push
the operator policy requires a cosign signature, but the runtime has neither --cosign-key nor --cosign-trusted-root to verify it, andfunction validate's egress warning likewise. Three conformance goldens were re-recorded deliberately for those two sentences and nothing else.testsigstore::Instance(featuretestutil) is an in-memory Sigstore instance: a Fulcio CA, a CT log and a Rekor log from fixed seeds that issue keyless bundles at a fixed past time (ten-minute certificate with an embedded SCT, Rekordsse/0.0.1entry with its signed entry timestamp, inclusion proof and checkpoint) and publish the trusted root that verifies them. Five new conformance cases runfunction validate --resolveagainst it undertestdata/validate/signer-policy*.cedar: a trusted signer admitted, an untrusted one refused, another instance's root refused, a keyless module met by--cosign-keyalone, a key-signed module met by--cosign-trusted-rootalone. The whole keyless path is now covered offline and deterministically; the real GHCR fixture stays as the cross-check against production material.Fixes #116. The trusted root's fetch and refresh over TUF continues in #183.