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