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

     1  //go:build search
     2  // +build search
     3  
     4  package trivy
     5  
     6  import (
     7  	"context"
     8  	"os"
     9  	"path"
    10  	"testing"
    11  	"time"
    12  
    13  	godigest "github.com/opencontainers/go-digest"
    14  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    15  	. "github.com/smartystreets/goconvey/convey"
    16  
    17  	zerr "zotregistry.dev/zot/errors"
    18  	"zotregistry.dev/zot/pkg/common"
    19  	"zotregistry.dev/zot/pkg/extensions/monitoring"
    20  	"zotregistry.dev/zot/pkg/extensions/search/cve/model"
    21  	"zotregistry.dev/zot/pkg/log"
    22  	"zotregistry.dev/zot/pkg/meta"
    23  	"zotregistry.dev/zot/pkg/meta/boltdb"
    24  	"zotregistry.dev/zot/pkg/meta/types"
    25  	"zotregistry.dev/zot/pkg/storage"
    26  	"zotregistry.dev/zot/pkg/storage/imagestore"
    27  	"zotregistry.dev/zot/pkg/storage/local"
    28  	storageTypes "zotregistry.dev/zot/pkg/storage/types"
    29  	test "zotregistry.dev/zot/pkg/test/common"
    30  	. "zotregistry.dev/zot/pkg/test/image-utils"
    31  	"zotregistry.dev/zot/pkg/test/mocks"
    32  )
    33  
    34  func generateTestImage(storeController storage.StoreController, imageName string) {
    35  	repoName, tag := common.GetImageDirAndTag(imageName)
    36  
    37  	image := CreateRandomImage()
    38  
    39  	err := WriteImageToFileSystem(
    40  		image, repoName, tag, storeController)
    41  	So(err, ShouldBeNil)
    42  }
    43  
    44  func TestMultipleStoragePath(t *testing.T) {
    45  	Convey("Test multiple storage path", t, func() {
    46  		// Create temporary directory
    47  		firstRootDir := t.TempDir()
    48  		secondRootDir := t.TempDir()
    49  		thirdRootDir := t.TempDir()
    50  
    51  		log := log.NewLogger("debug", "")
    52  		metrics := monitoring.NewMetricsServer(false, log)
    53  
    54  		// Create ImageStore
    55  
    56  		firstStore := local.NewImageStore(firstRootDir, false, false, log, metrics, nil, nil)
    57  
    58  		secondStore := local.NewImageStore(secondRootDir, false, false, log, metrics, nil, nil)
    59  
    60  		thirdStore := local.NewImageStore(thirdRootDir, false, false, log, metrics, nil, nil)
    61  
    62  		storeController := storage.StoreController{}
    63  
    64  		storeController.DefaultStore = firstStore
    65  
    66  		subStore := make(map[string]storageTypes.ImageStore)
    67  
    68  		subStore["/a"] = secondStore
    69  		subStore["/b"] = thirdStore
    70  
    71  		storeController.SubStore = subStore
    72  
    73  		params := boltdb.DBParameters{
    74  			RootDir: firstRootDir,
    75  		}
    76  		boltDriver, err := boltdb.GetBoltDriver(params)
    77  		So(err, ShouldBeNil)
    78  
    79  		metaDB, err := boltdb.New(boltDriver, log)
    80  		So(err, ShouldBeNil)
    81  
    82  		scanner := NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
    83  
    84  		So(scanner.storeController.DefaultStore, ShouldNotBeNil)
    85  		So(scanner.storeController.SubStore, ShouldNotBeNil)
    86  
    87  		img0 := "test/image0:tag0"
    88  		img1 := "a/test/image1:tag1"
    89  		img2 := "b/test/image2:tag2"
    90  
    91  		opts := scanner.getTrivyOptions(img0)
    92  		So(opts.ScanOptions.Target, ShouldEqual, path.Join(firstStore.RootDir(), img0))
    93  
    94  		opts = scanner.getTrivyOptions(img1)
    95  		So(opts.ScanOptions.Target, ShouldEqual, path.Join(secondStore.RootDir(), img1))
    96  
    97  		opts = scanner.getTrivyOptions(img2)
    98  		So(opts.ScanOptions.Target, ShouldEqual, path.Join(thirdStore.RootDir(), img2))
    99  
   100  		generateTestImage(storeController, img0)
   101  		generateTestImage(storeController, img1)
   102  		generateTestImage(storeController, img2)
   103  
   104  		err = meta.ParseStorage(metaDB, storeController, log)
   105  		So(err, ShouldBeNil)
   106  
   107  		// Try to scan without the DB being downloaded
   108  		_, err = scanner.ScanImage(context.Background(), img0)
   109  		So(err, ShouldNotBeNil)
   110  		So(err, ShouldWrap, zerr.ErrCVEDBNotFound)
   111  
   112  		// Try to scan with a context done
   113  
   114  		ctx, cancel := context.WithCancel(context.Background())
   115  		cancel()
   116  
   117  		_, err = scanner.ScanImage(ctx, img0)
   118  		So(err, ShouldNotBeNil)
   119  
   120  		ctx = context.Background()
   121  
   122  		// Download DB since DB download on scan is disabled
   123  		err = scanner.UpdateDB(ctx)
   124  		So(err, ShouldBeNil)
   125  
   126  		// Scanning image in default store
   127  		cveMap, err := scanner.ScanImage(ctx, img0)
   128  
   129  		So(err, ShouldBeNil)
   130  		So(len(cveMap), ShouldEqual, 0)
   131  
   132  		// Scanning image in substore
   133  		cveMap, err = scanner.ScanImage(ctx, img1)
   134  		So(err, ShouldBeNil)
   135  		So(len(cveMap), ShouldEqual, 0)
   136  
   137  		// Scanning image which does not exist
   138  		cveMap, err = scanner.ScanImage(ctx, "a/test/image2:tag100")
   139  		So(err, ShouldNotBeNil)
   140  		So(len(cveMap), ShouldEqual, 0)
   141  
   142  		// Download the DB to a default store location without permissions
   143  		err = os.Chmod(firstRootDir, 0o000)
   144  		So(err, ShouldBeNil)
   145  		err = scanner.UpdateDB(ctx)
   146  		So(err, ShouldNotBeNil)
   147  
   148  		// Check the download works correctly when permissions allow
   149  		err = os.Chmod(firstRootDir, 0o777)
   150  		So(err, ShouldBeNil)
   151  		err = scanner.UpdateDB(ctx)
   152  		So(err, ShouldBeNil)
   153  
   154  		// Download the DB to a substore location without permissions
   155  		err = os.Chmod(secondRootDir, 0o000)
   156  		So(err, ShouldBeNil)
   157  		err = scanner.UpdateDB(ctx)
   158  		So(err, ShouldNotBeNil)
   159  
   160  		err = os.Chmod(secondRootDir, 0o777)
   161  		So(err, ShouldBeNil)
   162  	})
   163  }
   164  
   165  func TestTrivyLibraryErrors(t *testing.T) {
   166  	Convey("Test trivy API errors", t, func() {
   167  		// Create temporary directory
   168  		rootDir := t.TempDir()
   169  
   170  		log := log.NewLogger("debug", "")
   171  		metrics := monitoring.NewMetricsServer(false, log)
   172  
   173  		// Create ImageStore
   174  		store := local.NewImageStore(rootDir, false, false, log, metrics, nil, nil)
   175  
   176  		storeController := storage.StoreController{}
   177  		storeController.DefaultStore = store
   178  
   179  		err := WriteImageToFileSystem(CreateDefaultVulnerableImage(), "zot-test", "0.0.1", storeController)
   180  		So(err, ShouldBeNil)
   181  
   182  		params := boltdb.DBParameters{
   183  			RootDir: rootDir,
   184  		}
   185  
   186  		boltDriver, err := boltdb.GetBoltDriver(params)
   187  		So(err, ShouldBeNil)
   188  
   189  		metaDB, err := boltdb.New(boltDriver, log)
   190  		So(err, ShouldBeNil)
   191  
   192  		err = meta.ParseStorage(metaDB, storeController, log)
   193  		So(err, ShouldBeNil)
   194  
   195  		img := "zot-test:0.0.1" //nolint:goconst
   196  
   197  		// Download DB fails for missing DB url
   198  		scanner := NewScanner(storeController, metaDB, "", "", log)
   199  
   200  		ctx := context.Background()
   201  
   202  		err = scanner.UpdateDB(ctx)
   203  		So(err, ShouldNotBeNil)
   204  
   205  		// Try to scan without the DB being downloaded
   206  		opts := scanner.getTrivyOptions(img)
   207  		_, err = scanner.runTrivy(ctx, opts)
   208  		So(err, ShouldNotBeNil)
   209  		So(err, ShouldWrap, zerr.ErrCVEDBNotFound)
   210  
   211  		// Download DB fails for invalid Java DB
   212  		scanner = NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db",
   213  			"ghcr.io/project-zot/trivy-not-db", log)
   214  
   215  		err = scanner.UpdateDB(ctx)
   216  		So(err, ShouldNotBeNil)
   217  
   218  		// Download DB passes for valid Trivy DB url, and missing Trivy Java DB url
   219  		// Download DB is necessary since DB download on scan is disabled
   220  		scanner = NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   221  
   222  		// UpdateDB with good ctx
   223  		err = scanner.UpdateDB(ctx)
   224  		So(err, ShouldBeNil)
   225  
   226  		// Scanning image with correct options
   227  		opts = scanner.getTrivyOptions(img)
   228  		_, err = scanner.runTrivy(ctx, opts)
   229  		So(err, ShouldBeNil)
   230  
   231  		// Scanning image with incorrect cache options
   232  		// to trigger runner initialization errors
   233  		opts.CacheOptions.CacheBackend = "redis://asdf!$%&!*)("
   234  		_, err = scanner.runTrivy(ctx, opts)
   235  		So(err, ShouldNotBeNil)
   236  
   237  		// Scanning image with invalid input to trigger a scanner error
   238  		opts = scanner.getTrivyOptions("nilnonexisting_image:0.0.1")
   239  		_, err = scanner.runTrivy(ctx, opts)
   240  		So(err, ShouldNotBeNil)
   241  
   242  		// Scanning image with incorrect report options
   243  		// to trigger report filtering errors
   244  		opts = scanner.getTrivyOptions(img)
   245  		opts.ReportOptions.IgnorePolicy = "invalid file path"
   246  		_, err = scanner.runTrivy(ctx, opts)
   247  		So(err, ShouldNotBeNil)
   248  	})
   249  }
   250  
   251  func TestImageScannable(t *testing.T) {
   252  	rootDir := t.TempDir()
   253  
   254  	params := boltdb.DBParameters{
   255  		RootDir: rootDir,
   256  	}
   257  
   258  	boltDriver, err := boltdb.GetBoltDriver(params)
   259  	if err != nil {
   260  		panic(err)
   261  	}
   262  
   263  	log := log.NewLogger("debug", "")
   264  
   265  	metaDB, err := boltdb.New(boltDriver, log)
   266  	if err != nil {
   267  		panic(err)
   268  	}
   269  
   270  	// Create test data for the following cases
   271  	// - Error: RepoMeta not found in DB
   272  	// - Error: Tag not found in DB
   273  	// - Error: Digest in RepoMeta is invalid
   274  	// - Error: ManifestData not found in metadb
   275  	// - Error: ManifestData cannot be unmarshalled
   276  	// - Error: ManifestData contains unscannable layer type
   277  	// - Valid Scannable image
   278  
   279  	// Create metadb data for scannable image
   280  	timeStamp := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC)
   281  
   282  	validConfig := ispec.Image{
   283  		Created: &timeStamp,
   284  	}
   285  
   286  	validImage := CreateImageWith().
   287  		Layers([]Layer{{
   288  			MediaType: ispec.MediaTypeImageLayerGzip,
   289  			Digest:    ispec.DescriptorEmptyJSON.Digest,
   290  			Blob:      ispec.DescriptorEmptyJSON.Data,
   291  		}}).ImageConfig(validConfig).Build()
   292  
   293  	err = metaDB.SetRepoReference(context.Background(), "repo1", "valid", validImage.AsImageMeta())
   294  	if err != nil {
   295  		panic(err)
   296  	}
   297  
   298  	// Create MetaDB data for manifest with unscannable layers
   299  	imageWithUnscannableLayer := CreateImageWith().
   300  		Layers([]Layer{{
   301  			MediaType: "unscannable_media_type",
   302  			Digest:    ispec.DescriptorEmptyJSON.Digest,
   303  			Blob:      ispec.DescriptorEmptyJSON.Data,
   304  		}}).ImageConfig(validConfig).Build()
   305  
   306  	err = metaDB.SetRepoReference(context.Background(), "repo1",
   307  		"unscannable-layer", imageWithUnscannableLayer.AsImageMeta())
   308  	if err != nil {
   309  		panic(err)
   310  	}
   311  
   312  	// Continue with initializing the objects the scanner depends on
   313  	metrics := monitoring.NewMetricsServer(false, log)
   314  
   315  	store := local.NewImageStore(rootDir, false, false, log, metrics, nil, nil)
   316  
   317  	storeController := storage.StoreController{}
   318  	storeController.DefaultStore = store
   319  
   320  	scanner := NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db",
   321  		"ghcr.io/aquasecurity/trivy-java-db", log)
   322  
   323  	Convey("Valid image should be scannable", t, func() {
   324  		result, err := scanner.IsImageFormatScannable("repo1", "valid")
   325  		So(err, ShouldBeNil)
   326  		So(result, ShouldBeTrue)
   327  	})
   328  
   329  	Convey("Image with layers of unsupported types should be unscannable", t, func() {
   330  		result, err := scanner.IsImageFormatScannable("repo1", "unscannable-layer")
   331  		So(err, ShouldNotBeNil)
   332  		So(result, ShouldBeFalse)
   333  	})
   334  
   335  	Convey("Image with invalid manifest digest should be unscannable", t, func() {
   336  		result, err := scanner.IsImageFormatScannable("repo1", "invalid-digest")
   337  		So(err, ShouldNotBeNil)
   338  		So(result, ShouldBeFalse)
   339  	})
   340  
   341  	Convey("Image with unknown tag should be unscannable", t, func() {
   342  		result, err := scanner.IsImageFormatScannable("repo1", "unknown-tag")
   343  		So(err, ShouldNotBeNil)
   344  		So(result, ShouldBeFalse)
   345  	})
   346  
   347  	Convey("Image with unknown repo should be unscannable", t, func() {
   348  		result, err := scanner.IsImageFormatScannable("unknown-repo", "sometag")
   349  		So(err, ShouldNotBeNil)
   350  		So(result, ShouldBeFalse)
   351  	})
   352  }
   353  
   354  func TestDefaultTrivyDBUrl(t *testing.T) {
   355  	Convey("Test trivy DB download from default location", t, func() {
   356  		// Create temporary directory
   357  		rootDir := t.TempDir()
   358  
   359  		err := test.CopyFiles("../../../../../test/data/zot-test", path.Join(rootDir, "zot-test"))
   360  		So(err, ShouldBeNil)
   361  
   362  		err = test.CopyFiles("../../../../../test/data/zot-cve-java-test", path.Join(rootDir, "zot-cve-java-test"))
   363  		So(err, ShouldBeNil)
   364  
   365  		log := log.NewLogger("debug", "")
   366  		metrics := monitoring.NewMetricsServer(false, log)
   367  
   368  		// Create ImageStore
   369  		store := local.NewImageStore(rootDir, false, false, log, metrics, nil, nil)
   370  
   371  		storeController := storage.StoreController{}
   372  		storeController.DefaultStore = store
   373  
   374  		params := boltdb.DBParameters{
   375  			RootDir: rootDir,
   376  		}
   377  
   378  		boltDriver, err := boltdb.GetBoltDriver(params)
   379  		So(err, ShouldBeNil)
   380  
   381  		metaDB, err := boltdb.New(boltDriver, log)
   382  		So(err, ShouldBeNil)
   383  
   384  		err = meta.ParseStorage(metaDB, storeController, log)
   385  		So(err, ShouldBeNil)
   386  
   387  		scanner := NewScanner(storeController, metaDB, "ghcr.io/aquasecurity/trivy-db",
   388  			"ghcr.io/aquasecurity/trivy-java-db", log)
   389  
   390  		ctx := context.Background()
   391  
   392  		cancelCtx, cancel := context.WithCancel(ctx)
   393  		cancel()
   394  
   395  		// Download DB with context done should return ctx error.
   396  		err = scanner.UpdateDB(cancelCtx)
   397  		So(err, ShouldNotBeNil)
   398  
   399  		// Download DB since DB download on scan is disabled
   400  		err = scanner.UpdateDB(ctx)
   401  		So(err, ShouldBeNil)
   402  
   403  		// Scanning image
   404  		img := "zot-test:0.0.1" //nolint:goconst
   405  
   406  		opts := scanner.getTrivyOptions(img)
   407  		_, err = scanner.runTrivy(ctx, opts)
   408  		So(err, ShouldBeNil)
   409  
   410  		// Scanning image containing a jar file
   411  		img = "zot-cve-java-test:0.0.1"
   412  
   413  		opts = scanner.getTrivyOptions(img)
   414  		_, err = scanner.runTrivy(ctx, opts)
   415  		So(err, ShouldBeNil)
   416  	})
   417  }
   418  
   419  func TestIsIndexScanable(t *testing.T) {
   420  	Convey("IsIndexScanable", t, func() {
   421  		storeController := storage.StoreController{}
   422  		storeController.DefaultStore = &imagestore.ImageStore{}
   423  
   424  		metaDB := &boltdb.BoltDB{}
   425  		log := log.NewLogger("debug", "")
   426  
   427  		Convey("Find index in cache", func() {
   428  			scanner := NewScanner(storeController, metaDB, "", "", log)
   429  
   430  			scanner.cache.Add("digest", make(map[string]model.CVE))
   431  
   432  			found, err := scanner.isIndexScannable("digest")
   433  			So(err, ShouldBeNil)
   434  			So(found, ShouldBeTrue)
   435  		})
   436  	})
   437  }
   438  
   439  func TestIsIndexScannableErrors(t *testing.T) {
   440  	Convey("Errors", t, func() {
   441  		storeController := storage.StoreController{}
   442  		storeController.DefaultStore = mocks.MockedImageStore{}
   443  
   444  		metaDB := mocks.MetaDBMock{}
   445  		log := log.NewLogger("debug", "")
   446  
   447  		Convey("all manifests of a index are not scannable", func() {
   448  			unscannableLayer := []Layer{{MediaType: "unscannable-layer-type", Digest: godigest.FromString("123")}}
   449  			img1 := CreateImageWith().Layers(unscannableLayer).RandomConfig().Build()
   450  			img2 := CreateImageWith().Layers(unscannableLayer).RandomConfig().Build()
   451  			multiarch := CreateMultiarchWith().Images([]Image{img1, img2}).Build()
   452  
   453  			metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   454  				return map[string]types.ImageMeta{
   455  					img1.DigestStr():      img1.AsImageMeta(),
   456  					img2.DigestStr():      img2.AsImageMeta(),
   457  					multiarch.DigestStr(): multiarch.AsImageMeta(),
   458  				}[digest.String()], nil
   459  			}
   460  
   461  			scanner := NewScanner(storeController, metaDB, "", "", log)
   462  			ok, err := scanner.isIndexScannable(multiarch.DigestStr())
   463  			So(err, ShouldBeNil)
   464  			So(ok, ShouldBeFalse)
   465  		})
   466  	})
   467  }
   468  
   469  func TestGetCVEReference(t *testing.T) {
   470  	Convey("getCVEReference", t, func() {
   471  		ref := getCVEReference("primary", []string{})
   472  		So(ref, ShouldResemble, "primary")
   473  
   474  		ref = getCVEReference("", []string{"secondary"})
   475  		So(ref, ShouldResemble, "secondary")
   476  
   477  		ref = getCVEReference("", []string{""})
   478  		So(ref, ShouldResemble, "")
   479  
   480  		ref = getCVEReference("", []string{"https://nvd.nist.gov/vuln/detail/CVE-2023-2650"})
   481  		So(ref, ShouldResemble, "https://nvd.nist.gov/vuln/detail/CVE-2023-2650")
   482  	})
   483  }