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 }