-
Notifications
You must be signed in to change notification settings - Fork 50
Support Sigstore bundle verification for cosign v3 #3123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,8 @@ import ( | |
| app "github.com/konflux-ci/application-api/api/v1alpha1" | ||
| "github.com/santhosh-tekuri/jsonschema/v5" | ||
| "github.com/sigstore/cosign/v3/pkg/cosign" | ||
| cosignOCI "github.com/sigstore/cosign/v3/pkg/oci" | ||
| ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" | ||
| log "github.com/sirupsen/logrus" | ||
| "github.com/spf13/afero" | ||
|
|
||
|
|
@@ -120,6 +122,12 @@ func (a *ApplicationSnapshotImage) SetImageURL(url string) error { | |
| return nil | ||
| } | ||
|
|
||
| func (a *ApplicationSnapshotImage) hasBundles(ctx context.Context) bool { | ||
| regOpts := []ociremote.Option{ociremote.WithRemoteOptions(oci.CreateRemoteOptions(ctx)...)} | ||
| bundles, _, err := cosign.GetBundles(ctx, a.reference, regOpts) | ||
| return err == nil && len(bundles) > 0 | ||
| } | ||
|
Comment on lines
+125
to
+129
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. hasbundles() ignores getbundles error hasBundles() silently converts cosign.GetBundles() failures into false, which can route verification down the legacy path without surfacing the underlying failure. This can mask registry/network/auth problems and produce misleading verification behavior. Agent Prompt
|
||
|
|
||
| func (a *ApplicationSnapshotImage) FetchImageConfig(ctx context.Context) error { | ||
| var err error | ||
| a.configJSON, err = config.FetchImageConfig(ctx, a.reference) | ||
|
|
@@ -143,38 +151,58 @@ func (a *ApplicationSnapshotImage) FetchImageFiles(ctx context.Context) error { | |
| return err | ||
| } | ||
|
|
||
| // ValidateImageSignature executes the cosign.VerifyImageSignature method on the ApplicationSnapshotImage image ref. | ||
| // ValidateImageSignature verifies the image signature. For images with Sigstore | ||
| // bundles (OCI referrers) the new bundle path is used; otherwise the legacy | ||
| // tag-based path is used. | ||
| func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) error { | ||
| // Set the ClaimVerifier on a shallow *copy* of CheckOpts to avoid unexpected side-effects | ||
| opts := a.checkOpts | ||
| opts.ClaimVerifier = cosign.SimpleClaimVerifier | ||
| signatures, _, err := oci.NewClient(ctx).VerifyImageSignatures(a.reference, &opts) | ||
| client := oci.NewClient(ctx) | ||
|
|
||
| var sigs []cosignOCI.Signature | ||
| var err error | ||
|
|
||
| if a.hasBundles(ctx) { | ||
| opts.NewBundleFormat = true | ||
| opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier | ||
| sigs, _, err = client.VerifyImageAttestations(a.reference, &opts) | ||
| } else { | ||
| opts.ClaimVerifier = cosign.SimpleClaimVerifier | ||
| sigs, _, err = client.VerifyImageSignatures(a.reference, &opts) | ||
|
Comment on lines
+164
to
+170
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Signature check verifies attestations For bundle images, ValidateImageSignature verifies attestations (and uses the in-toto claim verifier) instead of verifying image signatures, so the ImageSignatureCheck can become semantically incorrect. This risks passing the “image signature” gate based on attestations rather than actual image signatures. Agent Prompt
|
||
| } | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| for _, s := range signatures { | ||
| for _, s := range sigs { | ||
| es, err := signature.NewEntitySignature(s) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| a.signatures = append(a.signatures, es) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // ValidateAttestationSignature executes the cosign.VerifyImageAttestations method | ||
| // ValidateAttestationSignature verifies and collects in-toto attestations | ||
| // attached to the image. | ||
| func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Context) error { | ||
| // Set the ClaimVerifier on a shallow *copy* of CheckOpts to avoid unexpected side-effects | ||
| opts := a.checkOpts | ||
| opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier | ||
|
|
||
| useBundles := a.hasBundles(ctx) | ||
| if useBundles { | ||
| opts.NewBundleFormat = true | ||
| } | ||
|
|
||
| layers, _, err := oci.NewClient(ctx).VerifyImageAttestations(a.reference, &opts) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if useBundles { | ||
| return a.parseAttestationsFromBundles(layers) | ||
| } | ||
|
|
||
| // Extract the signatures from the attestations here in order to also validate that | ||
| // the signatures do exist in the expected format. | ||
| for _, sig := range layers { | ||
|
|
@@ -220,6 +248,40 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont | |
| return nil | ||
| } | ||
|
|
||
| // parseAttestationsFromBundles extracts attestations from Sigstore bundles. | ||
| // Bundle-wrapped layers report an incorrect media type, so we unmarshal the | ||
| // DSSE envelope from the raw payload directly. | ||
| func (a *ApplicationSnapshotImage) parseAttestationsFromBundles(layers []cosignOCI.Signature) error { | ||
| for _, sig := range layers { | ||
| payload, err := sig.Payload() | ||
| if err != nil { | ||
| log.Debugf("Skipping bundle entry: cannot read payload: %v", err) | ||
| continue | ||
| } | ||
| var dsseEnvelope struct { | ||
| PayloadType string `json:"payloadType"` | ||
| Payload string `json:"payload"` | ||
| } | ||
| if err := json.Unmarshal(payload, &dsseEnvelope); err != nil { | ||
| log.Debugf("Skipping bundle entry: not a valid DSSE envelope: %v", err) | ||
| continue | ||
| } | ||
| if dsseEnvelope.PayloadType != "application/vnd.in-toto+json" { | ||
| log.Debugf("Skipping bundle entry with payloadType: %s", dsseEnvelope.PayloadType) | ||
| continue | ||
| } | ||
|
|
||
| att, err := attestation.ProvenanceFromBundlePayload(payload) | ||
| if err != nil { | ||
| return fmt.Errorf("unable to parse bundle attestation: %w", err) | ||
| } | ||
| t := att.PredicateType() | ||
| log.Debugf("Found bundle attestation with predicateType: %s", t) | ||
| a.attestations = append(a.attestations, att) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // ValidateAttestationSyntax validates the attestations against known JSON | ||
| // schemas, errors out if there are no attestations to check to prevent | ||
| // successful syntax check of no inputs, must invoke | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3. Bundle attestations drop signatures
🐞 Bug✓ CorrectnessAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools