0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-03-18 02:22:53 -05:00

check notary v2 signature while looking for available signatures

expanded repo info also provides information if manifests of repo is signed or not
previously it was looking for only cosign signature.

Signed-off-by: Shivam Mishra <shimish2@cisco.com>
This commit is contained in:
Shivam Mishra 2022-02-20 17:32:44 -08:00 committed by Ramkumar Chinchani
parent c9b32c73ae
commit b61aff62cd
2 changed files with 192 additions and 30 deletions

View file

@ -6,13 +6,19 @@ package common_test
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"testing"
"time"
"github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sigstore/cosign/cmd/cosign/cli/generate"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/cmd/cosign/cli/sign"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1"
"zotregistry.io/zot/pkg/api"
@ -71,7 +77,7 @@ type ImageInfo struct {
Labels string
}
func testSetup(t *testing.T) error {
func testSetup(t *testing.T, subpath string) error {
t.Helper()
dir := t.TempDir()
@ -79,19 +85,89 @@ func testSetup(t *testing.T) error {
rootDir = dir
subRootDir = subDir
subRootDir = path.Join(subDir, subpath)
err := CopyFiles("../../../../test/data", rootDir)
if err != nil {
return err
}
err = CopyFiles("../../../../test/data", subDir)
return CopyFiles("../../../../test/data", subRootDir)
}
func signUsingCosign(port string) error {
cwd, err := os.Getwd()
So(err, ShouldBeNil)
defer func() { _ = os.Chdir(cwd) }()
tdir, err := ioutil.TempDir("", "cosign")
if err != nil {
return err
}
return nil
defer os.RemoveAll(tdir)
_ = os.Chdir(tdir)
// generate a keypair
os.Setenv("COSIGN_PASSWORD", "")
err = generate.GenerateKeyPairCmd(context.TODO(), "", nil)
if err != nil {
return err
}
imageURL := fmt.Sprintf("localhost:%s/%s@%s", port, "zot-cve-test",
"sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29")
// sign the image
return sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: 1 * time.Minute},
options.KeyOpts{KeyRef: path.Join(tdir, "cosign.key"), PassFunc: generate.GetPass},
options.RegistryOptions{AllowInsecure: true},
map[string]interface{}{"tag": "1.0"},
[]string{imageURL},
"", "", true, "", "", "", false, false, "")
}
func signUsingNotary(port string) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
defer func() { _ = os.Chdir(cwd) }()
tdir, err := ioutil.TempDir("", "notation")
if err != nil {
return err
}
defer os.RemoveAll(tdir)
_ = os.Chdir(tdir)
_, err = exec.LookPath("notation")
if err != nil {
return err
}
os.Setenv("XDG_CONFIG_HOME", tdir)
// generate a keypair
cmd := exec.Command("notation", "cert", "generate-test", "--trust", "notation-sign-test")
err = cmd.Run()
if err != nil {
return err
}
// sign the image
image := fmt.Sprintf("localhost:%s/%s:%s", port, "zot-cve-test", "0.0.1")
cmd = exec.Command("notation", "sign", "--key", "notation-sign-test", "--plain-http", image)
return cmd.Run()
}
func getTags() ([]common.TagInfo, []common.TagInfo) {
@ -184,7 +260,8 @@ func TestImageFormat(t *testing.T) {
func TestLatestTagSearchHTTP(t *testing.T) {
Convey("Test latest image search by timestamp", t, func() {
err := testSetup(t)
subpath := "/a"
err := testSetup(t, subpath)
if err != nil {
panic(err)
}
@ -194,7 +271,7 @@ func TestLatestTagSearchHTTP(t *testing.T) {
conf.HTTP.Port = port
conf.Storage.RootDirectory = rootDir
conf.Storage.SubPaths = make(map[string]config.StorageConfig)
conf.Storage.SubPaths["/a"] = config.StorageConfig{RootDirectory: subRootDir}
conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{Enable: &defaultVal},
@ -323,7 +400,8 @@ func TestLatestTagSearchHTTP(t *testing.T) {
func TestExpandedRepoInfo(t *testing.T) {
Convey("Test expanded repo info", t, func() {
err := testSetup(t)
subpath := "/a"
err := testSetup(t, subpath)
if err != nil {
panic(err)
}
@ -333,7 +411,7 @@ func TestExpandedRepoInfo(t *testing.T) {
conf.HTTP.Port = port
conf.Storage.RootDirectory = rootDir
conf.Storage.SubPaths = make(map[string]config.StorageConfig)
conf.Storage.SubPaths["/a"] = config.StorageConfig{RootDirectory: subRootDir}
conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir}
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{Enable: &defaultVal},
@ -375,7 +453,7 @@ func TestExpandedRepoInfo(t *testing.T) {
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
query := "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}"
query := "{ExpandedRepoInfo(repo:\"zot-cve-test\"){Manifests%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
So(resp, ShouldNotBeNil)
@ -388,15 +466,15 @@ func TestExpandedRepoInfo(t *testing.T) {
So(err, ShouldBeNil)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" {
So(m.IsSigned, ShouldEqual, false)
}
}
query = "{ExpandedRepoInfo(repo:\"\"){Manifests%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
So(resp, ShouldNotBeNil)
err = signUsingCosign(port)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
query = "{ExpandedRepoInfo(repo:\"a/zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20%Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
@ -406,16 +484,58 @@ func TestExpandedRepoInfo(t *testing.T) {
So(err, ShouldBeNil)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" {
So(m.IsSigned, ShouldEqual, true)
}
}
query = "{ExpandedRepoInfo(repo:\"\"){Manifests%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
query = "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20%Layers%20{Size%20Digest}}}}"
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" {
So(m.IsSigned, ShouldEqual, false)
}
}
err = signUsingNotary(port)
So(err, ShouldBeNil)
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "/query?query=" + query)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" {
So(m.IsSigned, ShouldEqual, true)
}
}
var manifestDigest digest.Digest
manifestDigest, _, _ = GetOciLayoutDigests("../../../../test/data/zot-test")
err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", manifestDigest.Encoded()))
if err != nil {
panic(err)
}
query = "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20%Layers%20{Size%20Digest}}}}"
So(err, ShouldBeNil)
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
So(resp, ShouldNotBeNil)

View file

@ -4,6 +4,7 @@ package common
import (
"encoding/json"
goerrors "errors"
"fmt"
"path"
"strconv"
"strings"
@ -18,8 +19,6 @@ import (
"zotregistry.io/zot/pkg/storage"
)
const cosignedAnnotation = "dev.cosign.signature.baseimage"
// OciLayoutInfo ...
type OciLayoutUtils struct {
Log log.Logger
@ -202,6 +201,51 @@ func (olu OciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, err
return tagsInfo, nil
}
// check notary signature corresponding to repo name, manifest digest and mediatype.
func (olu OciLayoutUtils) checkNotarySignature(name, digest, mediaType string) bool {
imageStore := olu.StoreController.GetImageStore(name)
_, err := imageStore.GetReferrers(name, digest, mediaType)
if err != nil {
olu.Log.Info().Str("repo", name).Str("digest",
digest).Str("mediatype", mediaType).Msg("invalid notary signature")
return false
}
return true
}
// check cosign signature corresponding to manifest.
func (olu OciLayoutUtils) checkCosignSignature(name, digest string) bool {
imageStore := olu.StoreController.GetImageStore(name)
// if manifest is signed using cosign mechanism, cosign adds a new manifest.
// new manifest is tagged as sha256-<manifest-digest>.sig.
reference := fmt.Sprintf("sha256-%s.sig", digest)
_, _, _, err := imageStore.GetImageManifest(name, reference) // nolint: dogsled
if err != nil {
olu.Log.Info().Str("repo", name).Str("digest",
digest).Msg("invalid cosign signature")
return false
}
return true
}
// checks if manifest is signed or not
// checks for notary or cosign signature
// if cosign signature found it does not looks for notary signature.
func (olu OciLayoutUtils) checkManifestSignature(name, digest, mediaType string) bool {
if !olu.checkCosignSignature(name, digest) {
return olu.checkNotarySignature(name, digest, mediaType)
}
return true
}
func (olu OciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
repo := RepoInfo{}
@ -214,27 +258,29 @@ func (olu OciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
return RepoInfo{}, err
}
for _, manifest := range manifestList {
for _, man := range manifestList {
manifestInfo := Manifest{}
manifestInfo.Digest = manifest.Digest.Encoded()
manifestInfo.Digest = man.Digest.Encoded()
manifestInfo.IsSigned = false
tag, ok := manifest.Annotations[ispec.AnnotationRefName]
tag, ok := man.Annotations[ispec.AnnotationRefName]
if !ok {
tag = "latest"
}
manifestInfo.Tag = tag
manifest, err := olu.GetImageBlobManifest(name, manifest.Digest)
manifest, err := olu.GetImageBlobManifest(name, man.Digest)
if err != nil {
olu.Log.Error().Err(err).Msg("error getting image manifest blob")
return RepoInfo{}, err
}
manifestInfo.IsSigned = olu.checkManifestSignature(name, man.Digest.Encoded(), man.MediaType)
layers := make([]Layer, 0)
for _, layer := range manifest.Layers {
@ -245,10 +291,6 @@ func (olu OciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
layerInfo.Size = strconv.FormatInt(layer.Size, 10)
layers = append(layers, layerInfo)
if _, ok := layer.Annotations[cosignedAnnotation]; ok {
manifestInfo.IsSigned = true
}
}
manifestInfo.Layers = layers