Skip to content

Commit

Permalink
feat: add support for oci1.1 cosign signatures
Browse files Browse the repository at this point in the history
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
  • Loading branch information
Andreea-Lupu committed Oct 26, 2023
1 parent d2fbd27 commit a3d85b4
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 42 deletions.
1 change: 1 addition & 0 deletions pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
// 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"
)

func Contains[T comparable](elems []T, v T) bool {
Expand Down
16 changes: 8 additions & 8 deletions pkg/extensions/search/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1364,7 +1364,7 @@ func TestExpandedRepoInfo(t *testing.T) {
}
So(found, ShouldEqual, true)

err = signature.SignImageUsingCosign("zot-cve-test:0.0.1", port)
err = signature.SignImageUsingCosign("zot-cve-test:0.0.1", port, false)
So(err, ShouldBeNil)

resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
Expand Down Expand Up @@ -1436,7 +1436,7 @@ func TestExpandedRepoInfo(t *testing.T) {
}
So(found, ShouldEqual, true)

err = signature.SignImageUsingCosign("zot-test@"+testManifestDigest.String(), port)
err = signature.SignImageUsingCosign("zot-test@"+testManifestDigest.String(), port, false)
So(err, ShouldBeNil)

resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "/query?query=" + url.QueryEscape(query))
Expand Down Expand Up @@ -3772,7 +3772,7 @@ func TestGlobalSearchFiltering(t *testing.T) {
)
So(err, ShouldBeNil)

err = signature.SignImageUsingCosign("signed-repo:test", port)
err = signature.SignImageUsingCosign("signed-repo:test", port, false)
So(err, ShouldBeNil)

query := `{
Expand Down Expand Up @@ -4336,7 +4336,7 @@ func TestMetaDBWhenSigningImages(t *testing.T) {
`

Convey("Sign with cosign", func() {
err = signature.SignImageUsingCosign("repo1:1.0.1", port)
err = signature.SignImageUsingCosign("repo1:1.0.1", port, false)
So(err, ShouldBeNil)

resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage1))
Expand Down Expand Up @@ -4416,7 +4416,7 @@ func TestMetaDBWhenSigningImages(t *testing.T) {
},
}

err := signature.SignImageUsingCosign("repo1:1.0.1", port)
err := signature.SignImageUsingCosign("repo1:1.0.1", port, false)
So(err, ShouldNotBeNil)
})
})
Expand Down Expand Up @@ -4456,7 +4456,7 @@ func TestMetaDBWhenSigningImages(t *testing.T) {
})

Convey("Sign with cosign index", func() {
err = signature.SignImageUsingCosign("repo1:index", port)
err = signature.SignImageUsingCosign("repo1:index", port, false)
So(err, ShouldBeNil)

resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryIndex))
Expand Down Expand Up @@ -4634,7 +4634,7 @@ func RunMetaDBIndexTests(baseURL, port string) {
responseImage := responseImages[0]
So(len(responseImage.Manifests), ShouldEqual, 3)

err = signature.SignImageUsingCosign(fmt.Sprintf("repo@%s", indexDigest), port)
err = signature.SignImageUsingCosign(fmt.Sprintf("repo@%s", indexDigest), port, false)
So(err, ShouldBeNil)

resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
Expand Down Expand Up @@ -5363,7 +5363,7 @@ func TestMetaDBWhenDeletingImages(t *testing.T) {

Convey("Delete a cosign signature", func() {
repo := "repo1"
err := signature.SignImageUsingCosign("repo1:1.0.1", port)
err := signature.SignImageUsingCosign("repo1:1.0.1", port, false)
So(err, ShouldBeNil)

query := `
Expand Down
2 changes: 1 addition & 1 deletion pkg/extensions/sync/references/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (ref OciReferences) IsSigned(ctx context.Context, remoteRepo, subjectDigest
return false
}

if len(getNotationManifestsFromOCIRefs(index)) > 0 {
if len(getNotationManifestsFromOCIRefs(index)) > 0 || len(getCosignManifestsFromOCIRefs(index)) > 0 {
return true
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/extensions/sync/references/references.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,18 @@ func getNotationManifestsFromOCIRefs(ociRefs ispec.Index) []ispec.Descriptor {
return notaryManifests
}

func getCosignManifestsFromOCIRefs(ociRefs ispec.Index) []ispec.Descriptor {
cosignManifests := []ispec.Descriptor{}

for _, ref := range ociRefs.Manifests {
if ref.ArtifactType == common.ArtifactTypeCosign {
cosignManifests = append(cosignManifests, ref)
}
}

return cosignManifests
}

func addSigToMeta(
metaDB mTypes.MetaDB, repo, sigType, tag string, signedManifestDig, referenceDigest godigest.Digest,
referenceBuf []byte, imageStore storageTypes.ImageStore, log log.Logger,
Expand Down
103 changes: 100 additions & 3 deletions pkg/extensions/sync/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ func TestOnDemand(t *testing.T) {
So(err, ShouldBeNil)

// sign using cosign
err = signature.SignImageUsingCosign(fmt.Sprintf("remote-repo@%s", manifestDigest.String()), port)
err = signature.SignImageUsingCosign(fmt.Sprintf("remote-repo@%s", manifestDigest.String()), port, false)
So(err, ShouldBeNil)

// add cosign sbom
Expand Down Expand Up @@ -4595,6 +4595,100 @@ func TestSignatures(t *testing.T) {
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
})

Convey("Verify sync oci1.1 cosign signatures", t, func() {
updateDuration, _ := time.ParseDuration("30m")

sctlr, srcBaseURL, _, _, _ := makeUpstreamServer(t, false, false)

scm := test.NewControllerManager(sctlr)
scm.StartAndWait(sctlr.Config.HTTP.Port)
defer scm.StopServer()

// create repo, push and sign it
repoName := testSignedImage
var digest godigest.Digest
So(func() { digest = pushRepo(srcBaseURL, repoName) }, ShouldNotPanic)

splittedURL := strings.SplitAfter(srcBaseURL, ":")
srcPort := splittedURL[len(splittedURL)-1]
t.Logf(srcPort)

err := signature.SignImageUsingCosign(fmt.Sprintf("%s@%s", repoName, digest.String()), srcPort, true)
So(err, ShouldBeNil)

regex := ".*"
var semver bool
var tlsVerify bool
onlySigned := true

syncRegistryConfig := syncconf.RegistryConfig{
Content: []syncconf.Content{
{
Prefix: "**",
Tags: &syncconf.Tags{
Regex: &regex,
Semver: &semver,
},
},
},
URLs: []string{srcBaseURL},
PollInterval: updateDuration,
TLSVerify: &tlsVerify,
CertDir: "",
OnlySigned: &onlySigned,
OnDemand: true,
}

defaultVal := true
syncConfig := &syncconf.Config{
Enable: &defaultVal,
Registries: []syncconf.RegistryConfig{syncRegistryConfig},
}

dctlr, destBaseURL, _, destClient := makeDownstreamServer(t, false, syncConfig)

dcm := test.NewControllerManager(dctlr)
dcm.StartAndWait(dctlr.Config.HTTP.Port)
defer dcm.StopServer()

// wait for sync
var destTagsList TagsList

for {
resp, err := destClient.R().Get(destBaseURL + "/v2/" + repoName + "/tags/list")
if err != nil {
panic(err)
}

err = json.Unmarshal(resp.Body(), &destTagsList)
if err != nil {
panic(err)
}

if len(destTagsList.Tags) > 0 {
break
}

time.Sleep(500 * time.Millisecond)
}

time.Sleep(1 * time.Second)

// get oci references from downstream, should be synced
getOCIReferrersURL := destBaseURL + path.Join("/v2", repoName, "referrers", digest.String())
resp, err := resty.R().Get(getOCIReferrersURL)
So(err, ShouldBeNil)
So(resp, ShouldNotBeEmpty)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)

var index ispec.Index

err = json.Unmarshal(resp.Body(), &index)
So(err, ShouldBeNil)

So(len(index.Manifests), ShouldEqual, 3)
})
}

func getPortFromBaseURL(baseURL string) string {
Expand Down Expand Up @@ -4628,7 +4722,10 @@ func TestSyncedSignaturesMetaDB(t *testing.T) {
err = signature.SignImageUsingNotary(repoName+":"+tag, srcPort, true)
So(err, ShouldBeNil)

err = signature.SignImageUsingCosign(repoName+":"+tag, srcPort)
err = signature.SignImageUsingCosign(repoName+":"+tag, srcPort, true)
So(err, ShouldBeNil)

err = signature.SignImageUsingCosign(repoName+":"+tag, srcPort, false)
So(err, ShouldBeNil)

// Create destination registry
Expand Down Expand Up @@ -4678,7 +4775,7 @@ func TestSyncedSignaturesMetaDB(t *testing.T) {

imageSignatures := repoMeta.Signatures[signedImage.DigestStr()]
So(imageSignatures, ShouldContainKey, zcommon.CosignSignature)
So(len(imageSignatures[zcommon.CosignSignature]), ShouldEqual, 1)
So(len(imageSignatures[zcommon.CosignSignature]), ShouldEqual, 2)
So(imageSignatures, ShouldContainKey, zcommon.NotationSignature)
So(len(imageSignatures[zcommon.NotationSignature]), ShouldEqual, 1)
})
Expand Down
15 changes: 4 additions & 11 deletions pkg/meta/boltdb/boltdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -952,17 +952,10 @@ func (bdw *BoltDB) AddManifestSignature(repo string, signedManifestDigest godige

signatureSlice := manifestSignatures[sygMeta.SignatureType]
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
if sygMeta.SignatureType == zcommon.NotationSignature {
signatureSlice = append(signatureSlice, mTypes.SignatureInfo{
SignatureManifestDigest: sygMeta.SignatureDigest,
LayersInfo: sygMeta.LayersInfo,
})
} else if sygMeta.SignatureType == zcommon.CosignSignature {
signatureSlice = []mTypes.SignatureInfo{{
SignatureManifestDigest: sygMeta.SignatureDigest,
LayersInfo: sygMeta.LayersInfo,
}}
}
signatureSlice = append(signatureSlice, mTypes.SignatureInfo{
SignatureManifestDigest: sygMeta.SignatureDigest,
LayersInfo: sygMeta.LayersInfo,
})
}

manifestSignatures[sygMeta.SignatureType] = signatureSlice
Expand Down
4 changes: 2 additions & 2 deletions pkg/meta/boltdb/boltdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,9 @@ func TestWrapperErrors(t *testing.T) {
repoData, err := boltdbWrapper.GetRepoMeta("repo1")
So(err, ShouldBeNil)
So(len(repoData.Signatures[string(digest.FromString("dig"))][zcommon.CosignSignature]),
ShouldEqual, 1)
ShouldEqual, 2)
So(repoData.Signatures[string(digest.FromString("dig"))][zcommon.CosignSignature][0].SignatureManifestDigest,
ShouldEqual, "digest2")
ShouldEqual, "digest1")

err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
mTypes.SignatureMetadata{
Expand Down
15 changes: 4 additions & 11 deletions pkg/meta/dynamodb/dynamodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -818,17 +818,10 @@ func (dwr *DynamoDB) AddManifestSignature(repo string, signedManifestDigest godi

signatureSlice := manifestSignatures[sygMeta.SignatureType]
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
if sygMeta.SignatureType == zcommon.NotationSignature {
signatureSlice = append(signatureSlice, mTypes.SignatureInfo{
SignatureManifestDigest: sygMeta.SignatureDigest,
LayersInfo: sygMeta.LayersInfo,
})
} else if sygMeta.SignatureType == zcommon.CosignSignature {
signatureSlice = []mTypes.SignatureInfo{{
SignatureManifestDigest: sygMeta.SignatureDigest,
LayersInfo: sygMeta.LayersInfo,
}}
}
signatureSlice = append(signatureSlice, mTypes.SignatureInfo{
SignatureManifestDigest: sygMeta.SignatureDigest,
LayersInfo: sygMeta.LayersInfo,
})
}

manifestSignatures[sygMeta.SignatureType] = signatureSlice
Expand Down
5 changes: 5 additions & 0 deletions pkg/storage/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,11 @@ func IsSignature(descriptor ispec.Descriptor) bool {
return true
}

// is cosign signature (OCI 1.1 support)
if descriptor.ArtifactType == zcommon.ArtifactTypeCosign {
return true
}

// is notation signature
if descriptor.ArtifactType == zcommon.ArtifactTypeNotation {
return true
Expand Down
5 changes: 5 additions & 0 deletions pkg/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ func CheckIsImageSignature(repoName string, manifestBlob []byte, reference strin
return true, NotationType, manifestContent.Subject.Digest, nil
}

// check cosign signature (OCI 1.1 support)
if manifestArtifactType == zcommon.ArtifactTypeCosign && manifestContent.Subject != nil {
return true, CosignType, manifestContent.Subject.Digest, nil
}

// check cosign
cosignTagRule := regexp.MustCompile(`sha256\-.+\.sig`)

Expand Down
20 changes: 14 additions & 6 deletions pkg/test/signature/cosign.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func GetCosignSignatureTagForDigest(manifestDigest godigest.Digest) string {
return manifestDigest.Algorithm().String() + "-" + manifestDigest.Encoded() + ".sig"
}

func SignImageUsingCosign(repoTag, port string) error {
func SignImageUsingCosign(repoTag, port string, oci11 bool) error {
cwd, err := os.Getwd()
if err != nil {
return err
Expand Down Expand Up @@ -59,13 +59,21 @@ func SignImageUsingCosign(repoTag, port string) error {

const timeoutPeriod = 5

signOpts := options.SignOptions{
Registry: options.RegistryOptions{AllowInsecure: true},
AnnotationOptions: options.AnnotationOptions{Annotations: []string{"tag=1.0"}},
Upload: true,
}

if oci11 {
signOpts.RegistryExperimental = options.RegistryExperimentalOptions{
RegistryReferrersMode: options.RegistryReferrersModeOCI11,
}
}

// sign the image
return sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: timeoutPeriod * time.Minute},
options.KeyOpts{KeyRef: path.Join(tdir, "cosign.key"), PassFunc: generate.GetPass},
options.SignOptions{
Registry: options.RegistryOptions{AllowInsecure: true},
AnnotationOptions: options.AnnotationOptions{Annotations: []string{"tag=1.0"}},
Upload: true,
},
signOpts,
[]string{imageURL})
}
22 changes: 22 additions & 0 deletions test/blackbox/annotations.bats
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,28 @@ function teardown_file() {
[[ "$sigName" == *"${digest}"* ]]
}

@test "sign/verify with cosign ( COSIGN_OCI_EXPERIMENTAL=1 COSIGN_EXPERIMENTAL=1 and --registry-referrers-mode=oci-1-1 )" {
run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Manifests {Digest ConfigDigest Size Layers { Size Digest }} Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"annotations"' ]
local digest=$(echo "${lines[-1]}" | jq -r '.data.ImageList.Results[0].Manifests[0].Digest')

export COSIGN_OCI_EXPERIMENTAL=1
export COSIGN_EXPERIMENTAL=1
run cosign initialize
[ "$status" -eq 0 ]
run cosign generate-key-pair --output-key-prefix "${BATS_FILE_TMPDIR}/cosign-sign-test-experimental"
[ "$status" -eq 0 ]
run cosign sign --registry-referrers-mode=oci-1-1 --key ${BATS_FILE_TMPDIR}/cosign-sign-test-experimental.key localhost:8080/annotations:latest --yes
[ "$status" -eq 0 ]
run cosign verify --key ${BATS_FILE_TMPDIR}/cosign-sign-test-experimental.pub localhost:8080/annotations:latest
[ "$status" -eq 0 ]
local sigName=$(echo "${lines[-1]}" | jq '.[].critical.image."docker-manifest-digest"')
[[ "$sigName" == *"${digest}"* ]]
unset COSIGN_OCI_EXPERIMENTAL
unset COSIGN_EXPERIMENTAL
}

@test "sign/verify with notation" {
run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Manifests {Digest ConfigDigest Size Layers { Size Digest }} Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search
[ "$status" -eq 0 ]
Expand Down

0 comments on commit a3d85b4

Please sign in to comment.