Skip to content

Commit 1ccc00b

Browse files
committed
Check pre-existing signatures
1 parent a88c068 commit 1ccc00b

File tree

2 files changed

+131
-12
lines changed

2 files changed

+131
-12
lines changed

image/oci/layout/oci_dest.go

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"io"
1010
"io/fs"
11+
"maps"
1112
"os"
1213
"path/filepath"
1314
"runtime"
@@ -19,6 +20,7 @@ import (
1920
"github.com/sirupsen/logrus"
2021
"go.podman.io/image/v5/internal/imagedestination/impl"
2122
"go.podman.io/image/v5/internal/imagedestination/stubs"
23+
"go.podman.io/image/v5/internal/iolimits"
2224
"go.podman.io/image/v5/internal/private"
2325
"go.podman.io/image/v5/internal/putblobdigest"
2426
"go.podman.io/image/v5/internal/signature"
@@ -336,7 +338,7 @@ func (d *ociImageDestination) PutSignaturesWithFormat(ctx context.Context, signa
336338
instanceDigest = &d.manifestDigest
337339
}
338340

339-
sigstoreSignatures := []signature.Sigstore{}
341+
var sigstoreSignatures []signature.Sigstore
340342
for _, sig := range signatures {
341343
if sigstoreSig, ok := sig.(signature.Sigstore); ok {
342344
sigstoreSignatures = append(sigstoreSignatures, sigstoreSig)
@@ -356,11 +358,28 @@ func (d *ociImageDestination) PutSignaturesWithFormat(ctx context.Context, signa
356358
func (d *ociImageDestination) putSignaturesToSigstoreAttachment(ctx context.Context, signatures []signature.Sigstore, manifestDigest digest.Digest) error {
357359
var signConfig imgspecv1.Image // Most fields empty by default
358360

359-
signManifest := manifest.OCI1FromComponents(imgspecv1.Descriptor{
360-
MediaType: imgspecv1.MediaTypeImageConfig,
361-
Digest: "", // We will fill this in later.
362-
Size: 0,
363-
}, nil)
361+
signManifest, err := d.ref.getSigstoreAttachmentManifest(manifestDigest, &d.index, d.sharedBlobDir)
362+
if err != nil {
363+
return err
364+
}
365+
if signManifest == nil {
366+
signManifest = manifest.OCI1FromComponents(imgspecv1.Descriptor{
367+
MediaType: imgspecv1.MediaTypeImageConfig,
368+
Digest: "", // We will fill this in later.
369+
Size: 0,
370+
}, nil)
371+
signConfig.RootFS.Type = "layers"
372+
} else {
373+
logrus.Debugf("Fetching sigstore attachment config %s", signManifest.Config.Digest.String())
374+
configBlob, err := d.ref.getOCIDescriptorContents(signManifest.Config, iolimits.MaxConfigBodySize, d.sharedBlobDir)
375+
if err != nil {
376+
return err
377+
}
378+
if err := json.Unmarshal(configBlob, &signConfig); err != nil {
379+
return fmt.Errorf("parsing sigstore attachment config %s in %s: %w", signManifest.Config.Digest.String(),
380+
d.ref.StringWithinTransport(), err)
381+
}
382+
}
364383

365384
desc, err := d.getDescriptor(&manifestDigest)
366385
if err != nil {
@@ -375,7 +394,14 @@ func (d *ociImageDestination) putSignaturesToSigstoreAttachment(ctx context.Cont
375394
payloadBlob := sig.UntrustedPayload()
376395
annotations := sig.UntrustedAnnotations()
377396

378-
sigDesc, err := d.putBlobBytesAsOCI(ctx, payloadBlob, mimeType, private.PutBlobOptions{
397+
// Skip if the signature is already on the registry.
398+
if slices.ContainsFunc(signManifest.Layers, func(layer imgspecv1.Descriptor) bool {
399+
return layerMatchesSigstoreSignature(layer, mimeType, payloadBlob, annotations)
400+
}) {
401+
continue
402+
}
403+
404+
signDesc, err := d.putBlobBytesAsOCI(ctx, payloadBlob, mimeType, private.PutBlobOptions{
379405
Cache: none.NoCache,
380406
IsConfig: false,
381407
EmptyLayer: false,
@@ -384,10 +410,10 @@ func (d *ociImageDestination) putSignaturesToSigstoreAttachment(ctx context.Cont
384410
if err != nil {
385411
return err
386412
}
387-
sigDesc.Annotations = annotations
388-
signManifest.Layers = append(signManifest.Layers, sigDesc)
389-
signConfig.RootFS.DiffIDs = append(signConfig.RootFS.DiffIDs, sigDesc.Digest)
390-
logrus.Debugf("Adding new signature, digest %s", sigDesc.Digest.String())
413+
signDesc.Annotations = annotations
414+
signManifest.Layers = append(signManifest.Layers, signDesc)
415+
signConfig.RootFS.DiffIDs = append(signConfig.RootFS.DiffIDs, signDesc.Digest)
416+
logrus.Debugf("Adding new signature, digest %s", signDesc.Digest.String())
391417
}
392418

393419
configBlob, err := json.Marshal(signConfig)
@@ -557,3 +583,18 @@ func indexExists(ref ociReference) bool {
557583
}
558584
return true
559585
}
586+
587+
func layerMatchesSigstoreSignature(layer imgspecv1.Descriptor, mimeType string,
588+
payloadBlob []byte, annotations map[string]string) bool {
589+
if layer.MediaType != mimeType ||
590+
layer.Size != int64(len(payloadBlob)) ||
591+
// This is not quite correct, we should use the layer’s digest algorithm.
592+
// But right now we don’t want to deal with corner cases like bad digest formats
593+
// or unavailable algorithms; in the worst case we end up with duplicate signature
594+
// entries.
595+
layer.Digest.String() != digest.FromBytes(payloadBlob).String() ||
596+
!maps.Equal(layer.Annotations, annotations) {
597+
return false
598+
}
599+
return true
600+
}

image/oci/layout/oci_transport.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"io"
89
"os"
910
"path/filepath"
1011
"strings"
@@ -14,7 +15,8 @@ import (
1415
"go.podman.io/image/v5/directory/explicitfilepath"
1516
"go.podman.io/image/v5/docker/reference"
1617
"go.podman.io/image/v5/internal/image"
17-
"go.podman.io/image/v5/internal/manifest"
18+
"go.podman.io/image/v5/internal/iolimits"
19+
"go.podman.io/image/v5/manifest"
1820
"go.podman.io/image/v5/oci/internal"
1921
"go.podman.io/image/v5/transports"
2022
"go.podman.io/image/v5/types"
@@ -310,3 +312,79 @@ func sigstoreAttachmentTag(d digest.Digest) (string, error) {
310312
}
311313
return strings.Replace(d.String(), ":", "-", 1) + ".sig", nil
312314
}
315+
316+
func (ref ociReference) getSigstoreAttachmentManifest(d digest.Digest, idx *imgspecv1.Index, sharedBlobDir string) (*manifest.OCI1, error) {
317+
signTag, err := sigstoreAttachmentTag(d)
318+
if err != nil {
319+
return nil, err
320+
}
321+
var signDesc *imgspecv1.Descriptor
322+
for _, m := range idx.Manifests {
323+
if m.Annotations[imgspecv1.AnnotationRefName] == signTag {
324+
signDesc = &m
325+
break
326+
}
327+
}
328+
if signDesc == nil {
329+
// No signature found
330+
return nil, nil
331+
}
332+
blobReader, _, err := ref.getBlob(signDesc.Digest, sharedBlobDir)
333+
if err != nil {
334+
return nil, fmt.Errorf("failed to get Blob %s: %w", signTag, err)
335+
}
336+
defer blobReader.Close()
337+
signBlob, err := iolimits.ReadAtMost(blobReader, iolimits.MaxManifestBodySize)
338+
mimeType := manifest.GuessMIMEType(signBlob)
339+
if mimeType != imgspecv1.MediaTypeImageManifest {
340+
return nil, fmt.Errorf("unexpected MIME type for sigstore attachment manifest %s: %q",
341+
signTag, mimeType)
342+
}
343+
res, err := manifest.OCI1FromManifest(signBlob)
344+
if err != nil {
345+
return nil, fmt.Errorf("parsing manifest %s: %w", signDesc.Digest, err)
346+
}
347+
return res, nil
348+
}
349+
350+
func (ref ociReference) getBlob(d digest.Digest, sharedBlobDir string) (io.ReadCloser, int64, error) {
351+
path, err := ref.blobPath(d, sharedBlobDir)
352+
if err != nil {
353+
return nil, 0, err
354+
}
355+
356+
r, err := os.Open(path)
357+
if err != nil {
358+
return nil, 0, err
359+
}
360+
fi, err := r.Stat()
361+
if err != nil {
362+
return nil, 0, err
363+
}
364+
return r, fi.Size(), nil
365+
}
366+
367+
func (ref ociReference) getOCIDescriptorContents(desc imgspecv1.Descriptor, maxSize int, sharedBlobDir string) ([]byte, error) {
368+
if err := desc.Digest.Validate(); err != nil { // .Algorithm() might panic without this check
369+
return nil, fmt.Errorf("invalid digest %q: %w", desc.Digest.String(), err)
370+
}
371+
digestAlgorithm := desc.Digest.Algorithm()
372+
if !digestAlgorithm.Available() {
373+
return nil, fmt.Errorf("invalid digest %q: unsupported digest algorithm %q", desc.Digest.String(), digestAlgorithm.String())
374+
}
375+
376+
reader, _, err := ref.getBlob(desc.Digest, sharedBlobDir)
377+
if err != nil {
378+
return nil, err
379+
}
380+
defer reader.Close()
381+
payload, err := iolimits.ReadAtMost(reader, maxSize)
382+
if err != nil {
383+
return nil, fmt.Errorf("reading blob %s in %s: %w", desc.Digest.String(), ref.image, err)
384+
}
385+
actualDigest := digestAlgorithm.FromBytes(payload)
386+
if actualDigest != desc.Digest {
387+
return nil, fmt.Errorf("digest mismatch, expected %q, got %q", desc.Digest.String(), actualDigest.String())
388+
}
389+
return payload, nil
390+
}

0 commit comments

Comments
 (0)