From b1d0553d70f0b16788374fd12dce782466c18364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Tue, 5 Jul 2022 19:34:12 +0200 Subject: [PATCH] Introduce ImageSource.GetSignaturesWithFormat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- directory/directory_src.go | 20 ++++++--- docker/docker_image_src.go | 41 ++++++++++++------- docker/internal/tarfile/src.go | 5 ++- internal/imagesource/impl/compat.go | 54 +++++++++++++++++++++++++ internal/imagesource/impl/signatures.go | 5 ++- internal/imagesource/wrapper.go | 20 +++++++++ internal/private/private.go | 6 +++ oci/archive/oci_src.go | 16 +++++--- oci/layout/oci_src.go | 2 + openshift/openshift_src.go | 16 +++++--- ostree/ostree_src.go | 28 ++++++++----- pkg/blobcache/src.go | 20 ++++++--- sif/src.go | 7 +++- storage/storage_src.go | 20 ++++++--- tarball/tarball_src.go | 2 + 15 files changed, 207 insertions(+), 55 deletions(-) create mode 100644 internal/imagesource/impl/compat.go diff --git a/directory/directory_src.go b/directory/directory_src.go index 4fc20b1cac..98efdedd73 100644 --- a/directory/directory_src.go +++ b/directory/directory_src.go @@ -2,18 +2,21 @@ package directory import ( "context" + "fmt" "io" "os" "github.com/containers/image/v5/internal/imagesource/impl" "github.com/containers/image/v5/internal/imagesource/stubs" "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" ) type dirImageSource struct { + impl.Compat impl.PropertyMethodsInitialize impl.DoesNotAffectLayerInfosForCopy stubs.NoGetBlobAtInitialize @@ -24,7 +27,7 @@ type dirImageSource struct { // newImageSource returns an ImageSource reading from an existing directory. // The caller must call .Close() on the returned ImageSource. func newImageSource(ref dirReference) private.ImageSource { - return &dirImageSource{ + s := &dirImageSource{ PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{ HasThreadSafeGetBlob: false, }), @@ -32,6 +35,8 @@ func newImageSource(ref dirReference) private.ImageSource { ref: ref, } + s.Compat = impl.AddCompat(s) + return s } // Reference returns the reference used to set up this source, _as specified by the user_ @@ -72,20 +77,25 @@ func (s *dirImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache return r, fi.Size(), nil } -// GetSignatures returns the image's signatures. It may use a remote (= slow) service. +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). -func (s *dirImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { - signatures := [][]byte{} +func (s *dirImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { + signatures := []signature.Signature{} for i := 0; ; i++ { - signature, err := os.ReadFile(s.ref.signaturePath(i, instanceDigest)) + path := s.ref.signaturePath(i, instanceDigest) + sigBlob, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { break } return nil, err } + signature, err := signature.FromBlob(sigBlob) + if err != nil { + return nil, fmt.Errorf("parsing signature %q: %w", path, err) + } signatures = append(signatures, signature) } return signatures, nil diff --git a/docker/docker_image_src.go b/docker/docker_image_src.go index 3cb477167b..53f9cecd4d 100644 --- a/docker/docker_image_src.go +++ b/docker/docker_image_src.go @@ -20,6 +20,7 @@ import ( "github.com/containers/image/v5/internal/imagesource/stubs" "github.com/containers/image/v5/internal/iolimits" "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/types" @@ -29,6 +30,7 @@ import ( ) type dockerImageSource struct { + impl.Compat impl.PropertyMethodsInitialize impl.DoesNotAffectLayerInfosForCopy stubs.ImplementsGetBlobAt @@ -140,6 +142,7 @@ func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logica physicalRef: physicalRef, c: client, } + s.Compat = impl.AddCompat(s) if err := s.ensureManifestIsLoaded(ctx); err != nil { return nil, err @@ -466,11 +469,11 @@ func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca return res.Body, getBlobSize(res), nil } -// GetSignatures returns the image's signatures. It may use a remote (= slow) service. +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). -func (s *dockerImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { +func (s *dockerImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { if err := s.c.detectProperties(ctx); err != nil { return nil, err } @@ -502,16 +505,16 @@ func (s *dockerImageSource) manifestDigest(ctx context.Context, instanceDigest * return manifest.Digest(s.cachedManifest) } -// getSignaturesFromLookaside implements GetSignatures() from the lookaside location configured in s.c.signatureBase, +// getSignaturesFromLookaside implements GetSignaturesWithFormat() from the lookaside location configured in s.c.signatureBase, // which is not nil. -func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { +func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { manifestDigest, err := s.manifestDigest(ctx, instanceDigest) if err != nil { return nil, err } // NOTE: Keep this in sync with docs/signature-protocols.md! - signatures := [][]byte{} + signatures := []signature.Signature{} for i := 0; ; i++ { url := signatureStorageURL(s.c.signatureBase, manifestDigest, i) signature, missing, err := s.getOneSignature(ctx, url) @@ -526,20 +529,24 @@ func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, inst return signatures, nil } -// getOneSignature downloads one signature from url. -// If it successfully determines that the signature does not exist, returns with missing set to true and error set to nil. +// getOneSignature downloads one signature from url, and returns (signature, false, nil) +// If it successfully determines that the signature does not exist, returns (nil, true, nil). // NOTE: Keep this in sync with docs/signature-protocols.md! -func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (signature []byte, missing bool, err error) { +func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (signature.Signature, bool, error) { switch url.Scheme { case "file": logrus.Debugf("Reading %s", url.Path) - sig, err := os.ReadFile(url.Path) + sigBlob, err := os.ReadFile(url.Path) if err != nil { if os.IsNotExist(err) { return nil, true, nil } return nil, false, err } + sig, err := signature.FromBlob(sigBlob) + if err != nil { + return nil, false, fmt.Errorf("parsing signature %q: %w", url.Path, err) + } return sig, false, nil case "http", "https": @@ -556,12 +563,16 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) ( if res.StatusCode == http.StatusNotFound { return nil, true, nil } else if res.StatusCode != http.StatusOK { - return nil, false, fmt.Errorf("Error reading signature from %s: status %d (%s)", url.Redacted(), res.StatusCode, http.StatusText(res.StatusCode)) + return nil, false, fmt.Errorf("reading signature from %s: status %d (%s)", url.Redacted(), res.StatusCode, http.StatusText(res.StatusCode)) } - sig, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureBodySize) + sigBlob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureBodySize) if err != nil { return nil, false, err } + sig, err := signature.FromBlob(sigBlob) + if err != nil { + return nil, false, fmt.Errorf("parsing signature %s: %w", url.Redacted(), err) + } return sig, false, nil default: @@ -569,8 +580,8 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) ( } } -// getSignaturesFromAPIExtension implements GetSignatures() using the X-Registry-Supports-Signatures API extension. -func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { +// getSignaturesFromAPIExtension implements GetSignaturesWithFormat() using the X-Registry-Supports-Signatures API extension. +func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { manifestDigest, err := s.manifestDigest(ctx, instanceDigest) if err != nil { return nil, err @@ -581,10 +592,10 @@ func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, i return nil, err } - var sigs [][]byte + var sigs []signature.Signature for _, sig := range parsedBody.Signatures { if sig.Version == extensionSignatureSchemaVersion && sig.Type == extensionSignatureTypeAtomic { - sigs = append(sigs, sig.Content) + sigs = append(sigs, signature.SimpleSigningFromBlob(sig.Content)) } } return sigs, nil diff --git a/docker/internal/tarfile/src.go b/docker/internal/tarfile/src.go index f5cfefe84f..8230d88c6d 100644 --- a/docker/internal/tarfile/src.go +++ b/docker/internal/tarfile/src.go @@ -25,6 +25,7 @@ import ( // Source is a partial implementation of types.ImageSource for reading from tarPath. type Source struct { + impl.Compat impl.PropertyMethodsInitialize impl.NoSignatures impl.DoesNotAffectLayerInfosForCopy @@ -56,7 +57,7 @@ type layerInfo struct { // and sourceIndex (or the only image if they are (nil, -1)). // The archive will be closed if closeArchive func NewSource(archive *Reader, closeArchive bool, transportName string, ref reference.NamedTagged, sourceIndex int) *Source { - return &Source{ + s := &Source{ PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{ HasThreadSafeGetBlob: true, }), @@ -67,6 +68,8 @@ func NewSource(archive *Reader, closeArchive bool, transportName string, ref ref ref: ref, sourceIndex: sourceIndex, } + s.Compat = impl.AddCompat(s) + return s } // ensureCachedDataIsPresent loads data necessary for any of the public accessors. diff --git a/internal/imagesource/impl/compat.go b/internal/imagesource/impl/compat.go new file mode 100644 index 0000000000..6f79329162 --- /dev/null +++ b/internal/imagesource/impl/compat.go @@ -0,0 +1,54 @@ +package impl + +import ( + "context" + + "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" + "github.com/opencontainers/go-digest" +) + +// Compat implements the obsolete parts of types.ImageSource +// for implementations of private.ImageSource. +// See AddCompat below. +type Compat struct { + src private.ImageSourceInternalOnly +} + +// AddCompat initializes Compat to implement the obsolete parts of types.ImageSource +// for implementations of private.ImageSource. +// +// Use it like this: +// type yourSource struct { +// impl.Compat +// … +// } +// src := &yourSource{…} +// src.Compat = impl.AddCompat(src) +// +func AddCompat(src private.ImageSourceInternalOnly) Compat { + return Compat{src} +} + +// GetSignatures returns the image's signatures. It may use a remote (= slow) service. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +func (c *Compat) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { + // Silently ignore signatures with other formats; the caller can’t handle them. + // Admittedly callers that want to sync all of the image might want to fail instead; this + // way an upgrade of c/image neither breaks them nor adds new functionality. + // Alternatively, we could possibly define the old GetSignatures to use the multi-format + // signature.Blob representation now, in general, but that could silently break them as well. + sigs, err := c.src.GetSignaturesWithFormat(ctx, instanceDigest) + if err != nil { + return nil, err + } + simpleSigs := [][]byte{} + for _, sig := range sigs { + if sig, ok := sig.(signature.SimpleSigning); ok { + simpleSigs = append(simpleSigs, sig.UntrustedSignature()) + } + } + return simpleSigs, nil +} diff --git a/internal/imagesource/impl/signatures.go b/internal/imagesource/impl/signatures.go index ee25152b56..b3a8c7e88d 100644 --- a/internal/imagesource/impl/signatures.go +++ b/internal/imagesource/impl/signatures.go @@ -3,16 +3,17 @@ package impl import ( "context" + "github.com/containers/image/v5/internal/signature" "github.com/opencontainers/go-digest" ) // NoSignatures implements GetSignatures() that returns nothing. type NoSignatures struct{} -// GetSignatures returns the image's signatures. It may use a remote (= slow) service. +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). -func (stub NoSignatures) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { +func (stub NoSignatures) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { return nil, nil } diff --git a/internal/imagesource/wrapper.go b/internal/imagesource/wrapper.go index deacc713d6..886b4e833b 100644 --- a/internal/imagesource/wrapper.go +++ b/internal/imagesource/wrapper.go @@ -1,9 +1,13 @@ package imagesource import ( + "context" + "github.com/containers/image/v5/internal/imagesource/stubs" "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" ) // wrapped provides the private.ImageSource operations @@ -34,3 +38,19 @@ func FromPublic(src types.ImageSource) private.ImageSource { ImageSource: src, } } + +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +func (w *wrapped) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { + sigs, err := w.GetSignatures(ctx, instanceDigest) + if err != nil { + return nil, err + } + res := []signature.Signature{} + for _, sig := range sigs { + res = append(res, signature.SimpleSigningFromBlob(sig)) + } + return res, nil +} diff --git a/internal/private/private.go b/internal/private/private.go index ed4ecf4dbc..40189e0435 100644 --- a/internal/private/private.go +++ b/internal/private/private.go @@ -18,6 +18,12 @@ type ImageSourceInternalOnly interface { SupportsGetBlobAt() bool // BlobChunkAccessor.GetBlobAt is available only if SupportsGetBlobAt(). BlobChunkAccessor + + // GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. + // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for + // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list + // (e.g. if the source never returns manifest lists). + GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) } // ImageSource is an internal extension to the types.ImageSource interface. diff --git a/oci/archive/oci_src.go b/oci/archive/oci_src.go index d53f2ae159..b24fc77b2a 100644 --- a/oci/archive/oci_src.go +++ b/oci/archive/oci_src.go @@ -6,7 +6,9 @@ import ( "io" "github.com/containers/image/v5/internal/imagesource" + "github.com/containers/image/v5/internal/imagesource/impl" "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" ocilayout "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/types" digest "github.com/opencontainers/go-digest" @@ -16,6 +18,8 @@ import ( ) type ociArchiveImageSource struct { + impl.Compat + ref ociArchiveReference unpackedSrc private.ImageSource tempDirRef tempDirOCIRef @@ -36,11 +40,13 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref ociArchiv } return nil, err } - return &ociArchiveImageSource{ + s := &ociArchiveImageSource{ ref: ref, unpackedSrc: imagesource.FromPublic(unpackedSrc), tempDirRef: tempDirRef, - }, nil + } + s.Compat = impl.AddCompat(s) + return s, nil } // LoadManifestDescriptor loads the manifest @@ -120,12 +126,12 @@ func (s *ociArchiveImageSource) GetBlobAt(ctx context.Context, info types.BlobIn return s.unpackedSrc.GetBlobAt(ctx, info, chunks) } -// GetSignatures returns the image's signatures. It may use a remote (= slow) service. +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). -func (s *ociArchiveImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { - return s.unpackedSrc.GetSignatures(ctx, instanceDigest) +func (s *ociArchiveImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { + return s.unpackedSrc.GetSignaturesWithFormat(ctx, instanceDigest) } // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer diff --git a/oci/layout/oci_src.go b/oci/layout/oci_src.go index 6040478722..9641ad0241 100644 --- a/oci/layout/oci_src.go +++ b/oci/layout/oci_src.go @@ -22,6 +22,7 @@ import ( ) type ociImageSource struct { + impl.Compat impl.PropertyMethodsInitialize impl.NoSignatures impl.DoesNotAffectLayerInfosForCopy @@ -71,6 +72,7 @@ func newImageSource(sys *types.SystemContext, ref ociReference) (private.ImageSo // TODO(jonboulle): check dir existence? s.sharedBlobDir = sys.OCISharedBlobDirPath } + s.Compat = impl.AddCompat(s) return s, nil } diff --git a/openshift/openshift_src.go b/openshift/openshift_src.go index 8555c05e0c..93ba8d10e3 100644 --- a/openshift/openshift_src.go +++ b/openshift/openshift_src.go @@ -12,12 +12,14 @@ import ( "github.com/containers/image/v5/internal/imagesource/impl" "github.com/containers/image/v5/internal/imagesource/stubs" "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" ) type openshiftImageSource struct { + impl.Compat impl.DoesNotAffectLayerInfosForCopy // This is slightly suboptimal. We could forward GetBlobAt(), but we need to call ensureImageIsResolved in SupportsGetBlobAt(), // and that method doesn’t provide a context for timing out. That could actually be fixed (SupportsGetBlobAt is private and we @@ -40,12 +42,14 @@ func newImageSource(sys *types.SystemContext, ref openshiftReference) (private.I return nil, err } - return &openshiftImageSource{ + s := &openshiftImageSource{ NoGetBlobAtInitialize: stubs.NoGetBlobAt(ref), client: client, sys: sys, - }, nil + } + s.Compat = impl.AddCompat(s) + return s, nil } // Reference returns the reference used to set up this source, _as specified by the user_ @@ -92,11 +96,11 @@ func (s *openshiftImageSource) GetBlob(ctx context.Context, info types.BlobInfo, return s.docker.GetBlob(ctx, info, cache) } -// GetSignatures returns the image's signatures. It may use a remote (= slow) service. +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). -func (s *openshiftImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { +func (s *openshiftImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { var imageStreamImageName string if instanceDigest == nil { if err := s.ensureImageIsResolved(ctx); err != nil { @@ -110,10 +114,10 @@ func (s *openshiftImageSource) GetSignatures(ctx context.Context, instanceDigest if err != nil { return nil, err } - var sigs [][]byte + var sigs []signature.Signature for _, sig := range image.Signatures { if sig.Type == imageSignatureTypeAtomic { - sigs = append(sigs, sig.Content) + sigs = append(sigs, signature.SimpleSigningFromBlob(sig.Content)) } } return sigs, nil diff --git a/ostree/ostree_src.go b/ostree/ostree_src.go index 29f4460ecc..fdbcab5002 100644 --- a/ostree/ostree_src.go +++ b/ostree/ostree_src.go @@ -17,6 +17,7 @@ import ( "github.com/containers/image/v5/internal/imagesource/impl" "github.com/containers/image/v5/internal/imagesource/stubs" "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/ioutils" @@ -37,6 +38,7 @@ import ( import "C" type ostreeImageSource struct { + impl.Compat impl.PropertyMethodsInitialize stubs.NoGetBlobAtInitialize @@ -49,7 +51,7 @@ type ostreeImageSource struct { // newImageSource returns an ImageSource for reading from an existing directory. func newImageSource(tmpDir string, ref ostreeReference) (private.ImageSource, error) { - return &ostreeImageSource{ + s := &ostreeImageSource{ PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{ HasThreadSafeGetBlob: false, }), @@ -58,7 +60,9 @@ func newImageSource(tmpDir string, ref ostreeReference) (private.ImageSource, er ref: ref, tmpDir: tmpDir, compressed: nil, - }, nil + } + s.Compat = impl.AddCompat(s) + return s, nil } // Reference returns the reference used to set up this source. @@ -349,10 +353,11 @@ func (s *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca return rc, layerSize, nil } -// GetSignatures returns the image's signatures. It may use a remote (= slow) service. -// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, -// as there can be no secondary manifests. -func (s *ostreeImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +func (s *ostreeImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { if instanceDigest != nil { return nil, errors.New(`Manifest lists are not supported by "ostree:"`) } @@ -370,18 +375,23 @@ func (s *ostreeImageSource) GetSignatures(ctx context.Context, instanceDigest *d s.repo = repo } - signatures := [][]byte{} + signatures := []signature.Signature{} for i := int64(1); i <= lenSignatures; i++ { - sigReader, err := s.readSingleFile(branch, fmt.Sprintf("/signature-%d", i)) + path := fmt.Sprintf("/signature-%d", i) + sigReader, err := s.readSingleFile(branch, path) if err != nil { return nil, err } defer sigReader.Close() - sig, err := os.ReadAll(sigReader) + sigBlob, err := os.ReadAll(sigReader) if err != nil { return nil, err } + sig, err := signature.FromBlob(sigBlob) + if err != nil { + return nil, fmt.Errorf("parsing signature %q: %w", path, err) + } signatures = append(signatures, sig) } return signatures, nil diff --git a/pkg/blobcache/src.go b/pkg/blobcache/src.go index c2983e6a5c..74ad466668 100644 --- a/pkg/blobcache/src.go +++ b/pkg/blobcache/src.go @@ -9,7 +9,9 @@ import ( "github.com/containers/image/v5/internal/image" "github.com/containers/image/v5/internal/imagesource" + "github.com/containers/image/v5/internal/imagesource/impl" "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/transports" @@ -21,6 +23,8 @@ import ( ) type blobCacheSource struct { + impl.Compat + reference *BlobCache source private.ImageSource sys types.SystemContext @@ -37,7 +41,9 @@ func (b *BlobCache) NewImageSource(ctx context.Context, sys *types.SystemContext return nil, perrors.Wrapf(err, "error creating new image source %q", transports.ImageName(b.reference)) } logrus.Debugf("starting to read from image %q using blob cache in %q (compression=%v)", transports.ImageName(b.reference), b.directory, b.compress) - return &blobCacheSource{reference: b, source: imagesource.FromPublic(src), sys: *sys}, nil + s := &blobCacheSource{reference: b, source: imagesource.FromPublic(src), sys: *sys} + s.Compat = impl.AddCompat(s) + return s, nil } func (s *blobCacheSource) Reference() types.ImageReference { @@ -100,16 +106,20 @@ func (s *blobCacheSource) GetBlob(ctx context.Context, blobinfo types.BlobInfo, return rc, size, nil } -func (s *blobCacheSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { - return s.source.GetSignatures(ctx, instanceDigest) +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +func (s *blobCacheSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { + return s.source.GetSignaturesWithFormat(ctx, instanceDigest) } func (s *blobCacheSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { - signatures, err := s.source.GetSignatures(ctx, instanceDigest) + signatures, err := s.source.GetSignaturesWithFormat(ctx, instanceDigest) if err != nil { return nil, perrors.Wrapf(err, "error checking if image %q has signatures", transports.ImageName(s.reference)) } - canReplaceBlobs := !(len(signatures) > 0 && len(signatures[0]) > 0) + canReplaceBlobs := len(signatures) == 0 infos, err := s.source.LayerInfosForCopy(ctx, instanceDigest) if err != nil { diff --git a/sif/src.go b/sif/src.go index a90d70fcde..b645f80dd0 100644 --- a/sif/src.go +++ b/sif/src.go @@ -22,6 +22,7 @@ import ( ) type sifImageSource struct { + impl.Compat impl.PropertyMethodsInitialize impl.NoSignatures impl.DoesNotAffectLayerInfosForCopy @@ -144,7 +145,7 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref sifRefere } succeeded = true - return &sifImageSource{ + s := &sifImageSource{ PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{ HasThreadSafeGetBlob: true, }), @@ -158,7 +159,9 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref sifRefere config: configBytes, configDigest: configDigest, manifest: manifestBytes, - }, nil + } + s.Compat = impl.AddCompat(s) + return s, nil } // Reference returns the reference used to set up this source. diff --git a/storage/storage_src.go b/storage/storage_src.go index 5456e21157..cf255b34b4 100644 --- a/storage/storage_src.go +++ b/storage/storage_src.go @@ -17,6 +17,7 @@ import ( "github.com/containers/image/v5/internal/image" "github.com/containers/image/v5/internal/imagesource/impl" "github.com/containers/image/v5/internal/imagesource/stubs" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/internal/tmpdir" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" @@ -30,6 +31,7 @@ import ( ) type storageImageSource struct { + impl.Compat impl.PropertyMethodsInitialize stubs.NoGetBlobAtInitialize @@ -65,6 +67,7 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, imageRef stor SignatureSizes: []int{}, SignaturesSizes: make(map[digest.Digest][]int), } + image.Compat = impl.AddCompat(image) if img.Metadata != "" { if err := json.Unmarshal([]byte(img.Metadata), image); err != nil { return nil, perrors.Wrap(err, "decoding metadata for source image") @@ -292,10 +295,12 @@ func buildLayerInfosForCopy(manifestInfos []manifest.LayerInfo, physicalInfos [] return res, nil } -// GetSignatures() parses the image's signatures blob into a slice of byte slices. -func (s *storageImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) (signatures [][]byte, err error) { +// GetSignaturesWithFormat returns the image's signatures. It may use a remote (= slow) service. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +func (s *storageImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { var offset int - sigslice := [][]byte{} signatureBlobs := []byte{} signatureSizes := s.SignatureSizes key := "signatures" @@ -312,17 +317,22 @@ func (s *storageImageSource) GetSignatures(ctx context.Context, instanceDigest * } signatureBlobs = data } + res := []signature.Signature{} for _, length := range signatureSizes { if offset+length > len(signatureBlobs) { return nil, fmt.Errorf("looking up signatures data for image %q (%s): expected at least %d bytes, only found %d", s.image.ID, instance, len(signatureBlobs), offset+length) } - sigslice = append(sigslice, signatureBlobs[offset:offset+length]) + sig, err := signature.FromBlob(signatureBlobs[offset : offset+length]) + if err != nil { + return nil, fmt.Errorf("parsing signature at (%d, %d): %w", offset, length, err) + } + res = append(res, sig) offset += length } if offset != len(signatureBlobs) { return nil, fmt.Errorf("signatures data (%s) contained %d extra bytes", instance, len(signatureBlobs)-offset) } - return sigslice, nil + return res, nil } // getSize() adds up the sizes of the image's data blobs (which includes the configuration blob), the diff --git a/tarball/tarball_src.go b/tarball/tarball_src.go index 039df406c1..1dc4c3ad94 100644 --- a/tarball/tarball_src.go +++ b/tarball/tarball_src.go @@ -21,6 +21,7 @@ import ( ) type tarballImageSource struct { + impl.Compat impl.PropertyMethodsInitialize impl.NoSignatures impl.DoesNotAffectLayerInfosForCopy @@ -209,6 +210,7 @@ func (r *tarballReference) NewImageSource(ctx context.Context, sys *types.System configSize: configSize, manifest: manifestBytes, } + src.Compat = impl.AddCompat(src) return src, nil }