mirror of
https://github.com/project-zot/zot.git
synced 2025-01-06 22:40:28 -05:00
ba6f347d8d
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>
670 lines
21 KiB
Go
670 lines
21 KiB
Go
//go:build search
|
|
// +build search
|
|
|
|
package cveinfo_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"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"
|
|
|
|
zerr "zotregistry.io/zot/errors"
|
|
"zotregistry.io/zot/pkg/api/config"
|
|
zcommon "zotregistry.io/zot/pkg/common"
|
|
"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/scheduler"
|
|
"zotregistry.io/zot/pkg/storage"
|
|
"zotregistry.io/zot/pkg/storage/local"
|
|
test "zotregistry.io/zot/pkg/test/common"
|
|
. "zotregistry.io/zot/pkg/test/image-utils"
|
|
"zotregistry.io/zot/pkg/test/mocks"
|
|
)
|
|
|
|
var (
|
|
ErrBadTest = errors.New("there is a bug in the test")
|
|
ErrFailedScan = errors.New("scan has failed intentionally")
|
|
)
|
|
|
|
func TestScanGeneratorWithMockedData(t *testing.T) { //nolint: gocyclo
|
|
Convey("Test CVE scanning task scheduler with diverse mocked data", t, func() {
|
|
repo1 := "repo1"
|
|
repoIndex := "repoIndex"
|
|
|
|
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
|
|
logPath := logFile.Name()
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
logger := log.NewLogger("debug", logPath)
|
|
writers := io.MultiWriter(os.Stdout, logFile)
|
|
logger.Logger = logger.Output(writers)
|
|
|
|
cfg := config.New()
|
|
cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3}
|
|
sch := scheduler.NewScheduler(cfg, logger)
|
|
|
|
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(
|
|
repoIndex,
|
|
"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()
|
|
image11Name := "repo1:0.1.0"
|
|
imageMap[image11Name] = image11Digest
|
|
image12Digest := image12.ManifestDescriptor.Digest.String()
|
|
image12Name := "repo1:1.0.0"
|
|
imageMap[image12Name] = image12Digest
|
|
image13Digest := image13.ManifestDescriptor.Digest.String()
|
|
image13Name := "repo1:1.1.0"
|
|
imageMap[image13Name] = image13Digest
|
|
image14Digest := image14.ManifestDescriptor.Digest.String()
|
|
image14Name := "repo1:1.0.1"
|
|
imageMap[image14Name] = image14Digest
|
|
image21Digest := image21.ManifestDescriptor.Digest.String()
|
|
image21Name := "repo2:1.0.0"
|
|
imageMap[image21Name] = image21Digest
|
|
image31Name := "repo3:invalid-manifest"
|
|
imageMap[image31Name] = digest31.String()
|
|
image41Digest := image41.ManifestDescriptor.Digest.String()
|
|
image41Name := "repo4:invalid-config"
|
|
imageMap[image41Name] = image41Digest
|
|
image51Name := "repo5:nonexitent-manifest"
|
|
imageMap[image51Name] = digest51.String()
|
|
image61Digest := image61.ManifestDescriptor.Digest.String()
|
|
image61Name := "repo6:1.0.0"
|
|
imageMap[image61Name] = image61Digest
|
|
image71Digest := image71.ManifestDescriptor.Digest.String()
|
|
image71Name := "repo7:1.0.0"
|
|
imageMap[image71Name] = image71Digest
|
|
indexDigest := multiarchImage.IndexDescriptor.Digest.String()
|
|
indexName := "repoIndex: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
|
|
|
|
// Initialize a test CVE cache
|
|
cache := cvecache.NewCveCache(10, logger)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 == repoIndex && ref == indexDigest) ||
|
|
(repo == repoIndex && 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 == repoIndex {
|
|
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" {
|
|
if digest == image21Digest {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
},
|
|
IsResultCachedFn: func(digest string) bool {
|
|
return cache.Contains(digest)
|
|
},
|
|
UpdateDBFn: func() error {
|
|
cache.Purge()
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// Purge scan, it should not be needed
|
|
So(scanner.UpdateDB(), ShouldBeNil)
|
|
|
|
// Verify none of the entries are cached to begin with
|
|
t.Log("verify cache is initially empty")
|
|
|
|
for image, digestStr := range imageMap {
|
|
t.Log("expecting " + image + " " + digestStr + " to be absent from cache")
|
|
So(scanner.IsResultCached(digestStr), ShouldBeFalse)
|
|
}
|
|
|
|
// Start the generator
|
|
generator := cveinfo.NewScanTaskGenerator(metaDB, scanner, logger)
|
|
|
|
sch.SubmitGenerator(generator, 10*time.Second, scheduler.MediumPriority)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
sch.RunScheduler(ctx)
|
|
|
|
defer cancel()
|
|
|
|
// Make sure the scanner generator has completed despite errors
|
|
found, err := test.ReadLogFileAndSearchString(logPath,
|
|
"Scheduled CVE scan: finished for available images", 20*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
t.Log("verify cache is up to date after scanner generator ran")
|
|
|
|
// Verify all of the entries are cached
|
|
for image, digestStr := range imageMap {
|
|
repo, _, _ := zcommon.GetImageDirAndReference(image)
|
|
|
|
ok, err := scanner.IsImageFormatScannable(repo, digestStr)
|
|
if ok && err == nil && repo != "repo7" {
|
|
t.Log("expecting " + image + " " + digestStr + " to be present in cache")
|
|
So(scanner.IsResultCached(digestStr), ShouldBeTrue)
|
|
} else {
|
|
// We don't cache results for unscannable manifests
|
|
t.Log("expecting " + image + " " + digestStr + " to be absent from cache")
|
|
So(scanner.IsResultCached(digestStr), ShouldBeFalse)
|
|
}
|
|
}
|
|
|
|
// Make sure the scanner generator is catching the metadb error for repo5:nonexitent-manifest
|
|
found, err = test.ReadLogFileAndSearchString(logPath,
|
|
"Scheduled CVE scan: error while obtaining repo metadata", 20*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
// Make sure the scanner generator is catching the scanning error for repo7
|
|
found, err = test.ReadLogFileAndSearchString(logPath,
|
|
"Scheduled CVE scan errored for image", 20*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
// Make sure the scanner generator is triggered at least twice
|
|
found, err = test.ReadLogFileAndCountStringOccurence(logPath,
|
|
"Scheduled CVE scan: finished for available images", 30*time.Second, 2)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
}
|
|
|
|
func TestScanGeneratorWithRealData(t *testing.T) {
|
|
Convey("Test CVE scanning task scheduler real data", t, func() {
|
|
rootDir := t.TempDir()
|
|
|
|
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
|
|
logPath := logFile.Name()
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
logger := log.NewLogger("debug", logPath)
|
|
writers := io.MultiWriter(os.Stdout, logFile)
|
|
logger.Logger = logger.Output(writers)
|
|
|
|
cfg := config.New()
|
|
cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3}
|
|
|
|
boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: rootDir})
|
|
So(err, ShouldBeNil)
|
|
|
|
metaDB, err := boltdb.New(boltDriver, logger)
|
|
So(err, ShouldBeNil)
|
|
|
|
imageStore := local.NewImageStore(rootDir, false, false,
|
|
logger, monitoring.NewMetricsServer(false, logger), nil, nil)
|
|
storeController := storage.StoreController{DefaultStore: imageStore}
|
|
|
|
image := CreateRandomVulnerableImage()
|
|
|
|
err = WriteImageToFileSystem(image, "zot-test", "0.0.1", storeController)
|
|
So(err, ShouldBeNil)
|
|
|
|
err = meta.ParseStorage(metaDB, storeController, logger)
|
|
So(err, ShouldBeNil)
|
|
|
|
scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", logger)
|
|
err = scanner.UpdateDB()
|
|
So(err, ShouldBeNil)
|
|
|
|
So(scanner.IsResultCached(image.DigestStr()), ShouldBeFalse)
|
|
|
|
sch := scheduler.NewScheduler(cfg, logger)
|
|
|
|
generator := cveinfo.NewScanTaskGenerator(metaDB, scanner, logger)
|
|
|
|
// Start the generator
|
|
sch.SubmitGenerator(generator, 120*time.Second, scheduler.MediumPriority)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
sch.RunScheduler(ctx)
|
|
|
|
defer cancel()
|
|
|
|
// Make sure the scanner generator has completed
|
|
found, err := test.ReadLogFileAndSearchString(logPath,
|
|
"Scheduled CVE scan: finished for available images", 120*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
found, err = test.ReadLogFileAndSearchString(logPath,
|
|
image.ManifestDescriptor.Digest.String(), 120*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
found, err = test.ReadLogFileAndSearchString(logPath,
|
|
"Scheduled CVE scan completed successfully for image", 120*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
So(scanner.IsResultCached(image.DigestStr()), ShouldBeTrue)
|
|
|
|
cveMap, err := scanner.ScanImage("zot-test:0.0.1")
|
|
So(err, ShouldBeNil)
|
|
t.Logf("cveMap: %v", cveMap)
|
|
// As of September 22 2023 there are 5 CVEs:
|
|
// CVE-2023-1255, CVE-2023-2650, CVE-2023-2975, CVE-2023-3817, CVE-2023-3446
|
|
// There may be more discovered in the future
|
|
So(len(cveMap), ShouldBeGreaterThanOrEqualTo, 5)
|
|
So(cveMap, ShouldContainKey, "CVE-2023-1255")
|
|
So(cveMap, ShouldContainKey, "CVE-2023-2650")
|
|
So(cveMap, ShouldContainKey, "CVE-2023-2975")
|
|
So(cveMap, ShouldContainKey, "CVE-2023-3817")
|
|
So(cveMap, ShouldContainKey, "CVE-2023-3446")
|
|
|
|
cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, logger)
|
|
|
|
// Based on cache population only, no extra scanning
|
|
cveSummary, err := cveInfo.GetCVESummaryForImageMedia("zot-test", image.DigestStr(),
|
|
image.ManifestDescriptor.MediaType)
|
|
So(err, ShouldBeNil)
|
|
So(cveSummary.Count, ShouldBeGreaterThanOrEqualTo, 5)
|
|
// As of September 22 the max severity is MEDIUM, but new CVEs could appear in the future
|
|
So([]string{"MEDIUM", "HIGH", "CRITICAL"}, ShouldContain, cveSummary.MaxSeverity)
|
|
})
|
|
}
|