zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/sync/references/references.go (about)

     1  //go:build sync
     2  // +build sync
     3  
     4  package references
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"net/http"
    11  
    12  	godigest "github.com/opencontainers/go-digest"
    13  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    14  	artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
    15  	"github.com/sigstore/cosign/v2/pkg/oci/static"
    16  
    17  	"zotregistry.io/zot/pkg/common"
    18  	client "zotregistry.io/zot/pkg/extensions/sync/httpclient"
    19  	"zotregistry.io/zot/pkg/log"
    20  	mTypes "zotregistry.io/zot/pkg/meta/types"
    21  	"zotregistry.io/zot/pkg/storage"
    22  	storageTypes "zotregistry.io/zot/pkg/storage/types"
    23  )
    24  
    25  type Reference interface {
    26  	// Returns name of reference (OCIReference/CosignReference/OrasReference)
    27  	Name() string
    28  	// Returns whether or not image is signed
    29  	IsSigned(ctx context.Context, upstreamRepo, subjectDigestStr string) bool
    30  	// Sync recursively all references for a subject digest (can be image/artifacts/signatures)
    31  	SyncReferences(ctx context.Context, localRepo, upstreamRepo, subjectDigestStr string) ([]godigest.Digest, error)
    32  }
    33  
    34  type References struct {
    35  	referenceList []Reference
    36  	log           log.Logger
    37  }
    38  
    39  func NewReferences(httpClient *client.Client, storeController storage.StoreController,
    40  	metaDB mTypes.MetaDB, log log.Logger,
    41  ) References {
    42  	refs := References{log: log}
    43  
    44  	refs.referenceList = append(refs.referenceList, NewCosignReference(httpClient, storeController, metaDB, log))
    45  	refs.referenceList = append(refs.referenceList, NewTagReferences(httpClient, storeController, metaDB, log))
    46  	refs.referenceList = append(refs.referenceList, NewOciReferences(httpClient, storeController, metaDB, log))
    47  	refs.referenceList = append(refs.referenceList, NewORASReferences(httpClient, storeController, metaDB, log))
    48  
    49  	return refs
    50  }
    51  
    52  func (refs References) IsSigned(ctx context.Context, upstreamRepo, subjectDigestStr string) bool {
    53  	for _, ref := range refs.referenceList {
    54  		ok := ref.IsSigned(ctx, upstreamRepo, subjectDigestStr)
    55  		if ok {
    56  			return true
    57  		}
    58  	}
    59  
    60  	return false
    61  }
    62  
    63  func (refs References) SyncAll(ctx context.Context, localRepo, upstreamRepo, subjectDigestStr string) error {
    64  	seen := &[]godigest.Digest{}
    65  
    66  	return refs.syncAll(ctx, localRepo, upstreamRepo, subjectDigestStr, seen)
    67  }
    68  
    69  func (refs References) syncAll(ctx context.Context, localRepo, upstreamRepo,
    70  	subjectDigestStr string, seen *[]godigest.Digest,
    71  ) error {
    72  	var err error
    73  
    74  	var syncedRefsDigests []godigest.Digest
    75  
    76  	// mark subject digest as seen as soon as it comes in
    77  	*seen = append(*seen, godigest.Digest(subjectDigestStr))
    78  
    79  	// for each reference type(cosign/oci/oras reference)
    80  	for _, ref := range refs.referenceList {
    81  		syncedRefsDigests, err = ref.SyncReferences(ctx, localRepo, upstreamRepo, subjectDigestStr)
    82  		if err != nil {
    83  			refs.log.Error().Err(err).
    84  				Str("reference type", ref.Name()).
    85  				Str("image", fmt.Sprintf("%s:%s", upstreamRepo, subjectDigestStr)).
    86  				Msg("couldn't sync image referrer")
    87  		}
    88  
    89  		// for each synced references
    90  		for _, refDigest := range syncedRefsDigests {
    91  			if !common.Contains(*seen, refDigest) {
    92  				// sync all references pointing to this one
    93  				err = refs.syncAll(ctx, localRepo, upstreamRepo, refDigest.String(), seen)
    94  			}
    95  		}
    96  	}
    97  
    98  	return err
    99  }
   100  
   101  func (refs References) SyncReference(ctx context.Context, localRepo, upstreamRepo,
   102  	subjectDigestStr, referenceType string,
   103  ) error {
   104  	var err error
   105  
   106  	var syncedRefsDigests []godigest.Digest
   107  
   108  	for _, ref := range refs.referenceList {
   109  		if ref.Name() == referenceType {
   110  			syncedRefsDigests, err = ref.SyncReferences(ctx, localRepo, upstreamRepo, subjectDigestStr)
   111  			if err != nil {
   112  				refs.log.Error().Err(err).
   113  					Str("reference type", ref.Name()).
   114  					Str("image", fmt.Sprintf("%s:%s", upstreamRepo, subjectDigestStr)).
   115  					Msg("couldn't sync image referrer")
   116  
   117  				return err
   118  			}
   119  
   120  			for _, refDigest := range syncedRefsDigests {
   121  				err = refs.SyncAll(ctx, localRepo, upstreamRepo, refDigest.String())
   122  			}
   123  		}
   124  	}
   125  
   126  	return err
   127  }
   128  
   129  func syncBlob(ctx context.Context, client *client.Client, imageStore storageTypes.ImageStore,
   130  	localRepo, remoteRepo string, digest godigest.Digest, log log.Logger,
   131  ) error {
   132  	var resultPtr interface{}
   133  
   134  	body, _, statusCode, err := client.MakeGetRequest(ctx, resultPtr, "", "v2", remoteRepo, "blobs", digest.String())
   135  	if err != nil {
   136  		if statusCode != http.StatusOK {
   137  			log.Info().Str("repo", remoteRepo).Str("digest", digest.String()).Msg("couldn't get remote blob")
   138  
   139  			return err
   140  		}
   141  	}
   142  
   143  	_, _, err = imageStore.FullBlobUpload(localRepo, bytes.NewBuffer(body), digest)
   144  	if err != nil {
   145  		log.Error().Str("errorType", common.TypeOf(err)).Str("digest", digest.String()).Str("repo", localRepo).
   146  			Err(err).Msg("couldn't upload blob")
   147  
   148  		return err
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func manifestsEqual(manifest1, manifest2 ispec.Manifest) bool {
   155  	if manifest1.Config.Digest == manifest2.Config.Digest &&
   156  		manifest1.Config.MediaType == manifest2.Config.MediaType &&
   157  		manifest1.Config.Size == manifest2.Config.Size {
   158  		if descriptorsEqual(manifest1.Layers, manifest2.Layers) {
   159  			return true
   160  		}
   161  	}
   162  
   163  	return false
   164  }
   165  
   166  func artifactDescriptorsEqual(desc1, desc2 []artifactspec.Descriptor) bool {
   167  	if len(desc1) != len(desc2) {
   168  		return false
   169  	}
   170  
   171  	for id, desc := range desc1 {
   172  		if desc.Digest != desc2[id].Digest ||
   173  			desc.Size != desc2[id].Size ||
   174  			desc.MediaType != desc2[id].MediaType ||
   175  			desc.ArtifactType != desc2[id].ArtifactType {
   176  			return false
   177  		}
   178  	}
   179  
   180  	return true
   181  }
   182  
   183  func descriptorsEqual(desc1, desc2 []ispec.Descriptor) bool {
   184  	if len(desc1) != len(desc2) {
   185  		return false
   186  	}
   187  
   188  	for id, desc := range desc1 {
   189  		if !descriptorEqual(desc, desc2[id]) {
   190  			return false
   191  		}
   192  	}
   193  
   194  	return true
   195  }
   196  
   197  func descriptorEqual(desc1, desc2 ispec.Descriptor) bool {
   198  	if desc1.Size == desc2.Size &&
   199  		desc1.Digest == desc2.Digest &&
   200  		desc1.MediaType == desc2.MediaType &&
   201  		desc1.Annotations[static.SignatureAnnotationKey] == desc2.Annotations[static.SignatureAnnotationKey] {
   202  		return true
   203  	}
   204  
   205  	return false
   206  }
   207  
   208  func getNotationManifestsFromOCIRefs(ociRefs ispec.Index) []ispec.Descriptor {
   209  	notaryManifests := []ispec.Descriptor{}
   210  
   211  	for _, ref := range ociRefs.Manifests {
   212  		if ref.ArtifactType == common.ArtifactTypeNotation {
   213  			notaryManifests = append(notaryManifests, ref)
   214  		}
   215  	}
   216  
   217  	return notaryManifests
   218  }
   219  
   220  func getCosignManifestsFromOCIRefs(ociRefs ispec.Index) []ispec.Descriptor {
   221  	cosignManifests := []ispec.Descriptor{}
   222  
   223  	for _, ref := range ociRefs.Manifests {
   224  		if ref.ArtifactType == common.ArtifactTypeCosign {
   225  			cosignManifests = append(cosignManifests, ref)
   226  		}
   227  	}
   228  
   229  	return cosignManifests
   230  }