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 }