Skip to content

Commit

Permalink
Introduce ImageSource.GetSignaturesWithFormat
Browse files Browse the repository at this point in the history
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
  • Loading branch information
mtrmac committed Jul 6, 2022
1 parent a295af7 commit b1d0553
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 55 deletions.
20 changes: 15 additions & 5 deletions directory/directory_src.go
Expand Up @@ -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
Expand All @@ -24,14 +27,16 @@ 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,
}),
NoGetBlobAtInitialize: stubs.NoGetBlobAt(ref),

ref: ref,
}
s.Compat = impl.AddCompat(s)
return s
}

// Reference returns the reference used to set up this source, _as specified by the user_
Expand Down Expand Up @@ -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
Expand Down
41 changes: 26 additions & 15 deletions docker/docker_image_src.go
Expand Up @@ -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"
Expand All @@ -29,6 +30,7 @@ import (
)

type dockerImageSource struct {
impl.Compat
impl.PropertyMethodsInitialize
impl.DoesNotAffectLayerInfosForCopy
stubs.ImplementsGetBlobAt
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand All @@ -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":
Expand All @@ -556,21 +563,25 @@ 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:
return nil, false, fmt.Errorf("Unsupported scheme when reading signature from %s", url.Redacted())
}
}

// 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
Expand All @@ -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
Expand Down
5 changes: 4 additions & 1 deletion docker/internal/tarfile/src.go
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}),
Expand All @@ -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.
Expand Down
54 changes: 54 additions & 0 deletions 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
}
5 changes: 3 additions & 2 deletions internal/imagesource/impl/signatures.go
Expand Up @@ -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
}
20 changes: 20 additions & 0 deletions 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
Expand Down Expand Up @@ -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
}
6 changes: 6 additions & 0 deletions internal/private/private.go
Expand Up @@ -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.
Expand Down
16 changes: 11 additions & 5 deletions oci/archive/oci_src.go
Expand Up @@ -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"
Expand All @@ -16,6 +18,8 @@ import (
)

type ociArchiveImageSource struct {
impl.Compat

ref ociArchiveReference
unpackedSrc private.ImageSource
tempDirRef tempDirOCIRef
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit b1d0553

Please sign in to comment.