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