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