zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/sync/references/cosign.go (about)

     1  //go:build sync
     2  // +build sync
     3  
     4  package references
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"strings"
    12  
    13  	godigest "github.com/opencontainers/go-digest"
    14  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    15  	"github.com/sigstore/cosign/v2/pkg/oci/remote"
    16  
    17  	zerr "zotregistry.dev/zot/errors"
    18  	"zotregistry.dev/zot/pkg/common"
    19  	"zotregistry.dev/zot/pkg/extensions/sync/constants"
    20  	client "zotregistry.dev/zot/pkg/extensions/sync/httpclient"
    21  	"zotregistry.dev/zot/pkg/log"
    22  	"zotregistry.dev/zot/pkg/meta"
    23  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    24  	"zotregistry.dev/zot/pkg/storage"
    25  )
    26  
    27  type CosignReference struct {
    28  	client          *client.Client
    29  	storeController storage.StoreController
    30  	metaDB          mTypes.MetaDB
    31  	log             log.Logger
    32  }
    33  
    34  func NewCosignReference(httpClient *client.Client, storeController storage.StoreController,
    35  	metaDB mTypes.MetaDB, log log.Logger,
    36  ) CosignReference {
    37  	return CosignReference{
    38  		client:          httpClient,
    39  		storeController: storeController,
    40  		metaDB:          metaDB,
    41  		log:             log,
    42  	}
    43  }
    44  
    45  func (ref CosignReference) Name() string {
    46  	return constants.Cosign
    47  }
    48  
    49  func (ref CosignReference) IsSigned(ctx context.Context, upstreamRepo, subjectDigestStr string) bool {
    50  	cosignSignatureTag := getCosignSignatureTagFromSubjectDigest(subjectDigestStr)
    51  	_, _, err := ref.getManifest(ctx, upstreamRepo, cosignSignatureTag)
    52  
    53  	return err == nil
    54  }
    55  
    56  func (ref CosignReference) canSkipReferences(localRepo, digest string, manifest *ispec.Manifest) (
    57  	bool, error,
    58  ) {
    59  	if manifest == nil {
    60  		return true, nil
    61  	}
    62  
    63  	imageStore := ref.storeController.GetImageStore(localRepo)
    64  
    65  	// check cosign signature already synced
    66  	_, localDigest, _, err := imageStore.GetImageManifest(localRepo, digest)
    67  	if err != nil {
    68  		if errors.Is(err, zerr.ErrManifestNotFound) {
    69  			return false, nil
    70  		}
    71  
    72  		ref.log.Error().Str("errorType", common.TypeOf(err)).Err(err).
    73  			Str("repository", localRepo).Str("reference", digest).
    74  			Msg("couldn't get local cosign manifest")
    75  
    76  		return false, err
    77  	}
    78  
    79  	if localDigest.String() != digest {
    80  		return false, nil
    81  	}
    82  
    83  	ref.log.Info().Str("repository", localRepo).Str("reference", digest).
    84  		Msg("skipping syncing cosign reference, already synced")
    85  
    86  	return true, nil
    87  }
    88  
    89  func (ref CosignReference) SyncReferences(ctx context.Context, localRepo, remoteRepo, subjectDigestStr string) (
    90  	[]godigest.Digest, error,
    91  ) {
    92  	cosignTags := getCosignTagsFromSubjectDigest(subjectDigestStr)
    93  
    94  	refsDigests := make([]godigest.Digest, 0, len(cosignTags))
    95  
    96  	for _, cosignTag := range cosignTags {
    97  		manifest, manifestBuf, err := ref.getManifest(ctx, remoteRepo, cosignTag)
    98  		if err != nil {
    99  			if errors.Is(err, zerr.ErrSyncReferrerNotFound) {
   100  				continue
   101  			}
   102  
   103  			return refsDigests, err
   104  		}
   105  
   106  		digest := godigest.FromBytes(manifestBuf)
   107  
   108  		skip, err := ref.canSkipReferences(localRepo, digest.String(), manifest)
   109  		if err != nil {
   110  			ref.log.Error().Err(err).Str("repository", localRepo).Str("subject", subjectDigestStr).
   111  				Msg("couldn't check if the remote image cosign reference can be skipped")
   112  		}
   113  
   114  		if skip {
   115  			refsDigests = append(refsDigests, digest)
   116  
   117  			continue
   118  		}
   119  
   120  		imageStore := ref.storeController.GetImageStore(localRepo)
   121  
   122  		ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
   123  			Msg("syncing cosign reference for image")
   124  
   125  		for _, blob := range manifest.Layers {
   126  			if err := syncBlob(ctx, ref.client, imageStore, localRepo, remoteRepo, blob.Digest, ref.log); err != nil {
   127  				return refsDigests, err
   128  			}
   129  		}
   130  
   131  		// sync config blob
   132  		if err := syncBlob(ctx, ref.client, imageStore, localRepo, remoteRepo, manifest.Config.Digest, ref.log); err != nil {
   133  			return refsDigests, err
   134  		}
   135  
   136  		// push manifest
   137  		referenceDigest, _, err := imageStore.PutImageManifest(localRepo, cosignTag,
   138  			ispec.MediaTypeImageManifest, manifestBuf)
   139  		if err != nil {
   140  			ref.log.Error().Str("errorType", common.TypeOf(err)).
   141  				Str("repository", localRepo).Str("subject", subjectDigestStr).
   142  				Err(err).Msg("couldn't upload cosign reference manifest for image")
   143  
   144  			return refsDigests, err
   145  		}
   146  
   147  		refsDigests = append(refsDigests, digest)
   148  
   149  		ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
   150  			Msg("successfully synced cosign reference for image")
   151  
   152  		if ref.metaDB != nil {
   153  			ref.log.Debug().Str("repository", localRepo).Str("subject", subjectDigestStr).Str("component", "metadb").
   154  				Msg("trying to sync cosign reference for image")
   155  
   156  			err = meta.SetImageMetaFromInput(ctx, localRepo, cosignTag, ispec.MediaTypeImageManifest,
   157  				referenceDigest, manifestBuf, ref.storeController.GetImageStore(localRepo),
   158  				ref.metaDB, ref.log)
   159  
   160  			if err != nil {
   161  				return refsDigests, fmt.Errorf("failed to set metadata for cosign reference in '%s@%s': %w",
   162  					localRepo, subjectDigestStr, err)
   163  			}
   164  
   165  			ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).Str("component", "metadb").
   166  				Msg("successfully added cosign reference for image")
   167  		}
   168  	}
   169  
   170  	return refsDigests, nil
   171  }
   172  
   173  func (ref CosignReference) getManifest(ctx context.Context, repo, cosignTag string) (*ispec.Manifest, []byte, error) {
   174  	var cosignManifest ispec.Manifest
   175  
   176  	body, _, statusCode, err := ref.client.MakeGetRequest(ctx, &cosignManifest, ispec.MediaTypeImageManifest,
   177  		"v2", repo, "manifests", cosignTag)
   178  	if err != nil {
   179  		if statusCode == http.StatusNotFound {
   180  			ref.log.Debug().Str("errorType", common.TypeOf(err)).
   181  				Str("repository", repo).Str("tag", cosignTag).
   182  				Err(err).Msg("couldn't find any cosign manifest for image")
   183  
   184  			return nil, nil, zerr.ErrSyncReferrerNotFound
   185  		}
   186  
   187  		return nil, nil, err
   188  	}
   189  
   190  	return &cosignManifest, body, nil
   191  }
   192  
   193  func getCosignSignatureTagFromSubjectDigest(digestStr string) string {
   194  	return strings.Replace(digestStr, ":", "-", 1) + "." + remote.SignatureTagSuffix
   195  }
   196  
   197  func getCosignSBOMTagFromSubjectDigest(digestStr string) string {
   198  	return strings.Replace(digestStr, ":", "-", 1) + "." + remote.SBOMTagSuffix
   199  }
   200  
   201  func getCosignTagsFromSubjectDigest(digestStr string) []string {
   202  	var cosignTags []string
   203  
   204  	// signature tag
   205  	cosignTags = append(cosignTags, getCosignSignatureTagFromSubjectDigest(digestStr))
   206  	// sbom tag
   207  	cosignTags = append(cosignTags, getCosignSBOMTagFromSubjectDigest(digestStr))
   208  
   209  	return cosignTags
   210  }
   211  
   212  // this function will check if tag is a cosign tag (signature or sbom).
   213  func IsCosignTag(tag string) bool {
   214  	if strings.HasPrefix(tag, "sha256-") &&
   215  		(strings.HasSuffix(tag, remote.SignatureTagSuffix) || strings.HasSuffix(tag, remote.SBOMTagSuffix)) {
   216  		return true
   217  	}
   218  
   219  	return false
   220  }