zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/cve/scan_test.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  package cveinfo_test
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"io"
    10  	"os"
    11  	"testing"
    12  	"time"
    13  
    14  	regTypes "github.com/google/go-containerregistry/pkg/v1/types"
    15  	godigest "github.com/opencontainers/go-digest"
    16  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    17  	. "github.com/smartystreets/goconvey/convey"
    18  
    19  	zerr "zotregistry.dev/zot/errors"
    20  	"zotregistry.dev/zot/pkg/api/config"
    21  	zcommon "zotregistry.dev/zot/pkg/common"
    22  	"zotregistry.dev/zot/pkg/extensions/monitoring"
    23  	cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
    24  	cvecache "zotregistry.dev/zot/pkg/extensions/search/cve/cache"
    25  	cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
    26  	"zotregistry.dev/zot/pkg/log"
    27  	"zotregistry.dev/zot/pkg/meta"
    28  	"zotregistry.dev/zot/pkg/meta/boltdb"
    29  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    30  	"zotregistry.dev/zot/pkg/scheduler"
    31  	"zotregistry.dev/zot/pkg/storage"
    32  	"zotregistry.dev/zot/pkg/storage/local"
    33  	test "zotregistry.dev/zot/pkg/test/common"
    34  	. "zotregistry.dev/zot/pkg/test/image-utils"
    35  	"zotregistry.dev/zot/pkg/test/mocks"
    36  )
    37  
    38  var (
    39  	ErrBadTest    = errors.New("there is a bug in the test")
    40  	ErrFailedScan = errors.New("scan has failed intentionally")
    41  )
    42  
    43  func TestScanGeneratorWithMockedData(t *testing.T) { //nolint: gocyclo
    44  	Convey("Test CVE scanning task scheduler with diverse mocked data", t, func() {
    45  		repo1 := "repo1"
    46  		repoIndex := "repoIndex"
    47  
    48  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
    49  		logPath := logFile.Name()
    50  		So(err, ShouldBeNil)
    51  
    52  		defer os.Remove(logFile.Name()) // clean up
    53  
    54  		logger := log.NewLogger("debug", logPath)
    55  		writers := io.MultiWriter(os.Stdout, logFile)
    56  		logger.Logger = logger.Output(writers)
    57  
    58  		cfg := config.New()
    59  		cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3}
    60  		metrics := monitoring.NewMetricsServer(true, logger)
    61  		sch := scheduler.NewScheduler(cfg, metrics, logger)
    62  
    63  		params := boltdb.DBParameters{
    64  			RootDir: t.TempDir(),
    65  		}
    66  		boltDriver, err := boltdb.GetBoltDriver(params)
    67  		So(err, ShouldBeNil)
    68  
    69  		metaDB, err := boltdb.New(boltDriver, log.NewLogger("debug", ""))
    70  		So(err, ShouldBeNil)
    71  
    72  		// Refactor Idea: We can use InitializeTestMetaDB
    73  
    74  		// Create metadb data for scannable image with vulnerabilities
    75  		image11 := CreateImageWith().DefaultLayers().
    76  			ImageConfig(ispec.Image{Created: DateRef(2008, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
    77  
    78  		err = metaDB.SetRepoReference(context.Background(), "repo1", "0.1.0", image11.AsImageMeta())
    79  		So(err, ShouldBeNil)
    80  
    81  		image12 := CreateImageWith().DefaultLayers().
    82  			ImageConfig(ispec.Image{Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
    83  
    84  		err = metaDB.SetRepoReference(context.Background(), "repo1", "1.0.0", image12.AsImageMeta())
    85  		So(err, ShouldBeNil)
    86  
    87  		image13 := CreateImageWith().DefaultLayers().
    88  			ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
    89  
    90  		err = metaDB.SetRepoReference(context.Background(), "repo1", "1.1.0", image13.AsImageMeta())
    91  		So(err, ShouldBeNil)
    92  
    93  		image14 := CreateImageWith().DefaultLayers().
    94  			ImageConfig(ispec.Image{Created: DateRef(2011, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
    95  
    96  		err = metaDB.SetRepoReference(context.Background(), "repo1", "1.0.1", image14.AsImageMeta())
    97  		So(err, ShouldBeNil)
    98  
    99  		// Create metadb data for scannable image with no vulnerabilities
   100  		image61 := CreateImageWith().DefaultLayers().
   101  			ImageConfig(ispec.Image{Created: DateRef(2016, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   102  
   103  		err = metaDB.SetRepoReference(context.Background(), "repo6", "1.0.0", image61.AsImageMeta())
   104  		So(err, ShouldBeNil)
   105  
   106  		// Create metadb data for image not supporting scanning
   107  		image21 := CreateImageWith().Layers([]Layer{{
   108  			MediaType: ispec.MediaTypeImageLayerNonDistributableGzip, //nolint:staticcheck
   109  			Blob:      []byte{10, 10, 10},
   110  			Digest:    godigest.FromBytes([]byte{10, 10, 10}),
   111  		}}).ImageConfig(ispec.Image{Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   112  
   113  		err = metaDB.SetRepoReference(context.Background(), "repo2", "1.0.0", image21.AsImageMeta())
   114  		So(err, ShouldBeNil)
   115  
   116  		// Create metadb data for invalid images/negative tests
   117  		img := CreateRandomImage()
   118  		digest31 := img.Digest()
   119  
   120  		err = metaDB.SetRepoReference(context.Background(), "repo3", "invalid-manifest", img.AsImageMeta())
   121  		So(err, ShouldBeNil)
   122  
   123  		image41 := CreateImageWith().DefaultLayers().
   124  			CustomConfigBlob([]byte("invalid config blob"), ispec.MediaTypeImageConfig).Build()
   125  
   126  		err = metaDB.SetRepoReference(context.Background(), "repo4", "invalid-config", image41.AsImageMeta())
   127  		So(err, ShouldBeNil)
   128  
   129  		image15 := CreateRandomMultiarch()
   130  
   131  		digest51 := image15.Digest()
   132  		err = metaDB.SetRepoReference(context.Background(), "repo5", "nonexitent-manifests-for-multiarch",
   133  			image15.AsImageMeta())
   134  		So(err, ShouldBeNil)
   135  
   136  		// Create metadb data for scannable image which errors during scan
   137  		image71 := CreateImageWith().DefaultLayers().
   138  			ImageConfig(ispec.Image{Created: DateRef(2000, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   139  
   140  		err = metaDB.SetRepoReference(context.Background(), "repo7", "1.0.0", image71.AsImageMeta())
   141  		So(err, ShouldBeNil)
   142  
   143  		// Create multiarch image with vulnerabilities
   144  		multiarchImage := CreateRandomMultiarch()
   145  
   146  		err = metaDB.SetRepoReference(context.Background(), repoIndex, multiarchImage.Images[0].DigestStr(),
   147  			multiarchImage.Images[0].AsImageMeta())
   148  		So(err, ShouldBeNil)
   149  		err = metaDB.SetRepoReference(context.Background(), repoIndex, multiarchImage.Images[1].DigestStr(),
   150  			multiarchImage.Images[1].AsImageMeta())
   151  		So(err, ShouldBeNil)
   152  		err = metaDB.SetRepoReference(context.Background(), repoIndex, multiarchImage.Images[2].DigestStr(),
   153  			multiarchImage.Images[2].AsImageMeta())
   154  		So(err, ShouldBeNil)
   155  
   156  		err = metaDB.SetRepoReference(context.Background(), repoIndex, "tagIndex", multiarchImage.AsImageMeta())
   157  		So(err, ShouldBeNil)
   158  
   159  		err = metaDB.SetRepoMeta("repo-with-bad-tag-digest", mTypes.RepoMeta{
   160  			Name: "repo-with-bad-tag-digest",
   161  			Tags: map[mTypes.Tag]mTypes.Descriptor{
   162  				"tag":            {MediaType: ispec.MediaTypeImageManifest, Digest: godigest.FromString("1").String()},
   163  				"tag-multi-arch": {MediaType: ispec.MediaTypeImageIndex, Digest: godigest.FromString("2").String()},
   164  			},
   165  		})
   166  		So(err, ShouldBeNil)
   167  
   168  		// Keep a record of all the image references / digest pairings
   169  		// This is normally done in MetaDB, but we want to verify
   170  		// the whole flow, including MetaDB
   171  		imageMap := map[string]string{}
   172  
   173  		image11Digest := image11.ManifestDescriptor.Digest.String()
   174  		image11Name := "repo1:0.1.0"
   175  		imageMap[image11Name] = image11Digest
   176  		image12Digest := image12.ManifestDescriptor.Digest.String()
   177  		image12Name := "repo1:1.0.0"
   178  		imageMap[image12Name] = image12Digest
   179  		image13Digest := image13.ManifestDescriptor.Digest.String()
   180  		image13Name := "repo1:1.1.0"
   181  		imageMap[image13Name] = image13Digest
   182  		image14Digest := image14.ManifestDescriptor.Digest.String()
   183  		image14Name := "repo1:1.0.1"
   184  		imageMap[image14Name] = image14Digest
   185  		image21Digest := image21.ManifestDescriptor.Digest.String()
   186  		image21Name := "repo2:1.0.0"
   187  		imageMap[image21Name] = image21Digest
   188  		image31Name := "repo3:invalid-manifest"
   189  		imageMap[image31Name] = digest31.String()
   190  		image41Digest := image41.ManifestDescriptor.Digest.String()
   191  		image41Name := "repo4:invalid-config"
   192  		imageMap[image41Name] = image41Digest
   193  		image51Name := "repo5:nonexitent-manifest-for-multiarch"
   194  		imageMap[image51Name] = digest51.String()
   195  		image61Digest := image61.ManifestDescriptor.Digest.String()
   196  		image61Name := "repo6:1.0.0"
   197  		imageMap[image61Name] = image61Digest
   198  		image71Digest := image71.ManifestDescriptor.Digest.String()
   199  		image71Name := "repo7:1.0.0"
   200  		imageMap[image71Name] = image71Digest
   201  		indexDigest := multiarchImage.IndexDescriptor.Digest.String()
   202  		indexName := "repoIndex:tagIndex"
   203  		imageMap[indexName] = indexDigest
   204  		indexM1Digest := multiarchImage.Images[0].ManifestDescriptor.Digest.String()
   205  		indexM1Name := "repoIndex@" + indexM1Digest
   206  		imageMap[indexM1Name] = indexM1Digest
   207  		indexM2Digest := multiarchImage.Images[1].ManifestDescriptor.Digest.String()
   208  		indexM2Name := "repoIndex@" + indexM2Digest
   209  		imageMap[indexM2Name] = indexM2Digest
   210  		indexM3Digest := multiarchImage.Images[2].ManifestDescriptor.Digest.String()
   211  		indexM3Name := "repoIndex@" + indexM3Digest
   212  		imageMap[indexM3Name] = indexM3Digest
   213  
   214  		// Initialize a test CVE cache
   215  		cache := cvecache.NewCveCache(20, logger)
   216  
   217  		// MetaDB loaded with initial data, now mock the scanner
   218  		// Setup test CVE data in mock scanner
   219  		scanner := mocks.CveScannerMock{
   220  			ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   221  				result := cache.Get(image)
   222  				// Will not match sending the repo:tag as a parameter, but we don't care
   223  				if result != nil {
   224  					return result, nil
   225  				}
   226  
   227  				repo, ref, isTag := zcommon.GetImageDirAndReference(image)
   228  				if isTag {
   229  					foundRef, ok := imageMap[image]
   230  					if !ok {
   231  						return nil, ErrBadTest
   232  					}
   233  					ref = foundRef
   234  				}
   235  
   236  				// Images in chronological order
   237  				if repo == repo1 && ref == image11Digest {
   238  					result := map[string]cvemodel.CVE{
   239  						"CVE1": {
   240  							ID:          "CVE1",
   241  							Severity:    "MEDIUM",
   242  							Title:       "Title CVE1",
   243  							Description: "Description CVE1",
   244  						},
   245  					}
   246  
   247  					cache.Add(ref, result)
   248  
   249  					return result, nil
   250  				}
   251  
   252  				if repo == repo1 && zcommon.Contains([]string{image12Digest, image21Digest}, ref) {
   253  					result := map[string]cvemodel.CVE{
   254  						"CVE1": {
   255  							ID:          "CVE1",
   256  							Severity:    "MEDIUM",
   257  							Title:       "Title CVE1",
   258  							Description: "Description CVE1",
   259  						},
   260  						"CVE2": {
   261  							ID:          "CVE2",
   262  							Severity:    "HIGH",
   263  							Title:       "Title CVE2",
   264  							Description: "Description CVE2",
   265  						},
   266  						"CVE3": {
   267  							ID:          "CVE3",
   268  							Severity:    "LOW",
   269  							Title:       "Title CVE3",
   270  							Description: "Description CVE3",
   271  						},
   272  					}
   273  
   274  					cache.Add(ref, result)
   275  
   276  					return result, nil
   277  				}
   278  
   279  				if repo == repo1 && ref == image13Digest {
   280  					result := map[string]cvemodel.CVE{
   281  						"CVE3": {
   282  							ID:          "CVE3",
   283  							Severity:    "LOW",
   284  							Title:       "Title CVE3",
   285  							Description: "Description CVE3",
   286  						},
   287  					}
   288  
   289  					cache.Add(ref, result)
   290  
   291  					return result, nil
   292  				}
   293  
   294  				// As a minor release on 1.0.0 banch
   295  				// does not include all fixes published in 1.1.0
   296  				if repo == repo1 && ref == image14Digest {
   297  					result := map[string]cvemodel.CVE{
   298  						"CVE1": {
   299  							ID:          "CVE1",
   300  							Severity:    "MEDIUM",
   301  							Title:       "Title CVE1",
   302  							Description: "Description CVE1",
   303  						},
   304  						"CVE3": {
   305  							ID:          "CVE3",
   306  							Severity:    "LOW",
   307  							Title:       "Title CVE3",
   308  							Description: "Description CVE3",
   309  						},
   310  					}
   311  
   312  					cache.Add(ref, result)
   313  
   314  					return result, nil
   315  				}
   316  
   317  				// Unexpected error while scanning
   318  				if repo == "repo7" {
   319  					return map[string]cvemodel.CVE{}, ErrFailedScan
   320  				}
   321  
   322  				if (repo == repoIndex && ref == indexDigest) ||
   323  					(repo == repoIndex && ref == indexM1Digest) {
   324  					result := map[string]cvemodel.CVE{
   325  						"CVE1": {
   326  							ID:          "CVE1",
   327  							Severity:    "MEDIUM",
   328  							Title:       "Title CVE1",
   329  							Description: "Description CVE1",
   330  						},
   331  					}
   332  
   333  					// Simulate scanning an index results in scanning its manifests
   334  					if ref == indexDigest {
   335  						cache.Add(indexM1Digest, result)
   336  						cache.Add(indexM2Digest, map[string]cvemodel.CVE{})
   337  						cache.Add(indexM3Digest, map[string]cvemodel.CVE{})
   338  					}
   339  
   340  					cache.Add(ref, result)
   341  
   342  					return result, nil
   343  				}
   344  
   345  				// By default the image has no vulnerabilities
   346  				result = map[string]cvemodel.CVE{}
   347  				cache.Add(ref, result)
   348  
   349  				return result, nil
   350  			},
   351  			IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
   352  				if repo == repoIndex {
   353  					return true, nil
   354  				}
   355  
   356  				// Almost same logic compared to actual Trivy specific implementation
   357  				imageDir, inputTag := repo, reference
   358  
   359  				repoMeta, err := metaDB.GetRepoMeta(context.Background(), imageDir)
   360  				if err != nil {
   361  					return false, err
   362  				}
   363  
   364  				manifestDigestStr := reference
   365  
   366  				if zcommon.IsTag(reference) {
   367  					var ok bool
   368  
   369  					descriptor, ok := repoMeta.Tags[inputTag]
   370  					if !ok {
   371  						return false, zerr.ErrTagMetaNotFound
   372  					}
   373  
   374  					manifestDigestStr = descriptor.Digest
   375  				}
   376  
   377  				manifestDigest, err := godigest.Parse(manifestDigestStr)
   378  				if err != nil {
   379  					return false, err
   380  				}
   381  
   382  				manifestData, err := metaDB.GetImageMeta(manifestDigest)
   383  				if err != nil {
   384  					return false, err
   385  				}
   386  
   387  				for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers {
   388  					switch imageLayer.MediaType {
   389  					case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
   390  
   391  						return true, nil
   392  					default:
   393  
   394  						return false, zerr.ErrScanNotSupported
   395  					}
   396  				}
   397  
   398  				return false, nil
   399  			},
   400  			IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
   401  				if repo == "repo2" {
   402  					if digest == image21Digest {
   403  						return false, nil
   404  					}
   405  				}
   406  
   407  				return true, nil
   408  			},
   409  			IsResultCachedFn: func(digest string) bool {
   410  				return cache.Contains(digest)
   411  			},
   412  			UpdateDBFn: func(ctx context.Context) error {
   413  				cache.Purge()
   414  
   415  				return nil
   416  			},
   417  		}
   418  
   419  		// Purge scan, it should not be needed
   420  		So(scanner.UpdateDB(context.Background()), ShouldBeNil)
   421  
   422  		// Verify none of the entries are cached to begin with
   423  		t.Log("verify cache is initially empty")
   424  
   425  		for image, digestStr := range imageMap {
   426  			t.Log("expecting " + image + " " + digestStr + " to be absent from cache")
   427  			So(scanner.IsResultCached(digestStr), ShouldBeFalse)
   428  		}
   429  
   430  		// Start the generator
   431  		generator := cveinfo.NewScanTaskGenerator(metaDB, scanner, logger)
   432  
   433  		sch.SubmitGenerator(generator, 10*time.Second, scheduler.MediumPriority)
   434  
   435  		sch.RunScheduler()
   436  
   437  		defer sch.Shutdown()
   438  
   439  		// Make sure the scanner generator has completed despite errors
   440  		found, err := test.ReadLogFileAndSearchString(logPath,
   441  			"finished scanning available images during scheduled cve scan", 40*time.Second)
   442  		So(err, ShouldBeNil)
   443  		So(found, ShouldBeTrue)
   444  
   445  		t.Log("verify cache is up to date after scanner generator ran")
   446  
   447  		// Verify all of the entries are cached
   448  		for image, digestStr := range imageMap {
   449  			repo, _, _ := zcommon.GetImageDirAndReference(image)
   450  
   451  			ok, err := scanner.IsImageFormatScannable(repo, digestStr)
   452  			if ok && err == nil && repo != "repo7" {
   453  				t.Log("expecting " + image + " " + digestStr + " to be present in cache")
   454  				So(scanner.IsResultCached(digestStr), ShouldBeTrue)
   455  			} else {
   456  				// We don't cache results for un-scannable manifests
   457  				t.Log("expecting " + image + " " + digestStr + " to be absent from cache")
   458  				So(scanner.IsResultCached(digestStr), ShouldBeFalse)
   459  			}
   460  		}
   461  
   462  		found, err = test.ReadLogFileAndSearchString(logPath,
   463  			"failed to obtain repo metadata during scheduled cve scan", 20*time.Second)
   464  		So(err, ShouldBeNil)
   465  		So(found, ShouldBeTrue)
   466  
   467  		// Make sure the scanner generator is catching the scanning error for repo7
   468  		found, err = test.ReadLogFileAndSearchString(logPath,
   469  			"failed to perform scheduled cve scan for image", 20*time.Second)
   470  		So(err, ShouldBeNil)
   471  		So(found, ShouldBeTrue)
   472  
   473  		// Make sure the scanner generator is triggered at least twice
   474  		found, err = test.ReadLogFileAndCountStringOccurence(logPath,
   475  			"finished scanning available images during scheduled cve scan", 30*time.Second, 2)
   476  		So(err, ShouldBeNil)
   477  		So(found, ShouldBeTrue)
   478  	})
   479  }
   480  
   481  func TestScanGeneratorWithRealData(t *testing.T) {
   482  	Convey("Test CVE scanning task scheduler real data", t, func() {
   483  		rootDir := t.TempDir()
   484  
   485  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   486  		logPath := logFile.Name()
   487  		So(err, ShouldBeNil)
   488  
   489  		defer os.Remove(logFile.Name()) // clean up
   490  
   491  		logger := log.NewLogger("debug", logPath)
   492  		writers := io.MultiWriter(os.Stdout, logFile)
   493  		logger.Logger = logger.Output(writers)
   494  
   495  		cfg := config.New()
   496  		cfg.Scheduler = &config.SchedulerConfig{NumWorkers: 3}
   497  
   498  		boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: rootDir})
   499  		So(err, ShouldBeNil)
   500  
   501  		metaDB, err := boltdb.New(boltDriver, logger)
   502  		So(err, ShouldBeNil)
   503  
   504  		metrics := monitoring.NewMetricsServer(true, logger)
   505  		imageStore := local.NewImageStore(rootDir, false, false,
   506  			logger, metrics, nil, nil)
   507  		storeController := storage.StoreController{DefaultStore: imageStore}
   508  
   509  		image := CreateRandomVulnerableImage()
   510  
   511  		err = WriteImageToFileSystem(image, "zot-test", "0.0.1", storeController)
   512  		So(err, ShouldBeNil)
   513  
   514  		err = meta.ParseStorage(metaDB, storeController, logger)
   515  		So(err, ShouldBeNil)
   516  
   517  		scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", logger)
   518  		err = scanner.UpdateDB(context.Background())
   519  		So(err, ShouldBeNil)
   520  
   521  		So(scanner.IsResultCached(image.DigestStr()), ShouldBeFalse)
   522  
   523  		sch := scheduler.NewScheduler(cfg, metrics, logger)
   524  
   525  		generator := cveinfo.NewScanTaskGenerator(metaDB, scanner, logger)
   526  
   527  		// Start the generator
   528  		sch.SubmitGenerator(generator, 120*time.Second, scheduler.MediumPriority)
   529  
   530  		sch.RunScheduler()
   531  
   532  		defer sch.Shutdown()
   533  
   534  		// Make sure the scanner generator has completed
   535  		found, err := test.ReadLogFileAndSearchString(logPath,
   536  			"finished scanning available images during scheduled cve scan", 120*time.Second)
   537  		So(err, ShouldBeNil)
   538  		So(found, ShouldBeTrue)
   539  
   540  		found, err = test.ReadLogFileAndSearchString(logPath,
   541  			image.ManifestDescriptor.Digest.String(), 120*time.Second)
   542  		So(err, ShouldBeNil)
   543  		So(found, ShouldBeTrue)
   544  
   545  		found, err = test.ReadLogFileAndSearchString(logPath,
   546  			"scheduled cve scan completed successfully for image", 120*time.Second)
   547  		So(err, ShouldBeNil)
   548  		So(found, ShouldBeTrue)
   549  
   550  		So(scanner.IsResultCached(image.DigestStr()), ShouldBeTrue)
   551  
   552  		cveMap, err := scanner.ScanImage(context.Background(), "zot-test:0.0.1")
   553  		So(err, ShouldBeNil)
   554  		t.Logf("cveMap: %v", cveMap)
   555  		// As of September 22 2023 there are 5 CVEs:
   556  		// CVE-2023-1255, CVE-2023-2650, CVE-2023-2975, CVE-2023-3817, CVE-2023-3446
   557  		// There may be more discovered in the future
   558  		So(len(cveMap), ShouldBeGreaterThanOrEqualTo, 5)
   559  		So(cveMap, ShouldContainKey, "CVE-2023-1255")
   560  		So(cveMap, ShouldContainKey, "CVE-2023-2650")
   561  		So(cveMap, ShouldContainKey, "CVE-2023-2975")
   562  		So(cveMap, ShouldContainKey, "CVE-2023-3817")
   563  		So(cveMap, ShouldContainKey, "CVE-2023-3446")
   564  
   565  		cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, logger)
   566  
   567  		// Based on cache population only, no extra scanning
   568  		cveSummary, err := cveInfo.GetCVESummaryForImageMedia(context.Background(), "zot-test", image.DigestStr(),
   569  			image.ManifestDescriptor.MediaType)
   570  		So(err, ShouldBeNil)
   571  		So(cveSummary.Count, ShouldBeGreaterThanOrEqualTo, 5)
   572  		// As of September 22 the max severity is MEDIUM, but new CVEs could appear in the future
   573  		So([]string{"MEDIUM", "HIGH", "CRITICAL"}, ShouldContain, cveSummary.MaxSeverity)
   574  	})
   575  }