Skip to content

Commit 5bbf83b

Browse files
simonbairdclaude
andcommitted
Filter v3 attestations and sigs in output
In v3 signature bundles, the signature is actually just another type of attestation. So when we list attestations (in v3) we see both the sig and the provenance. Similarly, when we list the image signatures we see the image signature and the attestation signature. In the detailed output for ec validate image we show details about the signatures and attestations. This changes attempts to avoid listing provenance atts in the image sig list, and imag sigs in the provenance att list. As mentioned in the comments, there might be other ways to do this. Ref: https://issues.redhat.com/browse/EC-1690 Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent f65988a commit 5bbf83b

3 files changed

Lines changed: 74 additions & 25 deletions

File tree

features/__snapshots__/task_validate_image.snap

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,7 @@ true
263263
"signatures": [
264264
{
265265
"keyid": "",
266-
"sig": "MEQCIDj5l7I0bPCua+H1ZfAAUnd4Hd4k7wUUEi/lpWYSLkOFAiBGgK9KWiNR1t+C4TbmkU/vnpHonmg5hNnwLRC70xc2Rg=="
267-
},
268-
{
269-
"keyid": "",
270-
"sig": "MEUCIBZc+dmgTn8SCx30h9yvCOjsBwj1+aZX0gW53c7TeyuSAiEAp4zWGNHMrjql9NFl/fCmFXnJkgDkOqbN5n7H7mw6aqI="
266+
"sig": "MEUCID1cJkxyk1oGvXcoAVkDST9A1vfX2gxPEz+LUzN10nDmAiEAxh9rp79yr4fZmAWWOit0dZ5QWK+uYIU8fQVb0/rLIyM="
271267
}
272268
],
273269
"attestations": [
@@ -280,16 +276,6 @@ true
280276
"sig": "MEUCIQC5bGm4zzbExXBMrZCmqZ98iqUhi8TV/maq/8dJ/c3POAIgCNw+RkeO7PAkT6JDWIvISZ2AjILu9YuPQ0qqfNwCqug="
281277
}
282278
]
283-
},
284-
{
285-
"type": "https://in-toto.io/Statement/v0.1",
286-
"predicateType": "https://sigstore.dev/cosign/sign/v1",
287-
"signatures": [
288-
{
289-
"keyid": "",
290-
"sig": "MEUCID1cJkxyk1oGvXcoAVkDST9A1vfX2gxPEz+LUzN10nDmAiEAxh9rp79yr4fZmAWWOit0dZ5QWK+uYIU8fQVb0/rLIyM="
291-
}
292-
]
293279
}
294280
]
295281
}

features/__snapshots__/validate_image.snap

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3647,12 +3647,6 @@ Error: success criteria not met
36473647
}
36483648
],
36493649
"success": true,
3650-
"signatures": [
3651-
{
3652-
"keyid": "",
3653-
"sig": ""
3654-
}
3655-
],
36563650
"attestations": [
36573651
{
36583652
"type": "https://in-toto.io/Statement/v0.1",

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package application_snapshot_image
1919
import (
2020
"bytes"
2121
"context"
22+
"encoding/base64"
2223
"encoding/json"
2324
"errors"
2425
"fmt"
@@ -181,6 +182,17 @@ func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) e
181182

182183
for _, s := range sigs {
183184
if useBundles {
185+
// This will appears in the output under "signatures" so filter out
186+
// the sigs that are provenance attestations leaving only the sigs
187+
// that are image signatures. Note: This does seems confusing and
188+
// I'm not 100% sure we're doing the right thing here. Maybe revisit
189+
// once we have a better idea about sigstore bundles and how they're
190+
// handled by cosign itself.
191+
if !isImageSignatureAttestation(s) {
192+
log.Debugf("Skipping non-image signature attestation")
193+
continue
194+
}
195+
184196
// For bundle image signatures produced by cosign v3, the old
185197
// method of accessing the signatures doesn't work. Instead we have
186198
// to extract them from the bundle. And the bundle actually has
@@ -191,7 +203,8 @@ func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) e
191203
}
192204
a.signatures = append(a.signatures, signatures...)
193205
} else {
194-
// For older non-bundle image signatures produced by cosign v2
206+
// For older non-bundle image signatures produced by cosign v2.
207+
// Note that filtering isn't needed, since we have only image sigs here.
195208
es, err := signature.NewEntitySignature(s)
196209
if err != nil {
197210
return err
@@ -218,6 +231,11 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
218231
return err
219232
}
220233

234+
// Todo:
235+
// - For the non-bundle code path we actually check the syntax.
236+
// We should do that for bundles as well probably.
237+
// - Doing an early return like thi shere seems untidy, refactor
238+
// maybe?
221239
if useBundles {
222240
return a.parseAttestationsFromBundles(layers)
223241
}
@@ -270,8 +288,19 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
270288
// parseAttestationsFromBundles extracts attestations from Sigstore bundles.
271289
// Bundle-wrapped layers report an incorrect media type, so we unmarshal the
272290
// DSSE envelope from the raw payload directly.
291+
//
292+
// Note: For v3 bundles, this function filters out image signature attestations
293+
// (https://sigstore.dev/cosign/sign/v1) since those are handled in ValidateImageSignature.
294+
// Only provenance and other attestations are added to the attestations array.
273295
func (a *ApplicationSnapshotImage) parseAttestationsFromBundles(layers []cosignOCI.Signature) error {
274296
for _, sig := range layers {
297+
// For v3 bundles, filter out image signature attestations - those are handled
298+
// in ValidateImageSignature. Only add provenance attestations here.
299+
if isImageSignatureAttestation(sig) {
300+
log.Debugf("Skipping image signature attestation - handled in ValidateImageSignature")
301+
continue
302+
}
303+
275304
payload, err := sig.Payload()
276305
if err != nil {
277306
log.Debugf("Skipping bundle entry: cannot read payload: %v", err)
@@ -294,8 +323,8 @@ func (a *ApplicationSnapshotImage) parseAttestationsFromBundles(layers []cosignO
294323
if err != nil {
295324
return fmt.Errorf("unable to parse bundle attestation: %w", err)
296325
}
297-
t := att.PredicateType()
298-
log.Debugf("Found bundle attestation with predicateType: %s", t)
326+
log.Debugf("Found bundle attestation with predicateType: %s", att.PredicateType())
327+
299328
a.attestations = append(a.attestations, att)
300329
}
301330
return nil
@@ -548,6 +577,46 @@ func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string,
548577
return inputJSONPath, inputJSON, nil
549578
}
550579

580+
const cosignSignPredicateType = "https://sigstore.dev/cosign/sign/v1"
581+
582+
// isImageSignatureAttestation checks if a bundle entry represents an image
583+
// signature (vs. a provenance or other attestation). For DSSE envelopes it
584+
// inspects the predicateType of the inner in-toto statement. For non-DSSE
585+
// payloads (e.g. simple signing) it returns true since those are always
586+
// image signatures.
587+
func isImageSignatureAttestation(sig cosignOCI.Signature) bool {
588+
payload, err := sig.Payload()
589+
if err != nil {
590+
log.Debugf("Cannot read signature payload: %v", err)
591+
return false
592+
}
593+
594+
var dsseEnvelope struct {
595+
PayloadType string `json:"payloadType"`
596+
Payload string `json:"payload"`
597+
}
598+
if err := json.Unmarshal(payload, &dsseEnvelope); err != nil || dsseEnvelope.PayloadType == "" {
599+
// Not a DSSE envelope — treat as a simple signing image signature
600+
return true
601+
}
602+
603+
innerPayload, err := base64.StdEncoding.DecodeString(dsseEnvelope.Payload)
604+
if err != nil {
605+
log.Debugf("Cannot decode DSSE payload: %v", err)
606+
return false
607+
}
608+
609+
var statement struct {
610+
PredicateType string `json:"predicateType"`
611+
}
612+
if err := json.Unmarshal(innerPayload, &statement); err != nil {
613+
log.Debugf("Cannot parse inner statement: %v", err)
614+
return false
615+
}
616+
617+
return statement.PredicateType == cosignSignPredicateType
618+
}
619+
551620
// extractSignaturesFromBundle extracts signature information from a bundle
552621
// image signature attestation, using the same pattern as createEntitySignatures.
553622
//
@@ -582,7 +651,7 @@ func extractSignaturesFromBundle(sig cosignOCI.Signature) ([]signature.EntitySig
582651
// the cosign.Signature. Prioritize information from oci.Signature, but fallback
583652
// to cosign.Signature when needed (same pattern as createEntitySignatures)
584653
//
585-
// Todo: Actually the above comment might be stale and/or wrong since I belive
654+
// Todo: Actually the above comment might be stale and/or wrong since I believe
586655
// it was copied from similar code in internal/attestation/attestation.go. Let's
587656
// review this later. As far as I can tell this code always produces an empty
588657
// string for the KeyId.

0 commit comments

Comments
 (0)