Skip to content

Commit f65988a

Browse files
simonbairdclaude
andcommitted
Include some v3 bundle signature detail in output
If we compare the output for a cosign v2 signed image versus a cosign v3 signed image, there is a lot of detail missing. I'm expecting we will be able to extract the signature info from the bundle, but for now Claude and I are having some difficulty. I think we'll need to gain a better understanding of the way the new sigstore bundles work and try again in the future. For now I think it's okay if we just show the sig field and add some todos in the code. I suspect no one is actually consuming this data, so it won't have an immediate impact if it's missing. Ref: https://issues.redhat.com/browse/EC-1690 Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 7c9c60f commit f65988a

2 files changed

Lines changed: 80 additions & 8 deletions

File tree

features/__snapshots__/task_validate_image.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,11 @@ true
263263
"signatures": [
264264
{
265265
"keyid": "",
266-
"sig": ""
266+
"sig": "MEQCIDj5l7I0bPCua+H1ZfAAUnd4Hd4k7wUUEi/lpWYSLkOFAiBGgK9KWiNR1t+C4TbmkU/vnpHonmg5hNnwLRC70xc2Rg=="
267267
},
268268
{
269269
"keyid": "",
270-
"sig": ""
270+
"sig": "MEUCIBZc+dmgTn8SCx30h9yvCOjsBwj1+aZX0gW53c7TeyuSAiEAp4zWGNHMrjql9NFl/fCmFXnJkgDkOqbN5n7H7mw6aqI="
271271
}
272272
],
273273
"attestations": [

internal/evaluation_target/application_snapshot_image/application_snapshot_image.go

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,19 @@ func (a *ApplicationSnapshotImage) FetchImageFiles(ctx context.Context) error {
155155
func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) error {
156156
opts := a.checkOpts
157157
client := oci.NewClient(ctx)
158+
useBundles := a.hasBundles(ctx)
158159

159160
var sigs []cosignOCI.Signature
160161
var err error
161162

162-
if a.hasBundles(ctx) {
163+
if useBundles {
164+
// For v3 bundles, both image signatures and attestations are stored as
165+
// "attestations" in the unified bundle format. So we use VerifyImageAttestations
166+
// to extract image signatures from the bundle, even though it seems unintuitive.
167+
// This is different from v2 where image signatures and attestations were separate.
168+
//
169+
// The certificate extraction requires different handling for bundles and
170+
// should be addressed in future work to achieve full v2 parity.
163171
opts.NewBundleFormat = true
164172
opts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
165173
sigs, _, err = client.VerifyImageAttestations(a.reference, &opts)
@@ -172,11 +180,24 @@ func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) e
172180
}
173181

174182
for _, s := range sigs {
175-
es, err := signature.NewEntitySignature(s)
176-
if err != nil {
177-
return err
183+
if useBundles {
184+
// For bundle image signatures produced by cosign v3, the old
185+
// method of accessing the signatures doesn't work. Instead we have
186+
// to extract them from the bundle. And the bundle actually has
187+
// signatures for both the image itself and the attestation.
188+
signatures, err := extractSignaturesFromBundle(s)
189+
if err != nil {
190+
return fmt.Errorf("cannot extract signatures from bundle: %w", err)
191+
}
192+
a.signatures = append(a.signatures, signatures...)
193+
} else {
194+
// For older non-bundle image signatures produced by cosign v2
195+
es, err := signature.NewEntitySignature(s)
196+
if err != nil {
197+
return err
198+
}
199+
a.signatures = append(a.signatures, es)
178200
}
179-
a.signatures = append(a.signatures, es)
180201
}
181202
return nil
182203
}
@@ -507,7 +528,7 @@ func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string,
507528
log.Debugf("Created dir %s", inputDir)
508529
inputJSONPath := path.Join(inputDir, "input.json")
509530

510-
f, err := fs.OpenFile(inputJSONPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
531+
f, err := fs.OpenFile(inputJSONPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o644)
511532
if err != nil {
512533
log.Debugf("Problem creating file in %s", inputDir)
513534
return "", nil, err
@@ -526,3 +547,54 @@ func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string,
526547
log.Debugf("Done preparing input file:\n%s", inputJSONPath)
527548
return inputJSONPath, inputJSON, nil
528549
}
550+
551+
// extractSignaturesFromBundle extracts signature information from a bundle
552+
// image signature attestation, using the same pattern as createEntitySignatures.
553+
//
554+
// TODO: This currently only extracts the signature value from the DSSE envelope.
555+
// Certificate information (keyid, certificate, chain, metadata) is not being
556+
// extracted because it requires different handling for v3 bundles compared to v2.
557+
// Future work should investigate how to access certificate data from bundle
558+
// attestations to achieve full parity with v2 signature output. Also, there might
559+
// be some cosign methods we can use instead of doing it ourselves here.
560+
func extractSignaturesFromBundle(sig cosignOCI.Signature) ([]signature.EntitySignature, error) {
561+
reader, err := sig.Uncompressed()
562+
if err != nil {
563+
return nil, fmt.Errorf("cannot read signature data: %w", err)
564+
}
565+
defer reader.Close()
566+
567+
var attestationPayload cosign.AttestationPayload
568+
if err := json.NewDecoder(reader).Decode(&attestationPayload); err != nil {
569+
return nil, fmt.Errorf("cannot parse DSSE envelope: %w", err)
570+
}
571+
572+
// Create the base EntitySignature from the oci.Signature (for certificate info)
573+
es, err := signature.NewEntitySignature(sig)
574+
if err != nil {
575+
return nil, fmt.Errorf("cannot create base signature: %w", err)
576+
}
577+
578+
var results []signature.EntitySignature
579+
for _, s := range attestationPayload.Signatures {
580+
esNew := es
581+
// The Signature and KeyID can come from two locations, the oci.Signature or
582+
// the cosign.Signature. Prioritize information from oci.Signature, but fallback
583+
// to cosign.Signature when needed (same pattern as createEntitySignatures)
584+
//
585+
// Todo: Actually the above comment might be stale and/or wrong since I belive
586+
// it was copied from similar code in internal/attestation/attestation.go. Let's
587+
// review this later. As far as I can tell this code always produces an empty
588+
// string for the KeyId.
589+
//
590+
if esNew.Signature == "" {
591+
esNew.Signature = s.Sig
592+
}
593+
if esNew.KeyID == "" {
594+
esNew.KeyID = s.KeyID
595+
}
596+
results = append(results, esNew)
597+
}
598+
599+
return results, nil
600+
}

0 commit comments

Comments
 (0)