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

     1  //go:build search
     2  
     3  package trivy_test
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	godigest "github.com/opencontainers/go-digest"
    13  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    14  	. "github.com/smartystreets/goconvey/convey"
    15  
    16  	"zotregistry.dev/zot/pkg/api"
    17  	"zotregistry.dev/zot/pkg/api/config"
    18  	extconf "zotregistry.dev/zot/pkg/extensions/config"
    19  	"zotregistry.dev/zot/pkg/extensions/monitoring"
    20  	"zotregistry.dev/zot/pkg/extensions/search/cve/trivy"
    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/local"
    27  	. "zotregistry.dev/zot/pkg/test/common"
    28  	. "zotregistry.dev/zot/pkg/test/image-utils"
    29  	"zotregistry.dev/zot/pkg/test/mocks"
    30  )
    31  
    32  var ErrTestError = errors.New("test error")
    33  
    34  func TestScanBigTestFile(t *testing.T) {
    35  	Convey("Scan zot-test", t, func() {
    36  		projRootDir, err := GetProjectRootDir()
    37  		So(err, ShouldBeNil)
    38  
    39  		testImage := filepath.Join(projRootDir, "test/data/zot-test")
    40  
    41  		tempDir := t.TempDir()
    42  		port := GetFreePort()
    43  		conf := config.New()
    44  		conf.HTTP.Port = port
    45  		defaultVal := true
    46  		conf.Storage.RootDirectory = tempDir
    47  		conf.Extensions = &extconf.ExtensionConfig{
    48  			Search: &extconf.SearchConfig{
    49  				BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
    50  			},
    51  		}
    52  		ctlr := api.NewController(conf)
    53  		So(ctlr, ShouldNotBeNil)
    54  
    55  		err = CopyFiles(testImage, filepath.Join(tempDir, "zot-test"))
    56  		So(err, ShouldBeNil)
    57  
    58  		cm := NewControllerManager(ctlr)
    59  		cm.StartAndWait(port)
    60  		defer cm.StopServer()
    61  		// scan
    62  		scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log)
    63  
    64  		err = scanner.UpdateDB(context.Background())
    65  		So(err, ShouldBeNil)
    66  
    67  		cveMap, err := scanner.ScanImage(context.Background(), "zot-test:0.0.1")
    68  		So(err, ShouldBeNil)
    69  		So(cveMap, ShouldNotBeNil)
    70  	})
    71  }
    72  
    73  func TestScanningByDigest(t *testing.T) {
    74  	Convey("Scan the individual manifests inside an index", t, func() {
    75  		// start server
    76  		tempDir := t.TempDir()
    77  		port := GetFreePort()
    78  		baseURL := GetBaseURL(port)
    79  		conf := config.New()
    80  		conf.HTTP.Port = port
    81  		defaultVal := true
    82  		conf.Storage.RootDirectory = tempDir
    83  		conf.Extensions = &extconf.ExtensionConfig{
    84  			Search: &extconf.SearchConfig{
    85  				BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
    86  			},
    87  		}
    88  		ctlr := api.NewController(conf)
    89  		So(ctlr, ShouldNotBeNil)
    90  
    91  		cm := NewControllerManager(ctlr)
    92  		cm.StartAndWait(port)
    93  		defer cm.StopServer()
    94  		// push index with 2 manifests: one with vulns and one without
    95  		vulnImage := CreateDefaultVulnerableImage()
    96  
    97  		simpleImage := CreateRandomImage()
    98  
    99  		multiArch := CreateMultiarchWith().Images([]Image{simpleImage, //nolint:staticcheck
   100  			vulnImage}).Build()
   101  
   102  		err := UploadMultiarchImage(multiArch, baseURL, "multi-arch", "multi-arch-tag")
   103  		So(err, ShouldBeNil)
   104  
   105  		// scan
   106  		scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log)
   107  
   108  		ctx := context.Background()
   109  
   110  		err = scanner.UpdateDB(ctx)
   111  		So(err, ShouldBeNil)
   112  
   113  		cveMap, err := scanner.ScanImage(ctx, "multi-arch@"+vulnImage.DigestStr())
   114  		So(err, ShouldBeNil)
   115  		So(cveMap, ShouldContainKey, Vulnerability1ID)
   116  		So(cveMap, ShouldContainKey, Vulnerability2ID)
   117  		So(cveMap, ShouldContainKey, Vulnerability3ID)
   118  
   119  		cveMap, err = scanner.ScanImage(ctx, "multi-arch@"+simpleImage.DigestStr())
   120  		So(err, ShouldBeNil)
   121  		So(cveMap, ShouldBeEmpty)
   122  
   123  		cveMap, err = scanner.ScanImage(ctx, "multi-arch@"+multiArch.DigestStr())
   124  		So(err, ShouldBeNil)
   125  		So(cveMap, ShouldContainKey, Vulnerability1ID)
   126  		So(cveMap, ShouldContainKey, Vulnerability2ID)
   127  		So(cveMap, ShouldContainKey, Vulnerability3ID)
   128  
   129  		cveMap, err = scanner.ScanImage(ctx, "multi-arch:multi-arch-tag")
   130  		So(err, ShouldBeNil)
   131  		So(cveMap, ShouldContainKey, Vulnerability1ID)
   132  		So(cveMap, ShouldContainKey, Vulnerability2ID)
   133  		So(cveMap, ShouldContainKey, Vulnerability3ID)
   134  	})
   135  }
   136  
   137  func TestVulnerableLayer(t *testing.T) {
   138  	Convey("Vulnerable layer", t, func() {
   139  		vulnerableLayer, err := GetLayerWithVulnerability()
   140  		So(err, ShouldBeNil)
   141  
   142  		created, err := time.Parse(time.RFC3339, "2023-03-29T18:19:24Z")
   143  		So(err, ShouldBeNil)
   144  
   145  		config := ispec.Image{
   146  			Created: &created,
   147  			Platform: ispec.Platform{
   148  				Architecture: "amd64",
   149  				OS:           "linux",
   150  			},
   151  			Config: ispec.ImageConfig{
   152  				Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
   153  				Cmd: []string{"/bin/sh"},
   154  			},
   155  			RootFS: ispec.RootFS{
   156  				Type:    "layers",
   157  				DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
   158  			},
   159  		}
   160  
   161  		img := CreateImageWith().
   162  			LayerBlobs([][]byte{vulnerableLayer}).
   163  			ImageConfig(config).
   164  			Build()
   165  
   166  		tempDir := t.TempDir()
   167  
   168  		log := log.NewLogger("debug", "")
   169  		imageStore := local.NewImageStore(tempDir, false, false,
   170  			log, monitoring.NewMetricsServer(false, log), nil, nil)
   171  
   172  		storeController := storage.StoreController{
   173  			DefaultStore: imageStore,
   174  		}
   175  
   176  		err = WriteImageToFileSystem(img, "repo", img.DigestStr(), storeController)
   177  		So(err, ShouldBeNil)
   178  
   179  		params := boltdb.DBParameters{
   180  			RootDir: tempDir,
   181  		}
   182  		boltDriver, err := boltdb.GetBoltDriver(params)
   183  		So(err, ShouldBeNil)
   184  
   185  		metaDB, err := boltdb.New(boltDriver, log)
   186  		So(err, ShouldBeNil)
   187  
   188  		err = meta.ParseStorage(metaDB, storeController, log)
   189  		So(err, ShouldBeNil)
   190  
   191  		scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   192  
   193  		err = scanner.UpdateDB(context.Background())
   194  		So(err, ShouldBeNil)
   195  
   196  		cveMap, err := scanner.ScanImage(context.Background(), "repo@"+img.DigestStr())
   197  		So(err, ShouldBeNil)
   198  		t.Logf("cveMap: %v", cveMap)
   199  		// As of September 17 2023 there are 5 CVEs:
   200  		// CVE-2023-1255, CVE-2023-2650, CVE-2023-2975, CVE-2023-3817, CVE-2023-3446
   201  		// There may be more discovered in the future
   202  		So(len(cveMap), ShouldBeGreaterThanOrEqualTo, 5)
   203  		So(cveMap, ShouldContainKey, "CVE-2023-1255")
   204  		So(cveMap, ShouldContainKey, "CVE-2023-2650")
   205  		So(cveMap, ShouldContainKey, "CVE-2023-2975")
   206  		So(cveMap, ShouldContainKey, "CVE-2023-3817")
   207  		So(cveMap, ShouldContainKey, "CVE-2023-3446")
   208  	})
   209  
   210  	Convey("Vulnerable layer with vulnerability in language-specific file", t, func() {
   211  		vulnerableLayer, err := GetLayerWithLanguageFileVulnerability()
   212  		So(err, ShouldBeNil)
   213  
   214  		created, err := time.Parse(time.RFC3339, "2024-02-15T09:56:01.500079786Z")
   215  		So(err, ShouldBeNil)
   216  
   217  		config := ispec.Image{
   218  			Created: &created,
   219  			Platform: ispec.Platform{
   220  				Architecture: "amd64",
   221  				OS:           "linux",
   222  			},
   223  			Config: ispec.ImageConfig{
   224  				Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
   225  			},
   226  			RootFS: ispec.RootFS{
   227  				Type:    "layers",
   228  				DiffIDs: []godigest.Digest{"sha256:d789b0723f3e6e5064d612eb3c84071cc84a7cf7921d549642252c3295e5f937"},
   229  			},
   230  		}
   231  
   232  		img := CreateImageWith().
   233  			LayerBlobs([][]byte{vulnerableLayer}).
   234  			ImageConfig(config).
   235  			Build()
   236  
   237  		tempDir := t.TempDir()
   238  
   239  		log := log.NewLogger("debug", "")
   240  		imageStore := local.NewImageStore(tempDir, false, false,
   241  			log, monitoring.NewMetricsServer(false, log), nil, nil)
   242  
   243  		storeController := storage.StoreController{
   244  			DefaultStore: imageStore,
   245  		}
   246  
   247  		err = WriteImageToFileSystem(img, "repo", img.DigestStr(), storeController)
   248  		So(err, ShouldBeNil)
   249  
   250  		params := boltdb.DBParameters{
   251  			RootDir: tempDir,
   252  		}
   253  		boltDriver, err := boltdb.GetBoltDriver(params)
   254  		So(err, ShouldBeNil)
   255  
   256  		metaDB, err := boltdb.New(boltDriver, log)
   257  		So(err, ShouldBeNil)
   258  
   259  		err = meta.ParseStorage(metaDB, storeController, log)
   260  		So(err, ShouldBeNil)
   261  
   262  		scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db",
   263  			"ghcr.io/aquasecurity/trivy-java-db", log)
   264  
   265  		err = scanner.UpdateDB(context.Background())
   266  		So(err, ShouldBeNil)
   267  
   268  		cveMap, err := scanner.ScanImage(context.Background(), "repo@"+img.DigestStr())
   269  		So(err, ShouldBeNil)
   270  		t.Logf("cveMap: %v", cveMap)
   271  
   272  		// As of Feb 15 2024, there is 1 CVE in this layer:
   273  		So(len(cveMap), ShouldBeGreaterThanOrEqualTo, 1)
   274  		So(cveMap, ShouldContainKey, "CVE-2016-1000027")
   275  
   276  		cveData := cveMap["CVE-2016-1000027"]
   277  		vulnerablePackages := cveData.PackageList
   278  
   279  		// There is only 1 vulnerable package in this layer
   280  		So(len(vulnerablePackages), ShouldEqual, 1)
   281  		vulnerableSpringWebPackage := vulnerablePackages[0]
   282  		So(vulnerableSpringWebPackage.Name, ShouldEqual, "org.springframework:spring-web")
   283  		So(vulnerableSpringWebPackage.InstalledVersion, ShouldEqual, "5.3.31")
   284  		So(vulnerableSpringWebPackage.FixedVersion, ShouldEqual, "6.0.0")
   285  		So(vulnerableSpringWebPackage.PackagePath, ShouldEqual, "usr/local/artifacts/spring-web-5.3.31.jar")
   286  	})
   287  }
   288  
   289  func TestScannerErrors(t *testing.T) {
   290  	Convey("Errors", t, func() {
   291  		storeController := storage.StoreController{}
   292  		metaDB := mocks.MetaDBMock{}
   293  		log := log.NewLogger("debug", "")
   294  
   295  		Convey("IsImageFormatScannable", func() {
   296  			storeController.DefaultStore = mocks.MockedImageStore{}
   297  			metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   298  				return types.ImageMeta{}, ErrTestError
   299  			}
   300  			scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   301  
   302  			_, err := scanner.IsImageFormatScannable("repo", godigest.FromString("dig").String())
   303  			So(err, ShouldNotBeNil)
   304  		})
   305  		Convey("IsImageMediaScannable", func() {
   306  			storeController.DefaultStore = mocks.MockedImageStore{}
   307  			metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   308  				return types.ImageMeta{}, ErrTestError
   309  			}
   310  			scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   311  
   312  			Convey("Manifest", func() {
   313  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageManifest)
   314  				So(err, ShouldNotBeNil)
   315  			})
   316  			Convey("Index", func() {
   317  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex)
   318  				So(err, ShouldNotBeNil)
   319  			})
   320  			Convey("Index with nil index", func() {
   321  				metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   322  					return types.ImageMeta{}, nil
   323  				}
   324  				scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   325  
   326  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex)
   327  				So(err, ShouldNotBeNil)
   328  			})
   329  			Convey("Index with good index", func() {
   330  				metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   331  					return types.ImageMeta{
   332  						Index: &ispec.Index{
   333  							Manifests: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
   334  						},
   335  						Manifests: []types.ManifestMeta{{Manifest: ispec.Manifest{
   336  							Layers: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
   337  						}}},
   338  					}, nil
   339  				}
   340  				scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   341  
   342  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex)
   343  				So(err, ShouldBeNil)
   344  			})
   345  		})
   346  		Convey("ScanImage", func() {
   347  			storeController.DefaultStore = mocks.MockedImageStore{}
   348  			metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   349  				return types.ImageMeta{}, ErrTestError
   350  			}
   351  
   352  			scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   353  
   354  			_, err := scanner.ScanImage(context.Background(), "image@"+godigest.FromString("digest").String())
   355  			So(err, ShouldNotBeNil)
   356  		})
   357  	})
   358  }