From 031a819f5e75954775dc377d9cb5420a95832d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Tue, 5 Jul 2022 19:54:02 +0200 Subject: [PATCH] Generalize copy.Image to be able to copy signatures with any format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- copy/copy.go | 17 ++++++++-------- copy/sign.go | 5 +++-- copy/sign_test.go | 9 ++++++-- internal/image/sourced.go | 6 ++++++ internal/image/unparsed.go | 26 ++++++++++++++++++++---- internal/private/private.go | 2 ++ signature/policy_reference_match_test.go | 6 +----- 7 files changed, 50 insertions(+), 21 deletions(-) diff --git a/copy/copy.go b/copy/copy.go index 78f555f183..e48b87da13 100644 --- a/copy/copy.go +++ b/copy/copy.go @@ -19,6 +19,7 @@ import ( "github.com/containers/image/v5/internal/imagesource" "github.com/containers/image/v5/internal/pkg/platform" "github.com/containers/image/v5/internal/private" + internalsig "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/pkg/blobinfocache" "github.com/containers/image/v5/pkg/compression" @@ -396,12 +397,12 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur updatedList := originalList.Clone() // Read and/or clear the set of signatures for this list. - var sigs [][]byte + var sigs []internalsig.Signature if options.RemoveSignatures { - sigs = [][]byte{} + sigs = []internalsig.Signature{} } else { c.Printf("Getting image list signatures\n") - s, err := c.rawSource.GetSignatures(ctx, nil) + s, err := c.rawSource.GetSignaturesWithFormat(ctx, nil) if err != nil { return nil, perrors.Wrap(err, "reading signatures") } @@ -576,7 +577,7 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur } c.Printf("Storing list signatures\n") - if err := c.dest.PutSignatures(ctx, sigs, nil); err != nil { + if err := c.dest.PutSignaturesWithFormat(ctx, sigs, nil); err != nil { return nil, perrors.Wrap(err, "writing signatures") } @@ -639,12 +640,12 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli return nil, "", "", err } - var sigs [][]byte + var sigs []internalsig.Signature if options.RemoveSignatures { - sigs = [][]byte{} + sigs = []internalsig.Signature{} } else { c.Printf("Getting image source signatures\n") - s, err := src.Signatures(ctx) + s, err := src.SignaturesWithFormat(ctx) if err != nil { return nil, "", "", perrors.Wrap(err, "reading signatures") } @@ -807,7 +808,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli } c.Printf("Storing signatures\n") - if err := c.dest.PutSignatures(ctx, sigs, targetInstance); err != nil { + if err := c.dest.PutSignaturesWithFormat(ctx, sigs, targetInstance); err != nil { return nil, "", "", perrors.Wrap(err, "writing signatures") } diff --git a/copy/sign.go b/copy/sign.go index 08e0c6c761..59ee5a70b3 100644 --- a/copy/sign.go +++ b/copy/sign.go @@ -4,13 +4,14 @@ import ( "fmt" "github.com/containers/image/v5/docker/reference" + internalsig "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/transports" perrors "github.com/pkg/errors" ) // createSignature creates a new signature of manifest using keyIdentity. -func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase string, identity reference.Named) ([]byte, error) { +func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase string, identity reference.Named) (internalsig.Signature, error) { mech, err := signature.NewGPGSigningMechanism() if err != nil { return nil, perrors.Wrap(err, "initializing GPG") @@ -36,5 +37,5 @@ func (c *copier) createSignature(manifest []byte, keyIdentity string, passphrase if err != nil { return nil, perrors.Wrap(err, "creating signature") } - return newSig, nil + return internalsig.SimpleSigningFromBlob(newSig), nil } diff --git a/copy/sign_test.go b/copy/sign_test.go index 5fa2df3264..b3a076aa50 100644 --- a/copy/sign_test.go +++ b/copy/sign_test.go @@ -8,6 +8,7 @@ import ( "github.com/containers/image/v5/directory" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/internal/imagedestination" + internalsig "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/types" @@ -81,7 +82,9 @@ func TestCreateSignature(t *testing.T) { // Signing without overriding the identity uses the docker reference sig, err := c.createSignature(manifestBlob, testKeyFingerprint, "", nil) require.NoError(t, err) - verified, err := signature.VerifyDockerManifestSignature(sig, manifestBlob, "docker.io/library/busybox:latest", mech, testKeyFingerprint) + simpleSig, ok := sig.(internalsig.SimpleSigning) + require.True(t, ok) + verified, err := signature.VerifyDockerManifestSignature(simpleSig.UntrustedSignature, manifestBlob, "docker.io/library/busybox:latest", mech, testKeyFingerprint) require.NoError(t, err) assert.Equal(t, "docker.io/library/busybox:latest", verified.DockerReference) assert.Equal(t, manifestDigest, verified.DockerManifestDigest) @@ -91,7 +94,9 @@ func TestCreateSignature(t *testing.T) { require.NoError(t, err) sig, err = c.createSignature(manifestBlob, testKeyFingerprint, "", ref) require.NoError(t, err) - verified, err = signature.VerifyDockerManifestSignature(sig, manifestBlob, "myregistry.io/myrepo:mytag", mech, testKeyFingerprint) + simpleSig, ok = sig.(internalsig.SimpleSigning) + require.True(t, ok) + verified, err = signature.VerifyDockerManifestSignature(simpleSig.UntrustedSignature, manifestBlob, "myregistry.io/myrepo:mytag", mech, testKeyFingerprint) require.NoError(t, err) assert.Equal(t, "myregistry.io/myrepo:mytag", verified.DockerReference) assert.Equal(t, manifestDigest, verified.DockerManifestDigest) diff --git a/internal/image/sourced.go b/internal/image/sourced.go index dc09a9e04b..ff7350113f 100644 --- a/internal/image/sourced.go +++ b/internal/image/sourced.go @@ -6,6 +6,7 @@ package image import ( "context" + "github.com/containers/image/v5/internal/signature" "github.com/containers/image/v5/types" ) @@ -129,6 +130,11 @@ func (i *SourcedImage) Manifest(ctx context.Context) ([]byte, string, error) { return i.ManifestBlob, i.ManifestMIMEType, nil } +// SignaturesWithFormat is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. +func (i *SourcedImage) SignaturesWithFormat(ctx context.Context) ([]signature.Signature, error) { + return i.UnparsedImage.signaturesWithFormat(ctx) +} + func (i *SourcedImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { return i.UnparsedImage.src.LayerInfosForCopy(ctx, i.UnparsedImage.instanceDigest) } diff --git a/internal/image/unparsed.go b/internal/image/unparsed.go index deb2e0cd0b..4b1d431ca8 100644 --- a/internal/image/unparsed.go +++ b/internal/image/unparsed.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/internal/imagesource" + "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" @@ -16,13 +19,13 @@ import ( // // This is publicly visible as c/image/image.UnparsedImage. type UnparsedImage struct { - src types.ImageSource + src private.ImageSource instanceDigest *digest.Digest cachedManifest []byte // A private cache for Manifest(); nil if not yet known. // A private cache for Manifest(), may be the empty string if guessing failed. // Valid iff cachedManifest is not nil. cachedManifestMIMEType string - cachedSignatures [][]byte // A private cache for Signatures(); nil if not yet known. + cachedSignatures []signature.Signature // A private cache for Signatures(); nil if not yet known. } // UnparsedInstance returns a types.UnparsedImage implementation for (source, instanceDigest). @@ -33,7 +36,7 @@ type UnparsedImage struct { // This is publicly visible as c/image/image.UnparsedInstance. func UnparsedInstance(src types.ImageSource, instanceDigest *digest.Digest) *UnparsedImage { return &UnparsedImage{ - src: src, + src: imagesource.FromPublic(src), instanceDigest: instanceDigest, } } @@ -89,8 +92,23 @@ func (i *UnparsedImage) expectedManifestDigest() (digest.Digest, bool) { // Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. func (i *UnparsedImage) Signatures(ctx context.Context) ([][]byte, error) { + sigs, err := i.signaturesWithFormat(ctx) + 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 +} + +// signaturesWithFormat is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. +func (i *UnparsedImage) signaturesWithFormat(ctx context.Context) ([]signature.Signature, error) { if i.cachedSignatures == nil { - sigs, err := i.src.GetSignatures(ctx, i.instanceDigest) + sigs, err := i.src.GetSignaturesWithFormat(ctx, i.instanceDigest) if err != nil { return nil, err } diff --git a/internal/private/private.go b/internal/private/private.go index 40189e0435..613c7b136a 100644 --- a/internal/private/private.go +++ b/internal/private/private.go @@ -37,6 +37,8 @@ type ImageSource interface { type ImageDestinationInternalOnly interface { // SupportsPutBlobPartial returns true if PutBlobPartial is supported. SupportsPutBlobPartial() bool + // FIXME: Add SupportsSignaturesWithFormat or something like that, to allow early failures + // on unsupported formats. // PutBlobWithOptions writes contents of stream and returns data representing the result. // inputInfo.Digest can be optionally provided if known; if provided, and stream is read to the end without error, the digest MUST match the stream contents. diff --git a/signature/policy_reference_match_test.go b/signature/policy_reference_match_test.go index 92fb227a61..746f90269f 100644 --- a/signature/policy_reference_match_test.go +++ b/signature/policy_reference_match_test.go @@ -69,11 +69,7 @@ type refImageReferenceMock struct { } func (ref refImageReferenceMock) Transport() types.ImageTransport { - // We use this in error messages, so sady we must return something. But right now we do so only when DockerReference is nil, so restrict to that. - if ref.ref == nil { - return mocks.NameImageTransport("== Transport mock") - } - panic("unexpected call to a mock function") + return mocks.NameImageTransport("== Transport mock") } func (ref refImageReferenceMock) StringWithinTransport() string { // We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that.