0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-01-06 22:40:28 -05:00
zot/pkg/extensions/search/cve/cve_test.go
Andrei Aaron ba6f347d8d
refactor(pkg/test): split logic in pkg/test/common.go into multiple packages (#1861)
Which could be imported independently. See more details:
1. "zotregistry.io/zot/pkg/test/common" - currently used as
   tcommon "zotregistry.io/zot/pkg/test/common" - inside pkg/test
   test "zotregistry.io/zot/pkg/test/common" - in tests
   . "zotregistry.io/zot/pkg/test/common" - in tests
Decouple zb from code in test/pkg in order to keep the size small.

2. "zotregistry.io/zot/pkg/test/image-utils" - curently used as
   . "zotregistry.io/zot/pkg/test/image-utils"

3. "zotregistry.io/zot/pkg/test/deprecated" -  curently used as
   "zotregistry.io/zot/pkg/test/deprecated"
This one will bre replaced gradually by image-utils in the future.

4. "zotregistry.io/zot/pkg/test/signature" - (cosign + notation) use as
   "zotregistry.io/zot/pkg/test/signature"

5. "zotregistry.io/zot/pkg/test/auth" - (bearer + oidc)  curently used as
   authutils "zotregistry.io/zot/pkg/test/auth"

 6. "zotregistry.io/zot/pkg/test/oci-utils" -  curently used as
   ociutils "zotregistry.io/zot/pkg/test/oci-utils"

Some unused functions were removed, some were replaced, and in
a few cases specific funtions were moved to the files they were used in.

Added an interface for the StoreController, this reduces the number of imports
of the entire image store, decreasing binary size for tests.
If the zb code was still coupled with pkg/test, this would have reflected in zb size.

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
2023-09-27 11:34:48 -07:00

1894 lines
79 KiB
Go

//go:build search
// +build search
//nolint:lll,gosimple
package cveinfo_test
import (
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"path"
"strings"
"testing"
"time"
regTypes "github.com/google/go-containerregistry/pkg/v1/types"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
apiErr "zotregistry.io/zot/pkg/api/errors"
zcommon "zotregistry.io/zot/pkg/common"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/monitoring"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
cvecache "zotregistry.io/zot/pkg/extensions/search/cve/cache"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta"
"zotregistry.io/zot/pkg/meta/boltdb"
mTypes "zotregistry.io/zot/pkg/meta/types"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/storage/local"
. "zotregistry.io/zot/pkg/test/common"
"zotregistry.io/zot/pkg/test/deprecated"
. "zotregistry.io/zot/pkg/test/image-utils"
"zotregistry.io/zot/pkg/test/mocks"
ociutils "zotregistry.io/zot/pkg/test/oci-utils"
)
const (
username = "test"
passphrase = "test"
)
type CveResult struct {
ImgList ImgList `json:"data"`
Errors []ErrorGQL `json:"errors"`
}
//nolint:tagliatelle // graphQL schema
type ImgListWithCVEFixed struct {
Images []ImageInfo `json:"ImageListWithCVEFixed"`
}
type ImageInfo struct {
RepoName string
LastUpdated time.Time
}
//nolint:tagliatelle // graphQL schema
type ImgList struct {
CVEResultForImage CVEResultForImage `json:"CVEListForImage"`
}
type ErrorGQL struct {
Message string `json:"message"`
Path []string `json:"path"`
}
//nolint:tagliatelle // graphQL schema
type CVEResultForImage struct {
Tag string `json:"Tag"`
CVEList []cvemodel.CVE `json:"CVEList"`
}
func testSetup(t *testing.T) (string, error) {
t.Helper()
dir := t.TempDir()
err := generateTestData(dir)
if err != nil {
return "", err
}
testStorageCtrl := ociutils.GetDefaultStoreController(dir, log.NewLogger("debug", ""))
err = WriteImageToFileSystem(CreateRandomVulnerableImage(), "zot-test", "0.0.1", testStorageCtrl)
if err != nil {
return "", err
}
err = WriteImageToFileSystem(CreateRandomVulnerableImage(), "zot-cve-test", "0.0.1", testStorageCtrl)
if err != nil {
return "", err
}
return dir, nil
}
func generateTestData(dbDir string) error { //nolint: gocyclo
// Image dir with no files
err := os.Mkdir(path.Join(dbDir, "zot-noindex-test"), 0o755)
if err != nil {
return err
}
err = os.Mkdir(path.Join(dbDir, "zot-nonreadable-test"), 0o755)
if err != nil {
return err
}
index := ispec.Index{}
index.SchemaVersion = 2
buf, err := json.Marshal(index)
if err != nil {
return err
}
if err = os.WriteFile(path.Join(dbDir, "zot-nonreadable-test", "index.json"), //nolint:gosec // test code
buf, 0o111); err != nil {
return err
}
// Image dir with invalid index.json
err = os.Mkdir(path.Join(dbDir, "zot-squashfs-invalid-index"), 0o755)
if err != nil {
return err
}
content := `{"schemaVersion": 2,"manifests"[{"mediaType": "application/vnd.oci.image.manifest.v1+json","digest": "sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size": 1240,"annotations": {"org.opencontainers.image.ref.name": "commit-aaa7c6e7-squashfs"},"platform": {"architecture": "amd64","os": "linux"}}]}`
err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-index", "index.json"), content)
if err != nil {
return err
}
// Image dir with no blobs
err = os.Mkdir(path.Join(dbDir, "zot-squashfs-noblobs"), 0o755)
if err != nil {
return err
}
content = `{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size":1240,"annotations":{"org.opencontainers.image.ref.name":"commit-aaa7c6e7-squashfs"},"platform":{"architecture":"amd64","os":"linux"}}]}`
err = makeTestFile(path.Join(dbDir, "zot-squashfs-noblobs", "index.json"), content)
if err != nil {
return err
}
// Image dir with invalid blob
err = os.MkdirAll(path.Join(dbDir, "zot-squashfs-invalid-blob", "blobs/sha256"), 0o755)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size":1240,"annotations":{"org.opencontainers.image.ref.name":"commit-aaa7c6e7-squashfs"},"platform":{"architecture":"amd64","os":"linux"}}]}
`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "index.json"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"config"{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:4b37d4133908ac9a3032ba996020f2ad41354a616b071ca7e726a1df18a0f354","size":1691},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:a01a66356aace53222e92fb6fd990b23eb44ab0e58dff6f853fa9f771ecf3ac5","size":54996992},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91c26d6934ef2b5c5c4d8458af9bfc4ca46cf90c22380193154964abc8298a7a","size":52330496},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:f281a550ca49746cfc6b8f1ac52f8086b3d5845db2ca18fde980dab62ae3bf7d","size":23343104},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:7ee02568717acdda336c9d56d4dc6ea7f3b1c553e43bb0c0ecc6fd3bbd059d1a","size":5910528},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:8fb33b130588b239235dedd560cdf49d29bbf6f2db5419ac68e4592a85c1f416","size":123269120},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:1b49f0b33d4a696bb94d84c9acab3623e2c195bfb446d446a583a2f9f27b04c3","size":113901568}],"annotations":{"com.cisco.stacker.git_version":"7-dev19-63-gaaa7c6e7","ws.tycho.stacker.git_version":"0.3.26"}}
`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "blobs/sha256", "2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929"), content)
if err != nil {
return err
}
// Create a squashfs image
err = os.MkdirAll(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256"), 0o755)
if err != nil {
return err
}
il := ispec.ImageLayout{Version: ispec.ImageLayoutVersion}
buf, err = json.Marshal(il)
if err != nil {
return err
}
if err = os.WriteFile(path.Join(dbDir, "zot-squashfs-test", "oci-layout"), buf, 0o644); err != nil { //nolint: gosec
return err
}
err = os.Mkdir(path.Join(dbDir, "zot-squashfs-test", ".uploads"), 0o755)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "index.json"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8","size":1738},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:3414b5ef0ad2f0390daaf55b63c422eeedef6191d47036a69d8ee396fabdce72","size":58993484},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:a3b04fff744c13dfa4883e01fa35e01af8daa7f72d9e9b6b7fad1f28843846b6","size":55631733},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:754f517f58f302190aa94e025c25890c18e1e811127aed1ef25c189278ec4ab0","size":113612795},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:ec004cd43488b803d6e232599e83a3164394d44fcd9f44755fed7b5791087ede","size":108889651}],"annotations":{"ws.tycho.stacker.git_version":"0.3.19"}}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-04-08T05:08:43.590117872Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0","size":1218},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:c40d72b1556293c00a3e4b6c64c46ef4c7ae4d876dc18bad942b7d1903e8e5b7","size":54745420},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:4115890e3e2563e545e03f264bfecb0097e24e02306ae3e7668dea52e00c6cc2","size":52213357},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91859e13e0cf704d5405199d73a2d1a0718391dbb183a77c4cb85d99e923ff57","size":109479329},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:20aef84d8098d47a0643a2f99ce05f0deed957b3a259fb708c538f23ed97cc82","size":103996238}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-05-11T19:30:02.467956112Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0"), content)
if err != nil {
return err
}
// Create a image with invalid layer blob
err = os.MkdirAll(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256"), 0o755)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "index.json"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`)
err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content)
if err != nil {
return err
}
// Create a image with no layer blob
err = os.MkdirAll(path.Join(dbDir, "zot-no-layer", "blobs/sha256"), 0o755)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
err = makeTestFile(path.Join(dbDir, "zot-no-layer", "index.json"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
if err != nil {
return err
}
content = fmt.Sprint(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`)
err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a"), content)
if err != nil {
return err
}
return nil
}
func makeTestFile(fileName, content string) error {
if err := os.WriteFile(fileName, []byte(content), 0o600); err != nil {
panic(err)
}
return nil
}
func TestImageFormat(t *testing.T) {
Convey("Test valid image", t, func() {
log := log.NewLogger("debug", "")
imgDir := "../../../../test/data"
dbDir := t.TempDir()
metrics := monitoring.NewMetricsServer(false, log)
defaultStore := local.NewImageStore(imgDir, false, false, log, metrics, nil, nil)
storeController := storage.StoreController{DefaultStore: defaultStore}
params := boltdb.DBParameters{
RootDir: dbDir,
}
boltDriver, err := boltdb.GetBoltDriver(params)
So(err, ShouldBeNil)
metaDB, err := boltdb.New(boltDriver, log)
So(err, ShouldBeNil)
err = meta.ParseStorage(metaDB, storeController, log)
So(err, ShouldBeNil)
scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
isValidImage, err := scanner.IsImageFormatScannable("zot-test", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-test", "0.0.1")
So(err, ShouldBeNil)
So(isValidImage, ShouldEqual, true)
isValidImage, err = scanner.IsImageFormatScannable("zot-test", "0.0.")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-noindex-test", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot--tet", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-noindex-test", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-noblobs", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-invalid-index", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-invalid-blob", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-test:0.3.22-squashfs", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
isValidImage, err = scanner.IsImageFormatScannable("zot-nonreadable-test", "")
So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false)
})
Convey("isIndexScanable", t, func() {
log := log.NewLogger("debug", "")
metaDB := &mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {
MediaType: ispec.MediaTypeImageIndex,
Digest: godigest.FromString("digest").String(),
},
},
}, nil
},
GetIndexDataFn: func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{IndexBlob: []byte(`{}`)}, nil
},
}
storeController := storage.StoreController{
DefaultStore: mocks.MockedImageStore{},
}
scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
isScanable, err := scanner.IsImageFormatScannable("repo", "tag")
So(err, ShouldBeNil)
So(isScanable, ShouldBeTrue)
})
}
func TestCVESearchDisabled(t *testing.T) {
Convey("Test with CVE search disabled", t, func() {
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
htpasswdPath := MakeHtpasswdFile()
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
dbDir := t.TempDir()
conf.Storage.RootDirectory = dbDir
defaultVal := true
searchConfig := &extconf.SearchConfig{
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
}
conf.Extensions = &extconf.ExtensionConfig{
Search: searchConfig,
}
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
if err != nil {
panic(err)
}
logPath := logFile.Name()
defer os.Remove(logPath)
writers := io.MultiWriter(os.Stdout, logFile)
ctlr := api.NewController(conf)
ctlr.Log.Logger = ctlr.Log.Output(writers)
ctrlManager := NewControllerManager(ctlr)
ctrlManager.StartAndWait(port)
// Wait for trivy db to download
found, err := ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second)
So(err, ShouldBeNil)
So(found, ShouldBeTrue)
defer ctrlManager.StopServer()
resp, _ := resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(string(resp.Body()), ShouldContainSubstring, "search: CVE search is disabled")
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"CVE-201-20482\"){Results{RepoName%20Tag}}}")
So(string(resp.Body()), ShouldContainSubstring, "search: CVE search is disabled")
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + "randomId" + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(string(resp.Body()), ShouldContainSubstring, "search: CVE search is disabled")
So(resp.StatusCode(), ShouldEqual, 200)
})
}
func TestCVESearch(t *testing.T) {
Convey("Test image vulnerability scanning", t, func() {
updateDuration, _ := time.ParseDuration("1h")
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
htpasswdPath := MakeHtpasswdFile()
defer os.Remove(htpasswdPath)
dbDir, err := testSetup(t)
So(err, ShouldBeNil)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
conf.Storage.RootDirectory = dbDir
trivyConfig := &extconf.TrivyConfig{
DBRepository: "ghcr.io/project-zot/trivy-db",
}
cveConfig := &extconf.CVEConfig{
UpdateInterval: updateDuration,
Trivy: trivyConfig,
}
defaultVal := true
searchConfig := &extconf.SearchConfig{
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
CVE: cveConfig,
}
conf.Extensions = &extconf.ExtensionConfig{
Search: searchConfig,
}
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
if err != nil {
panic(err)
}
logPath := logFile.Name()
defer os.Remove(logPath)
writers := io.MultiWriter(os.Stdout, logFile)
ctlr := api.NewController(conf)
ctlr.Log.Logger = ctlr.Log.Output(writers)
ctrlManager := NewControllerManager(ctlr)
ctrlManager.StartAndWait(port)
// trivy db download fail
err = os.Mkdir(dbDir+"/_trivy", 0o000)
So(err, ShouldBeNil)
found, err := ReadLogFileAndSearchString(logPath, "Error downloading Trivy DB to destination dir", 180*time.Second)
So(err, ShouldBeNil)
So(found, ShouldBeTrue)
err = os.Chmod(dbDir+"/_trivy", 0o755)
So(err, ShouldBeNil)
// Wait for trivy db to download
found, err = ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 180*time.Second)
So(err, ShouldBeNil)
So(found, ShouldBeTrue)
defer ctrlManager.StopServer()
// without creds, should get access error
resp, err := resty.R().Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 401)
var apiErr apiErr.Error
err = json.Unmarshal(resp.Body(), &apiErr)
So(err, ShouldBeNil)
resp, err = resty.R().Get(baseURL + constants.FullSearchPrefix)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 401)
err = json.Unmarshal(resp.Body(), &apiErr)
So(err, ShouldBeNil)
// with creds, should get expected status code
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 404)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
var cveResult CveResult
contains := false
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
err = json.Unmarshal(resp.Body(), &cveResult)
So(err, ShouldBeNil)
for _, err := range cveResult.Errors {
result := strings.Contains(err.Message, "no reference provided")
if result {
contains = result
}
}
So(contains, ShouldBeTrue)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), &cveResult)
So(err, ShouldBeNil)
So(len(cveResult.ImgList.CVEResultForImage.CVEList), ShouldNotBeZeroValue)
cveid := cveResult.ImgList.CVEResultForImage.CVEList[0].ID
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
var imgListWithCVEFixed ImgListWithCVEFixed
err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed)
So(err, ShouldBeNil)
So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-cve-test\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed)
So(err, ShouldBeNil)
So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"b/zot-squashfs-test:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
var cveSquashFSResult CveResult
err = json.Unmarshal(resp.Body(), &cveSquashFSResult)
So(err, ShouldBeNil)
So(len(cveSquashFSResult.ImgList.CVEResultForImage.CVEList), ShouldBeZeroValue)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-noindex:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-noindex\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-invalid-index:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-invalid-index\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-noblobs:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-noblob\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-test\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-invalid-blob:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-invalid-blob\"){Results{RepoName%20LastUpdated}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"cntos\"){Tag%20CVEList{Id%20Description%20Severity}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"CVE-201-20482\"){Results{RepoName%20Tag}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description%20Severity}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description%20Severity}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Severity}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
// Testing Invalid Search URL
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Ta%20CVEList{Id%20Description%20Severity}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(tet:\"CVE-2018-20482\"){Results{RepoName%20Tag}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageistForCVE(id:\"CVE-2018-20482\"){Results{RepoName%20Tag}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"CVE-2018-20482\"){ame%20Tags}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(reo:\"zot-test:1.0.0\"){Tag%20CVEList{Id%20Description%20Severity}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 422)
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"" + cveid + "\"){Results{RepoName%20Tag}}}")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
})
}
func TestCVEStruct(t *testing.T) { //nolint:gocyclo
Convey("Unit test the CVE struct", t, func() {
const repo1 = "repo1"
const repo2 = "repo2"
const repo3 = "repo3"
const repo4 = "repo4"
const repo5 = "repo5"
const repo6 = "repo6"
const repo7 = "repo7"
const repo100 = "repo100"
const repoMultiarch = "repoIndex"
params := boltdb.DBParameters{
RootDir: t.TempDir(),
}
boltDriver, err := boltdb.GetBoltDriver(params)
So(err, ShouldBeNil)
metaDB, err := boltdb.New(boltDriver, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
// Create metadb data for scannable image with vulnerabilities
image11 := CreateImageWith().DefaultLayers().
ImageConfig(ispec.Image{Created: DateRef(2008, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
repoMeta11 := mTypes.ManifestMetadata{
ManifestBlob: image11.ManifestDescriptor.Data,
ConfigBlob: image11.ConfigDescriptor.Data,
DownloadCount: 0,
Signatures: mTypes.ManifestSignatures{},
}
err = metaDB.SetManifestMeta(repo1, image11.ManifestDescriptor.Digest, repoMeta11)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo1, "0.1.0", image11.ManifestDescriptor.Digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
image12 := CreateImageWith().DefaultLayers().
ImageConfig(ispec.Image{Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
repoMeta12 := mTypes.ManifestMetadata{
ManifestBlob: image12.ManifestDescriptor.Data,
ConfigBlob: image12.ConfigDescriptor.Data,
DownloadCount: 0,
Signatures: mTypes.ManifestSignatures{},
}
err = metaDB.SetManifestMeta(repo1, image12.ManifestDescriptor.Digest, repoMeta12)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo1, "1.0.0", image12.ManifestDescriptor.Digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
image13 := CreateImageWith().DefaultLayers().
ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
repoMeta13 := mTypes.ManifestMetadata{
ManifestBlob: image13.ManifestDescriptor.Data,
ConfigBlob: image13.ConfigDescriptor.Data,
DownloadCount: 0,
Signatures: mTypes.ManifestSignatures{},
}
err = metaDB.SetManifestMeta(repo1, image13.ManifestDescriptor.Digest, repoMeta13)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo1, "1.1.0", image13.ManifestDescriptor.Digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
image14 := CreateImageWith().DefaultLayers().
ImageConfig(ispec.Image{Created: DateRef(2011, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
repoMeta14 := mTypes.ManifestMetadata{
ManifestBlob: image14.ManifestDescriptor.Data,
ConfigBlob: image14.ConfigDescriptor.Data,
}
err = metaDB.SetManifestMeta(repo1, image14.ManifestDescriptor.Digest, repoMeta14)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo1, "1.0.1", image14.ManifestDescriptor.Digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
// Create metadb data for scannable image with no vulnerabilities
image61 := CreateImageWith().DefaultLayers().
ImageConfig(ispec.Image{Created: DateRef(2016, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
repoMeta61 := mTypes.ManifestMetadata{
ManifestBlob: image61.ManifestDescriptor.Data,
ConfigBlob: image61.ConfigDescriptor.Data,
}
err = metaDB.SetManifestMeta(repo6, image61.ManifestDescriptor.Digest, repoMeta61)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo6, "1.0.0", image61.ManifestDescriptor.Digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
// Create metadb data for image not supporting scanning
image21 := CreateImageWith().Layers([]Layer{{
MediaType: ispec.MediaTypeImageLayerNonDistributableGzip, //nolint:staticcheck
Blob: []byte{10, 10, 10},
Digest: godigest.FromBytes([]byte{10, 10, 10}),
}}).ImageConfig(ispec.Image{Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
repoMeta21 := mTypes.ManifestMetadata{
ManifestBlob: image21.ManifestDescriptor.Data,
ConfigBlob: image21.ConfigDescriptor.Data,
}
err = metaDB.SetManifestMeta(repo2, image21.ManifestDescriptor.Digest, repoMeta21)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo2, "1.0.0", image21.ManifestDescriptor.Digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
// Create metadb data for invalid images/negative tests
manifestBlob31 := []byte("invalid manifest blob")
So(err, ShouldBeNil)
repoMeta31 := mTypes.ManifestMetadata{
ManifestBlob: manifestBlob31,
}
digest31 := godigest.FromBytes(manifestBlob31)
err = metaDB.SetManifestMeta(repo3, digest31, repoMeta31)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo3, "invalid-manifest", digest31, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
image41 := CreateImageWith().DefaultLayers().
CustomConfigBlob([]byte("invalid config blob"), ispec.MediaTypeImageConfig).Build()
repoMeta41 := mTypes.ManifestMetadata{
ManifestBlob: image41.ManifestDescriptor.Data,
ConfigBlob: image41.ConfigDescriptor.Data,
}
err = metaDB.SetManifestMeta(repo4, image41.ManifestDescriptor.Digest, repoMeta41)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo4, "invalid-config", image41.ManifestDescriptor.Digest,
ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
digest51 := godigest.FromString("abc8")
err = metaDB.SetRepoReference(repo5, "nonexitent-manifest", digest51, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
// Create metadb data for scannable image which errors during scan
image71 := CreateImageWith().DefaultLayers().
ImageConfig(ispec.Image{Created: DateRef(2000, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
repoMeta71 := mTypes.ManifestMetadata{
ManifestBlob: image71.ManifestDescriptor.Data,
ConfigBlob: image71.ConfigDescriptor.Data,
}
err = metaDB.SetManifestMeta(repo7, image71.ManifestDescriptor.Digest, repoMeta71)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(repo7, "1.0.0", image71.ManifestDescriptor.Digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
// create multiarch image with vulnerabilities
multiarchImage := CreateRandomMultiarch()
err = metaDB.SetIndexData(
multiarchImage.IndexDescriptor.Digest,
mTypes.IndexData{IndexBlob: multiarchImage.IndexDescriptor.Data},
)
So(err, ShouldBeNil)
err = metaDB.SetManifestData(
multiarchImage.Images[0].ManifestDescriptor.Digest,
mTypes.ManifestData{
ManifestBlob: multiarchImage.Images[0].ManifestDescriptor.Data,
ConfigBlob: multiarchImage.Images[0].ConfigDescriptor.Data,
},
)
So(err, ShouldBeNil)
err = metaDB.SetManifestData(
multiarchImage.Images[1].ManifestDescriptor.Digest,
mTypes.ManifestData{
ManifestBlob: multiarchImage.Images[1].ManifestDescriptor.Data,
ConfigBlob: multiarchImage.Images[1].ConfigDescriptor.Data,
},
)
So(err, ShouldBeNil)
err = metaDB.SetManifestData(
multiarchImage.Images[2].ManifestDescriptor.Digest,
mTypes.ManifestData{
ManifestBlob: multiarchImage.Images[2].ManifestDescriptor.Data,
ConfigBlob: multiarchImage.Images[2].ConfigDescriptor.Data,
},
)
So(err, ShouldBeNil)
err = metaDB.SetRepoReference(
repoMultiarch,
"tagIndex",
multiarchImage.IndexDescriptor.Digest,
ispec.MediaTypeImageIndex,
)
So(err, ShouldBeNil)
// Keep a record of all the image references / digest pairings
// This is normally done in MetaDB, but we want to verify
// the whole flow, including MetaDB
imageMap := map[string]string{}
image11Digest := image11.ManifestDescriptor.Digest.String()
image11Media := image11.ManifestDescriptor.MediaType
image11Name := repo1 + ":0.1.0"
imageMap[image11Name] = image11Digest
image12Digest := image12.ManifestDescriptor.Digest.String()
image12Media := image12.ManifestDescriptor.MediaType
image12Name := repo1 + ":1.0.0"
imageMap[image12Name] = image12Digest
image13Digest := image13.ManifestDescriptor.Digest.String()
image13Media := image13.ManifestDescriptor.MediaType
image13Name := repo1 + ":1.1.0"
imageMap[image13Name] = image13Digest
image14Digest := image14.ManifestDescriptor.Digest.String()
image14Media := image14.ManifestDescriptor.MediaType
image14Name := repo1 + ":1.0.1"
imageMap[image14Name] = image14Digest
image21Digest := image21.ManifestDescriptor.Digest.String()
image21Media := image21.ManifestDescriptor.MediaType
image21Name := repo2 + ":1.0.0"
imageMap[image21Name] = image21Digest
image31Digest := digest31.String()
image31Media := ispec.MediaTypeImageManifest
image31Name := repo3 + ":invalid-manifest"
imageMap[image31Name] = image31Digest
image41Digest := image41.ManifestDescriptor.Digest.String()
image41Media := image41.ManifestDescriptor.MediaType
image41Name := repo4 + ":invalid-config"
imageMap[image41Name] = image41Digest
image51Digest := digest51.String()
image51Media := ispec.MediaTypeImageManifest
image51Name := repo5 + ":nonexitent-manifest"
imageMap[image51Name] = digest51.String()
image61Digest := image61.ManifestDescriptor.Digest.String()
image61Media := image61.ManifestDescriptor.MediaType
image61Name := repo6 + ":1.0.0"
imageMap[image61Name] = image61Digest
image71Digest := image71.ManifestDescriptor.Digest.String()
image71Media := image71.ManifestDescriptor.MediaType
image71Name := repo7 + ":1.0.0"
imageMap[image71Name] = image71Digest
indexDigest := multiarchImage.IndexDescriptor.Digest.String()
indexMedia := multiarchImage.IndexDescriptor.MediaType
indexName := repoMultiarch + ":tagIndex"
imageMap[indexName] = indexDigest
indexM1Digest := multiarchImage.Images[0].ManifestDescriptor.Digest.String()
indexM1Name := "repoIndex@" + indexM1Digest
imageMap[indexM1Name] = indexM1Digest
indexM2Digest := multiarchImage.Images[1].ManifestDescriptor.Digest.String()
indexM2Name := "repoIndex@" + indexM2Digest
imageMap[indexM2Name] = indexM2Digest
indexM3Digest := multiarchImage.Images[2].ManifestDescriptor.Digest.String()
indexM3Name := "repoIndex@" + indexM3Digest
imageMap[indexM3Name] = indexM3Digest
log := log.NewLogger("debug", "")
// Initialize a test CVE cache
cache := cvecache.NewCveCache(100, log)
// MetaDB loaded with initial data, now mock the scanner
// Setup test CVE data in mock scanner
scanner := mocks.CveScannerMock{
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
result := cache.Get(image)
// Will not match sending the repo:tag as a parameter, but we don't care
if result != nil {
return result, nil
}
repo, ref, isTag := zcommon.GetImageDirAndReference(image)
if isTag {
foundRef, ok := imageMap[image]
if !ok {
return nil, ErrBadTest
}
ref = foundRef
}
defer func() {
t.Logf("ScanImageFn cached for image %s digest %s: %v", image, ref, cache.Get(ref))
}()
// Images in chronological order
if repo == repo1 && ref == image11Digest {
result := map[string]cvemodel.CVE{
"CVE1": {
ID: "CVE1",
Severity: "MEDIUM",
Title: "Title CVE1",
Description: "Description CVE1",
},
}
cache.Add(ref, result)
return result, nil
}
if repo == repo1 && zcommon.Contains([]string{image12Digest, image21Digest}, ref) {
result := map[string]cvemodel.CVE{
"CVE1": {
ID: "CVE1",
Severity: "MEDIUM",
Title: "Title CVE1",
Description: "Description CVE1",
},
"CVE2": {
ID: "CVE2",
Severity: "HIGH",
Title: "Title CVE2",
Description: "Description CVE2",
},
"CVE3": {
ID: "CVE3",
Severity: "LOW",
Title: "Title CVE3",
Description: "Description CVE3",
},
}
cache.Add(ref, result)
return result, nil
}
if repo == repo1 && ref == image13Digest {
result := map[string]cvemodel.CVE{
"CVE3": {
ID: "CVE3",
Severity: "LOW",
Title: "Title CVE3",
Description: "Description CVE3",
},
}
cache.Add(ref, result)
return result, nil
}
// As a minor release on 1.0.0 banch
// does not include all fixes published in 1.1.0
if repo == repo1 && ref == image14Digest {
result := map[string]cvemodel.CVE{
"CVE1": {
ID: "CVE1",
Severity: "MEDIUM",
Title: "Title CVE1",
Description: "Description CVE1",
},
"CVE3": {
ID: "CVE3",
Severity: "LOW",
Title: "Title CVE3",
Description: "Description CVE3",
},
}
cache.Add(ref, result)
return result, nil
}
// Unexpected error while scanning
if repo == repo7 {
return map[string]cvemodel.CVE{}, ErrFailedScan
}
if (repo == repoMultiarch && ref == indexDigest) ||
(repo == repoMultiarch && ref == indexM1Digest) {
result := map[string]cvemodel.CVE{
"CVE1": {
ID: "CVE1",
Severity: "MEDIUM",
Title: "Title CVE1",
Description: "Description CVE1",
},
}
// Simulate scanning an index results in scanning its manifests
if ref == indexDigest {
cache.Add(indexM1Digest, result)
cache.Add(indexM2Digest, map[string]cvemodel.CVE{})
cache.Add(indexM3Digest, map[string]cvemodel.CVE{})
}
cache.Add(ref, result)
return result, nil
}
// By default the image has no vulnerabilities
result = map[string]cvemodel.CVE{}
cache.Add(ref, result)
return result, nil
},
IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
if repo == repoMultiarch {
return true, nil
}
// Almost same logic compared to actual Trivy specific implementation
imageDir, inputTag := repo, reference
repoMeta, err := metaDB.GetRepoMeta(imageDir)
if err != nil {
return false, err
}
manifestDigestStr := reference
if zcommon.IsTag(reference) {
var ok bool
descriptor, ok := repoMeta.Tags[inputTag]
if !ok {
return false, zerr.ErrTagMetaNotFound
}
manifestDigestStr = descriptor.Digest
}
manifestDigest, err := godigest.Parse(manifestDigestStr)
if err != nil {
return false, err
}
manifestData, err := metaDB.GetManifestData(manifestDigest)
if err != nil {
return false, err
}
var manifestContent ispec.Manifest
err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent)
if err != nil {
return false, zerr.ErrScanNotSupported
}
for _, imageLayer := range manifestContent.Layers {
switch imageLayer.MediaType {
case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
return true, nil
default:
return false, zerr.ErrScanNotSupported
}
}
return false, nil
},
IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
if repo == repo2 && digest == image21Digest {
return false, zerr.ErrScanNotSupported
}
if repo == repo3 && digest == image31Digest {
return false, zerr.ErrTagMetaNotFound
}
if repo == repo5 && digest == image51Digest {
return false, zerr.ErrManifestDataNotFound
}
if repo == repo100 {
return false, zerr.ErrRepoMetaNotFound
}
return true, nil
},
IsResultCachedFn: func(digest string) bool {
t.Logf("IsResultCachedFn found in cache for digest %s: %v", digest, cache.Get(digest))
return cache.Contains(digest)
},
GetCachedResultFn: func(digest string) map[string]cvemodel.CVE {
t.Logf("GetCachedResultFn found in cache for digest %s: %v", digest, cache.Get(digest))
return cache.Get(digest)
},
}
cveInfo := cveinfo.BaseCveInfo{Log: log, Scanner: scanner, MetaDB: metaDB}
t.Log("\nTest GetCVEListForImage\n")
pageInput := cvemodel.PageInput{
SortBy: cveinfo.SeverityDsc,
}
// Image is found
cveList, pageInfo, err := cveInfo.GetCVEListForImage(repo1, "0.1.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE1")
So(pageInfo.ItemCount, ShouldEqual, 1)
So(pageInfo.TotalCount, ShouldEqual, 1)
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo1, "1.0.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 3)
So(cveList[0].ID, ShouldEqual, "CVE2")
So(cveList[1].ID, ShouldEqual, "CVE1")
So(cveList[2].ID, ShouldEqual, "CVE3")
So(pageInfo.ItemCount, ShouldEqual, 3)
So(pageInfo.TotalCount, ShouldEqual, 3)
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo1, "1.0.1", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 2)
So(cveList[0].ID, ShouldEqual, "CVE1")
So(cveList[1].ID, ShouldEqual, "CVE3")
So(pageInfo.ItemCount, ShouldEqual, 2)
So(pageInfo.TotalCount, ShouldEqual, 2)
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo1, "1.1.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE3")
So(pageInfo.ItemCount, ShouldEqual, 1)
So(pageInfo.TotalCount, ShouldEqual, 1)
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo6, "1.0.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Image is multiarch
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repoMultiarch, "tagIndex", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE1")
So(pageInfo.ItemCount, ShouldEqual, 1)
So(pageInfo.TotalCount, ShouldEqual, 1)
// Image is not scannable
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo2, "1.0.0", "", pageInput)
So(err, ShouldEqual, zerr.ErrScanNotSupported)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Tag is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo3, "1.0.0", "", pageInput)
So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Config not valid
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo4, "invalid-config", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Manifest is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo5, "nonexitent-manifest", "", pageInput)
So(err, ShouldEqual, zerr.ErrManifestDataNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Scan failed
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo7, "1.0.0", "", pageInput)
So(err, ShouldEqual, ErrFailedScan)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Repo is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo100, "1.0.0", "", pageInput)
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// By this point the cache should already be pupulated by previous function calls
t.Log("\nTest GetCVESummaryForImage\n")
// Image is found
cveSummary, err := cveInfo.GetCVESummaryForImageMedia(repo1, image11Digest, image11Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo1, image12Digest, image12Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 3)
So(cveSummary.MaxSeverity, ShouldEqual, "HIGH")
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo1, image14Digest, image14Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 2)
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo1, image13Digest, image13Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "LOW")
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo6, image61Digest, image61Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "NONE")
// Image is multiarch
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repoMultiarch, indexDigest, indexMedia)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
// Image is not scannable
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo2, image21Digest, image21Media)
So(err, ShouldEqual, zerr.ErrScanNotSupported)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")
// Tag is not found
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo3, image31Digest, image31Media)
So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")
// Config not valid
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo4, image41Digest, image41Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "NONE")
// Manifest is not found
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo5, image51Digest, image51Media)
So(err, ShouldEqual, zerr.ErrManifestDataNotFound)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")
// Scan failed
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo5, image71Digest, image71Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")
// Repo is not found
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo100,
godigest.FromString("missing_digest").String(), ispec.MediaTypeImageManifest)
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")
t.Log("\nTest GetImageListWithCVEFixed\n")
// Image is found
tagList, err := cveInfo.GetImageListWithCVEFixed(repo1, "CVE1")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 1)
So(tagList[0].Tag, ShouldEqual, "1.1.0")
tagList, err = cveInfo.GetImageListWithCVEFixed(repo1, "CVE2")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 2)
expectedTags := []string{"1.0.1", "1.1.0"}
So(expectedTags, ShouldContain, tagList[0].Tag)
So(expectedTags, ShouldContain, tagList[1].Tag)
tagList, err = cveInfo.GetImageListWithCVEFixed(repo1, "CVE3")
So(err, ShouldBeNil)
// CVE3 is not present in 0.1.0, but that is older than all other
// images where it is present. The rest of the images explicitly have it.
// This means we consider it not fixed in any image.
So(len(tagList), ShouldEqual, 0)
// Image doesn't have any CVEs in the first place
tagList, err = cveInfo.GetImageListWithCVEFixed(repo6, "CVE1")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 1)
So(tagList[0].Tag, ShouldEqual, "1.0.0")
// Image is not scannable
tagList, err = cveInfo.GetImageListWithCVEFixed(repo2, "CVE100")
// CVE is not considered fixed as scan is not possible
// but do not return an error
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
// Tag is not found, but we should not error
tagList, err = cveInfo.GetImageListWithCVEFixed(repo3, "CVE101")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
// Manifest is not found, we just consider exclude it from the fixed list
tagList, err = cveInfo.GetImageListWithCVEFixed(repo5, "CVE101")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
// Repo is not found, there could potentially be unaffected tags in the repo
// but we can't access their data
tagList, err = cveInfo.GetImageListWithCVEFixed(repo100, "CVE100")
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(len(tagList), ShouldEqual, 0)
t.Log("\nTest GetImageListForCVE\n")
// Image is found
tagList, err = cveInfo.GetImageListForCVE(repo1, "CVE1")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 3)
expectedTags = []string{"0.1.0", "1.0.0", "1.0.1"}
So(expectedTags, ShouldContain, tagList[0].Tag)
So(expectedTags, ShouldContain, tagList[1].Tag)
So(expectedTags, ShouldContain, tagList[2].Tag)
tagList, err = cveInfo.GetImageListForCVE(repo1, "CVE2")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 1)
So(tagList[0].Tag, ShouldEqual, "1.0.0")
tagList, err = cveInfo.GetImageListForCVE(repo1, "CVE3")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 3)
expectedTags = []string{"1.0.0", "1.0.1", "1.1.0"}
So(expectedTags, ShouldContain, tagList[0].Tag)
So(expectedTags, ShouldContain, tagList[1].Tag)
So(expectedTags, ShouldContain, tagList[2].Tag)
// Image/repo doesn't have the CVE at all
tagList, err = cveInfo.GetImageListForCVE(repo6, "CVE1")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
// Image is not scannable
tagList, err = cveInfo.GetImageListForCVE(repo2, "CVE100")
// Image is not considered affected with CVE as scan is not possible
// but do not return an error
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
// Tag is not found, but we should not error
tagList, err = cveInfo.GetImageListForCVE(repo3, "CVE101")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
// Repo is not found, assume it is affected by the CVE
// But we don't have enough of its data to actually return it
tagList, err = cveInfo.GetImageListForCVE(repo100, "CVE100")
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(len(tagList), ShouldEqual, 0)
t.Log("\nTest errors while scanning\n")
faultyScanner := mocks.CveScannerMock{
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
// Could be any type of error, let's reuse this one
return nil, zerr.ErrScanNotSupported
},
}
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: faultyScanner, MetaDB: metaDB}
cveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo1, image11Digest, image11Media)
So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")
cveList, pageInfo, err = cveInfo.GetCVEListForImage(repo1, "0.1.0", "", pageInput)
So(err, ShouldNotBeNil)
So(cveList, ShouldBeEmpty)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
tagList, err = cveInfo.GetImageListWithCVEFixed(repo1, "CVE1")
// CVE is not considered fixed as scan is not possible
// but do not return an error
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
tagList, err = cveInfo.GetImageListForCVE(repo1, "CVE1")
// Image is not considered affected with CVE as scan is not possible
// but do not return an error
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
return false, nil
},
}, MetaDB: metaDB}
_, err = cveInfo.GetImageListForCVE(repoMultiarch, "CVE1")
So(err, ShouldBeNil)
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
return true, nil
},
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
return nil, zerr.ErrTypeAssertionFailed
},
}, MetaDB: metaDB}
_, err = cveInfo.GetImageListForCVE(repoMultiarch, "CVE1")
So(err, ShouldBeNil)
})
}
func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
tags := make([]cvemodel.TagInfo, 0)
firstTag := cvemodel.TagInfo{
Tag: "1.0.0",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
secondTag := cvemodel.TagInfo{
Tag: "1.0.1",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
thirdTag := cvemodel.TagInfo{
Tag: "1.0.2",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
fourthTag := cvemodel.TagInfo{
Tag: "1.0.3",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
tags = append(tags, firstTag, secondTag, thirdTag, fourthTag)
vulnerableTags := make([]cvemodel.TagInfo, 0)
vulnerableTags = append(vulnerableTags, secondTag)
return tags, vulnerableTags
}
func TestFixedTags(t *testing.T) {
Convey("Test fixed tags", t, func() {
allTags, vulnerableTags := getTags()
fixedTags := cveinfo.GetFixedTags(allTags, vulnerableTags)
So(len(fixedTags), ShouldEqual, 2)
fixedTags = cveinfo.GetFixedTags(allTags, append(vulnerableTags, cvemodel.TagInfo{
Tag: "taginfo",
Descriptor: cvemodel.Descriptor{
MediaType: ispec.MediaTypeImageManifest,
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
},
Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
}))
So(len(fixedTags), ShouldEqual, 3)
})
}
func TestFixedTagsWithIndex(t *testing.T) {
Convey("Test fixed tags", t, func() {
tempDir := t.TempDir()
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
defaultVal := true
conf.Storage.RootDirectory = tempDir
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
CVE: &extconf.CVEConfig{
UpdateInterval: 24 * time.Hour,
Trivy: &extconf.TrivyConfig{
DBRepository: "ghcr.io/project-zot/trivy-db",
},
},
},
}
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
So(err, ShouldBeNil)
logPath := logFile.Name()
defer os.Remove(logPath)
writers := io.MultiWriter(os.Stdout, logFile)
ctlr := api.NewController(conf)
ctlr.Log.Logger = ctlr.Log.Output(writers)
cm := NewControllerManager(ctlr)
cm.StartAndWait(port)
defer cm.StopServer()
// push index with 2 manifests: one with vulns and one without
vulnManifestCreated := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC)
vulnManifest, err := deprecated.GetVulnImageWithConfig(ispec.Image{ //nolint:staticcheck
Created: &vulnManifestCreated,
Platform: ispec.Platform{OS: "linux", Architecture: "amd64"},
})
So(err, ShouldBeNil)
fixedManifestCreated := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC)
fixedManifest, err := deprecated.GetImageWithConfig(ispec.Image{ //nolint:staticcheck
Created: &fixedManifestCreated,
Platform: ispec.Platform{OS: "windows", Architecture: "amd64"},
})
So(err, ShouldBeNil)
fixedDigest := fixedManifest.Digest()
multiArch := deprecated.GetMultiarchImageForImages([]Image{fixedManifest, //nolint:staticcheck
vulnManifest})
err = UploadMultiarchImage(multiArch, baseURL, "repo", "multi-arch-tag")
So(err, ShouldBeNil)
// oldest vulnerability
simpleVulnCreated := time.Date(2005, 1, 1, 1, 1, 1, 1, time.UTC)
simpleVulnImg, err := deprecated.GetVulnImageWithConfig(ispec.Image{ //nolint:staticcheck
Created: &simpleVulnCreated,
Platform: ispec.Platform{OS: "windows", Architecture: "amd64"},
})
So(err, ShouldBeNil)
err = UploadImage(simpleVulnImg, baseURL, "repo", "vuln-img")
So(err, ShouldBeNil)
// Wait for trivy db to download
found, err := ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 180*time.Second)
So(err, ShouldBeNil)
So(found, ShouldBeTrue)
cveInfo := cveinfo.NewCVEInfo(ctlr.CveScanner, ctlr.MetaDB, ctlr.Log)
tagsInfo, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
So(err, ShouldBeNil)
So(len(tagsInfo), ShouldEqual, 1)
So(len(tagsInfo[0].Manifests), ShouldEqual, 1)
So(tagsInfo[0].Manifests[0].Digest, ShouldResemble, fixedDigest)
const query = `
{
ImageListWithCVEFixed(id:"%s",image:"%s"){
Results{
RepoName
Manifests {Digest}
}
}
}`
resp, _ := resty.R().Get(baseURL + constants.FullSearchPrefix + "?query=" +
url.QueryEscape(fmt.Sprintf(query, Vulnerability1ID, "repo")))
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
responseStruct := &zcommon.ImageListWithCVEFixedResponse{}
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
So(len(responseStruct.Results), ShouldEqual, 1)
So(len(responseStruct.Results[0].Manifests), ShouldEqual, 1)
fixedManifestResp := responseStruct.Results[0].Manifests[0]
So(fixedManifestResp.Digest, ShouldResemble, fixedDigest.String())
})
}
func TestImageListWithCVEFixedErrors(t *testing.T) {
indexDigest := godigest.FromString("index")
manifestDigest := "sha256:1111111111111111111111111111111111111111111111111111111111111111"
Convey("Errors", t, func() {
storeController := storage.StoreController{}
storeController.DefaultStore = mocks.MockedImageStore{}
metaDB := mocks.MetaDBMock{}
log := log.NewLogger("debug", "")
Convey("getIndexContent errors", func() {
metaDB.GetRepoMetaFn = func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {
Digest: indexDigest.String(),
MediaType: ispec.MediaTypeImageIndex,
},
},
}, nil
}
metaDB.GetIndexDataFn = func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{}, zerr.ErrIndexDataNotFount
}
scanner := cveinfo.NewScanner(storeController, metaDB, "", "", log)
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
So(err, ShouldBeNil)
})
Convey("getIndexContent bad indexDigest", func() {
metaDB.GetRepoMetaFn = func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {
Digest: "bad digest",
MediaType: ispec.MediaTypeImageIndex,
},
},
}, nil
}
metaDB.GetIndexDataFn = func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{}, zerr.ErrIndexDataNotFount
}
scanner := cveinfo.NewScanner(storeController, metaDB, "", "", log)
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
So(err, ShouldBeNil)
})
Convey("getIndexContent bad index content", func() {
metaDB.GetRepoMetaFn = func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {
Digest: indexDigest.String(),
MediaType: ispec.MediaTypeImageIndex,
},
},
}, nil
}
metaDB.GetIndexDataFn = func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{IndexBlob: []byte(`bad index`)}, nil
}
scanner := cveinfo.NewScanner(storeController, metaDB, "", "", log)
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
So(err, ShouldBeNil)
})
Convey("getTagInfoForManifest bad manifest digest", func() {
metaDB.GetRepoMetaFn = func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {
Digest: "bad digest",
MediaType: ispec.MediaTypeImageManifest,
},
},
}, nil
}
scanner := cveinfo.NewScanner(storeController, metaDB, "", "", log)
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
So(err, ShouldBeNil)
})
Convey("getTagInfoForManifest fails for index", func() {
metaDB.GetRepoMetaFn = func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {
Digest: indexDigest.String(),
MediaType: ispec.MediaTypeImageIndex,
},
},
}, nil
}
metaDB.GetIndexDataFn = func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{
IndexBlob: []byte(fmt.Sprintf(`{
"manifests": [
{
"digest": "%s",
"mediaType": "application/vnd.oci.image.manifest.v1+json"
}
]}`, manifestDigest)),
}, nil
}
metaDB.GetManifestDataFn = func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{}, zerr.ErrManifestDataNotFound
}
scanner := cveinfo.NewScanner(storeController, metaDB, "", "", log)
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
tagsInfo, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
So(err, ShouldBeNil)
So(tagsInfo, ShouldBeEmpty)
})
Convey("media type not supported", func() {
metaDB.GetRepoMetaFn = func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {
Digest: godigest.FromString("media type").String(),
MediaType: "bad media type",
},
},
}, nil
}
scanner := cveinfo.NewScanner(storeController, metaDB, "", "", log)
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
tagsInfo, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
So(err, ShouldBeNil)
So(tagsInfo, ShouldBeEmpty)
})
})
}
func TestGetCVESummaryForImageMediaErrors(t *testing.T) {
Convey("Errors", t, func() {
storeController := storage.StoreController{}
storeController.DefaultStore = mocks.MockedImageStore{}
metaDB := mocks.MetaDBMock{}
log := log.NewLogger("debug", "")
Convey("IsImageMediaScannable returns false", func() {
scanner := mocks.CveScannerMock{
IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
return false, zerr.ErrScanNotSupported
},
}
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
_, err := cveInfo.GetCVESummaryForImageMedia("repo", "digest", ispec.MediaTypeImageManifest)
So(err, ShouldNotBeNil)
})
})
}