2023-05-31 12:26:23 -05:00
|
|
|
//go:build sync
|
|
|
|
// +build sync
|
|
|
|
|
|
|
|
package references
|
|
|
|
|
|
|
|
import (
|
2023-09-05 11:48:56 -05:00
|
|
|
"context"
|
2023-05-31 12:26:23 -05:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
godigest "github.com/opencontainers/go-digest"
|
2023-05-31 12:26:23 -05:00
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
"github.com/sigstore/cosign/v2/pkg/oci/remote"
|
|
|
|
|
2024-01-31 23:34:07 -05:00
|
|
|
zerr "zotregistry.dev/zot/errors"
|
|
|
|
"zotregistry.dev/zot/pkg/common"
|
|
|
|
"zotregistry.dev/zot/pkg/extensions/sync/constants"
|
|
|
|
client "zotregistry.dev/zot/pkg/extensions/sync/httpclient"
|
|
|
|
"zotregistry.dev/zot/pkg/log"
|
|
|
|
"zotregistry.dev/zot/pkg/meta"
|
|
|
|
mTypes "zotregistry.dev/zot/pkg/meta/types"
|
|
|
|
"zotregistry.dev/zot/pkg/storage"
|
2023-05-31 12:26:23 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type CosignReference struct {
|
|
|
|
client *client.Client
|
|
|
|
storeController storage.StoreController
|
2023-07-18 12:27:26 -05:00
|
|
|
metaDB mTypes.MetaDB
|
2023-05-31 12:26:23 -05:00
|
|
|
log log.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewCosignReference(httpClient *client.Client, storeController storage.StoreController,
|
2023-07-18 12:27:26 -05:00
|
|
|
metaDB mTypes.MetaDB, log log.Logger,
|
2023-05-31 12:26:23 -05:00
|
|
|
) CosignReference {
|
|
|
|
return CosignReference{
|
|
|
|
client: httpClient,
|
|
|
|
storeController: storeController,
|
2023-07-18 12:27:26 -05:00
|
|
|
metaDB: metaDB,
|
2023-05-31 12:26:23 -05:00
|
|
|
log: log,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ref CosignReference) Name() string {
|
|
|
|
return constants.Cosign
|
|
|
|
}
|
|
|
|
|
2023-09-05 11:48:56 -05:00
|
|
|
func (ref CosignReference) IsSigned(ctx context.Context, upstreamRepo, subjectDigestStr string) bool {
|
2023-05-31 12:26:23 -05:00
|
|
|
cosignSignatureTag := getCosignSignatureTagFromSubjectDigest(subjectDigestStr)
|
2023-09-05 11:48:56 -05:00
|
|
|
_, _, err := ref.getManifest(ctx, upstreamRepo, cosignSignatureTag)
|
2023-05-31 12:26:23 -05:00
|
|
|
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
func (ref CosignReference) canSkipReferences(localRepo, digest string, manifest *ispec.Manifest) (
|
2023-05-31 12:26:23 -05:00
|
|
|
bool, error,
|
|
|
|
) {
|
|
|
|
if manifest == nil {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
imageStore := ref.storeController.GetImageStore(localRepo)
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
// check cosign signature already synced
|
|
|
|
_, localDigest, _, err := imageStore.GetImageManifest(localRepo, digest)
|
2023-05-31 12:26:23 -05:00
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, zerr.ErrManifestNotFound) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ref.log.Error().Str("errorType", common.TypeOf(err)).Err(err).
|
2023-06-16 12:27:33 -05:00
|
|
|
Str("repository", localRepo).Str("reference", digest).
|
2023-05-31 12:26:23 -05:00
|
|
|
Msg("couldn't get local cosign manifest")
|
|
|
|
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
if localDigest.String() != digest {
|
2023-05-31 12:26:23 -05:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
ref.log.Info().Str("repository", localRepo).Str("reference", digest).
|
|
|
|
Msg("skipping syncing cosign reference, already synced")
|
2023-05-31 12:26:23 -05:00
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2023-09-05 11:48:56 -05:00
|
|
|
func (ref CosignReference) SyncReferences(ctx context.Context, localRepo, remoteRepo, subjectDigestStr string) (
|
|
|
|
[]godigest.Digest, error,
|
|
|
|
) {
|
2023-05-31 12:26:23 -05:00
|
|
|
cosignTags := getCosignTagsFromSubjectDigest(subjectDigestStr)
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
refsDigests := make([]godigest.Digest, 0, len(cosignTags))
|
|
|
|
|
2023-05-31 12:26:23 -05:00
|
|
|
for _, cosignTag := range cosignTags {
|
2023-09-05 11:48:56 -05:00
|
|
|
manifest, manifestBuf, err := ref.getManifest(ctx, remoteRepo, cosignTag)
|
2023-06-16 12:27:33 -05:00
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, zerr.ErrSyncReferrerNotFound) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return refsDigests, err
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
digest := godigest.FromBytes(manifestBuf)
|
|
|
|
|
|
|
|
skip, err := ref.canSkipReferences(localRepo, digest.String(), manifest)
|
2023-05-31 12:26:23 -05:00
|
|
|
if err != nil {
|
|
|
|
ref.log.Error().Err(err).Str("repository", localRepo).Str("subject", subjectDigestStr).
|
|
|
|
Msg("couldn't check if the remote image cosign reference can be skipped")
|
|
|
|
}
|
|
|
|
|
|
|
|
if skip {
|
2023-06-16 12:27:33 -05:00
|
|
|
refsDigests = append(refsDigests, digest)
|
|
|
|
|
2023-05-31 12:26:23 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
imageStore := ref.storeController.GetImageStore(localRepo)
|
|
|
|
|
|
|
|
ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
|
|
|
|
Msg("syncing cosign reference for image")
|
|
|
|
|
|
|
|
for _, blob := range manifest.Layers {
|
2023-09-05 11:48:56 -05:00
|
|
|
if err := syncBlob(ctx, ref.client, imageStore, localRepo, remoteRepo, blob.Digest, ref.log); err != nil {
|
2023-06-16 12:27:33 -05:00
|
|
|
return refsDigests, err
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sync config blob
|
2023-09-05 11:48:56 -05:00
|
|
|
if err := syncBlob(ctx, ref.client, imageStore, localRepo, remoteRepo, manifest.Config.Digest, ref.log); err != nil {
|
2023-06-16 12:27:33 -05:00
|
|
|
return refsDigests, err
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// push manifest
|
|
|
|
referenceDigest, _, err := imageStore.PutImageManifest(localRepo, cosignTag,
|
|
|
|
ispec.MediaTypeImageManifest, manifestBuf)
|
|
|
|
if err != nil {
|
|
|
|
ref.log.Error().Str("errorType", common.TypeOf(err)).
|
|
|
|
Str("repository", localRepo).Str("subject", subjectDigestStr).
|
|
|
|
Err(err).Msg("couldn't upload cosign reference manifest for image")
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
return refsDigests, err
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
refsDigests = append(refsDigests, digest)
|
|
|
|
|
2023-05-31 12:26:23 -05:00
|
|
|
ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
|
|
|
|
Msg("successfully synced cosign reference for image")
|
|
|
|
|
2023-07-18 12:27:26 -05:00
|
|
|
if ref.metaDB != nil {
|
2023-12-08 03:05:02 -05:00
|
|
|
ref.log.Debug().Str("repository", localRepo).Str("subject", subjectDigestStr).Str("component", "metadb").
|
|
|
|
Msg("trying to sync cosign reference for image")
|
2023-05-31 12:26:23 -05:00
|
|
|
|
2023-11-01 11:16:18 -05:00
|
|
|
err = meta.SetImageMetaFromInput(ctx, localRepo, cosignTag, ispec.MediaTypeImageManifest,
|
2023-10-30 15:06:04 -05:00
|
|
|
referenceDigest, manifestBuf, ref.storeController.GetImageStore(localRepo),
|
|
|
|
ref.metaDB, ref.log)
|
2023-05-31 12:26:23 -05:00
|
|
|
if err != nil {
|
2023-06-16 12:27:33 -05:00
|
|
|
return refsDigests, fmt.Errorf("failed to set metadata for cosign reference in '%s@%s': %w",
|
|
|
|
localRepo, subjectDigestStr, err)
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
2023-12-08 03:05:02 -05:00
|
|
|
ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).Str("component", "metadb").
|
|
|
|
Msg("successfully added cosign reference for image")
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
return refsDigests, nil
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
2023-09-05 11:48:56 -05:00
|
|
|
func (ref CosignReference) getManifest(ctx context.Context, repo, cosignTag string) (*ispec.Manifest, []byte, error) {
|
2023-05-31 12:26:23 -05:00
|
|
|
var cosignManifest ispec.Manifest
|
|
|
|
|
2023-09-05 11:48:56 -05:00
|
|
|
body, _, statusCode, err := ref.client.MakeGetRequest(ctx, &cosignManifest, ispec.MediaTypeImageManifest,
|
2023-05-31 12:26:23 -05:00
|
|
|
"v2", repo, "manifests", cosignTag)
|
|
|
|
if err != nil {
|
|
|
|
if statusCode == http.StatusNotFound {
|
|
|
|
ref.log.Debug().Str("errorType", common.TypeOf(err)).
|
|
|
|
Str("repository", repo).Str("tag", cosignTag).
|
|
|
|
Err(err).Msg("couldn't find any cosign manifest for image")
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
return nil, nil, zerr.ErrSyncReferrerNotFound
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
return nil, nil, err
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
2023-06-16 12:27:33 -05:00
|
|
|
return &cosignManifest, body, nil
|
2023-05-31 12:26:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func getCosignSignatureTagFromSubjectDigest(digestStr string) string {
|
|
|
|
return strings.Replace(digestStr, ":", "-", 1) + "." + remote.SignatureTagSuffix
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCosignSBOMTagFromSubjectDigest(digestStr string) string {
|
|
|
|
return strings.Replace(digestStr, ":", "-", 1) + "." + remote.SBOMTagSuffix
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCosignTagsFromSubjectDigest(digestStr string) []string {
|
|
|
|
var cosignTags []string
|
|
|
|
|
|
|
|
// signature tag
|
|
|
|
cosignTags = append(cosignTags, getCosignSignatureTagFromSubjectDigest(digestStr))
|
|
|
|
// sbom tag
|
|
|
|
cosignTags = append(cosignTags, getCosignSBOMTagFromSubjectDigest(digestStr))
|
|
|
|
|
|
|
|
return cosignTags
|
|
|
|
}
|
|
|
|
|
|
|
|
// this function will check if tag is a cosign tag (signature or sbom).
|
|
|
|
func IsCosignTag(tag string) bool {
|
|
|
|
if strings.HasPrefix(tag, "sha256-") &&
|
|
|
|
(strings.HasSuffix(tag, remote.SignatureTagSuffix) || strings.HasSuffix(tag, remote.SBOMTagSuffix)) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|