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:
parent
c9b32c73ae
commit
b61aff62cd
2 changed files with 192 additions and 30 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue