feat(cosign): add support for cosign bundle (#4023)

Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
This commit is contained in:
Ramkumar Chinchani
2026-05-01 00:21:06 -07:00
committed by GitHub
parent 993a17f5d0
commit 0b2eaa0f9a
15 changed files with 135 additions and 47 deletions
+14 -13
View File
@@ -546,23 +546,24 @@ func isCosignSigned(ctx context.Context, repo, digestStr string, searchConf Sear
return true
}
var referrers ispec.Index
for _, artifactType := range []string{common.ArtifactTypeCosign, common.ArtifactTypeCosignBundle} {
var referrers ispec.Index
artifactType := url.QueryEscape(common.ArtifactTypeCosign)
URL = fmt.Sprintf("%s/v2/%s/referrers/%s?artifactType=%s",
searchConf.ServURL, repo, digestStr, artifactType)
URL = fmt.Sprintf("%s/v2/%s/referrers/%s?artifactType=%s",
searchConf.ServURL, repo, digestStr, url.QueryEscape(artifactType))
_, err = httpClient.makeGETRequest(ctx, URL, username, password, searchConf.VerifyTLS,
searchConf.Debug, &referrers, searchConf.ResultWriter)
if err != nil {
return false
_, err = httpClient.makeGETRequest(ctx, URL, username, password, searchConf.VerifyTLS,
searchConf.Debug, &referrers, searchConf.ResultWriter)
if err != nil {
continue
}
if len(referrers.Manifests) > 0 {
return true
}
}
if len(referrers.Manifests) == 0 {
return false
}
return true
return false
}
func (p *requestsPool) submitJob(job *httpJob) {
+9 -2
View File
@@ -30,8 +30,9 @@ const (
// ArtifactTypeNotation is the same value as github.com/notaryproject/notation-go/registry.ArtifactTypeNotation
// (assert by internal test).
// reason used: to reduce zot minimal binary size (otherwise adds oras.land/oras-go/v2 deps).
ArtifactTypeNotation = "application/vnd.cncf.notary.signature"
ArtifactTypeCosign = "application/vnd.dev.cosign.artifact.sig.v1+json"
ArtifactTypeNotation = "application/vnd.cncf.notary.signature"
ArtifactTypeCosign = "application/vnd.dev.cosign.artifact.sig.v1+json"
ArtifactTypeCosignBundle = "application/vnd.dev.sigstore.bundle.v0.3+json"
// CosignSignatureTagSuffix is the suffix used for cosign signature tags (e.g., "sha256-digest.sig").
// Using constant to avoid pulling in cosign dependency.
CosignSignatureTagSuffix = "sig"
@@ -53,6 +54,12 @@ func IsCosignTag(tag string) bool {
return IsCosignSignature(tag) || IsCosignSBOM(tag)
}
// IsArtifactTypeCosign returns true if the given artifact type corresponds to a cosign signature,
// covering both the legacy type and the newer sigstore bundle type.
func IsArtifactTypeCosign(artifactType string) bool {
return artifactType == ArtifactTypeCosign || artifactType == ArtifactTypeCosignBundle
}
// RemoveFrom removes matches of item in [].
func RemoveFrom(inputSlice []string, item string) []string {
var newSlice []string
+7
View File
@@ -61,6 +61,13 @@ func TestCommon(t *testing.T) {
So(common.ArtifactTypeNotation, ShouldEqual, notreg.ArtifactTypeNotation)
})
Convey("Test IsArtifactTypeCosign", t, func() {
So(common.IsArtifactTypeCosign(common.ArtifactTypeCosign), ShouldBeTrue)
So(common.IsArtifactTypeCosign(common.ArtifactTypeCosignBundle), ShouldBeTrue)
So(common.IsArtifactTypeCosign(common.ArtifactTypeNotation), ShouldBeFalse)
So(common.IsArtifactTypeCosign("application/example"), ShouldBeFalse)
})
Convey("Test GetLocalIPs", t, func() {
localIPs, err := common.GetLocalIPs()
So(err, ShouldBeNil)
+1 -1
View File
@@ -26,7 +26,7 @@ func hasSignatureReferrers(refs referrer.ReferrerList) bool {
return true
}
if desc.ArtifactType == common.ArtifactTypeCosign {
if common.IsArtifactTypeCosign(desc.ArtifactType) {
return true
}
}
+1 -1
View File
@@ -439,7 +439,7 @@ func isSignature(reference string, manifestContent ispec.Manifest) (bool, string
}
// check cosign signature
if manifestArtifactType == zcommon.ArtifactTypeCosign && manifestContent.Subject != nil {
if zcommon.IsArtifactTypeCosign(manifestArtifactType) && manifestContent.Subject != nil {
return true, CosignType, manifestContent.Subject.Digest
}
+29
View File
@@ -556,6 +556,35 @@ func RunParseStorageTests(rootDir string, metaDB mTypes.MetaDB, log log.Logger)
So(repos[0].Signatures, ShouldContainKey, missingImageDigest.String())
})
Convey("Detect cosign bundle signatures by artifact type and subject", func() {
imageStore := local.NewImageStore(rootDir, false, false,
log, monitoring.NewMetricsServer(false, log), nil, nil, nil, nil)
storeController := storage.StoreController{DefaultStore: imageStore}
signedImage := CreateRandomImage()
err := WriteImageToFileSystem(signedImage, repo, "signed", storeController)
So(err, ShouldBeNil)
bundleSig := CreateMockCosignBundleSignature(signedImage.DescriptorRef())
err = WriteImageToFileSystem(bundleSig, repo, "bundle-sig", storeController)
So(err, ShouldBeNil)
err = meta.ParseStorage(metaDB, storeController, log) //nolint: contextcheck
So(err, ShouldBeNil)
repos, err := metaDB.GetMultipleRepoMeta(ctx,
func(repoMeta mTypes.RepoMeta) bool { return true })
So(err, ShouldBeNil)
So(repos, ShouldNotBeEmpty)
repoMeta := repos[0]
subjectDigest := signedImage.DigestStr()
So(repoMeta.Signatures, ShouldContainKey, subjectDigest)
So(repoMeta.Signatures[subjectDigest], ShouldContainKey, zcommon.CosignSignature)
So(len(repoMeta.Signatures[subjectDigest][zcommon.CosignSignature]), ShouldBeGreaterThan, 0)
})
Convey("Check statistics after load", func() {
imageStore := local.NewImageStore(rootDir, false, false,
log, monitoring.NewMetricsServer(false, log), nil, nil, nil, nil)
+1 -1
View File
@@ -653,7 +653,7 @@ func IsSignature(descriptor ispec.Descriptor) bool {
}
// is cosign signature (OCI 1.1 support)
if descriptor.ArtifactType == zcommon.ArtifactTypeCosign {
if zcommon.IsArtifactTypeCosign(descriptor.ArtifactType) {
return true
}
+1 -1
View File
@@ -321,7 +321,7 @@ func (gc GarbageCollect) removeReferrer(repo string, index *ispec.Index, manifes
// check if its notation or cosign signature
if artifactType == zcommon.ArtifactTypeNotation {
signatureType = storage.NotationType
} else if artifactType == zcommon.ArtifactTypeCosign {
} else if zcommon.IsArtifactTypeCosign(artifactType) {
signatureType = storage.CosignType
}
+1 -1
View File
@@ -297,7 +297,7 @@ func CheckIsImageSignature(repoName string, manifestBlob []byte, reference strin
}
// check cosign signature (OCI 1.1 support)
if manifestArtifactType == zcommon.ArtifactTypeCosign && manifestContent.Subject != nil {
if zcommon.IsArtifactTypeCosign(manifestArtifactType) && manifestContent.Subject != nil {
return true, CosignType, manifestContent.Subject.Digest, nil
}
+5
View File
@@ -268,6 +268,11 @@ func CreateMockCosignSignature(subject *ispec.Descriptor) Image {
ArtifactType(common.ArtifactTypeCosign).Build()
}
func CreateMockCosignBundleSignature(subject *ispec.Descriptor) Image {
return CreateImageWith().EmptyLayer().EmptyConfig().Subject(subject).
ArtifactType(common.ArtifactTypeCosignBundle).Build()
}
type BaseImageBuilder struct {
layers []Layer
+3 -3
View File
@@ -266,12 +266,12 @@ func (olu BaseOciLayoutUtils) checkCosignSignature(name string, digest godigest.
return true
}
mediaType := common.ArtifactTypeCosign
mediaTypes := []string{common.ArtifactTypeCosign, common.ArtifactTypeCosignBundle}
referrers, err := imageStore.GetReferrers(name, digest, []string{mediaType})
referrers, err := imageStore.GetReferrers(name, digest, mediaTypes)
if err != nil {
olu.Log.Info().Err(err).Str("repository", name).Str("digest",
digest.String()).Str("mediatype", mediaType).Msg("invalid cosign signature")
digest.String()).Interface("mediatypes", mediaTypes).Msg("invalid cosign signature")
return false
}