zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/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.io/zot/pkg/api"
    17  	"zotregistry.io/zot/pkg/api/config"
    18  	extconf "zotregistry.io/zot/pkg/extensions/config"
    19  	"zotregistry.io/zot/pkg/extensions/monitoring"
    20  	"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
    21  	"zotregistry.io/zot/pkg/log"
    22  	"zotregistry.io/zot/pkg/meta"
    23  	"zotregistry.io/zot/pkg/meta/boltdb"
    24  	"zotregistry.io/zot/pkg/meta/types"
    25  	"zotregistry.io/zot/pkg/storage"
    26  	"zotregistry.io/zot/pkg/storage/local"
    27  	. "zotregistry.io/zot/pkg/test/common"
    28  	"zotregistry.io/zot/pkg/test/deprecated"
    29  	. "zotregistry.io/zot/pkg/test/image-utils"
    30  	"zotregistry.io/zot/pkg/test/mocks"
    31  )
    32  
    33  var ErrTestError = errors.New("test error")
    34  
    35  func TestScanBigTestFile(t *testing.T) {
    36  	Convey("Scan zot-test", t, func() {
    37  		projRootDir, err := GetProjectRootDir()
    38  		So(err, ShouldBeNil)
    39  
    40  		testImage := filepath.Join(projRootDir, "test/data/zot-test")
    41  
    42  		tempDir := t.TempDir()
    43  		port := GetFreePort()
    44  		conf := config.New()
    45  		conf.HTTP.Port = port
    46  		defaultVal := true
    47  		conf.Storage.RootDirectory = tempDir
    48  		conf.Extensions = &extconf.ExtensionConfig{
    49  			Search: &extconf.SearchConfig{
    50  				BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
    51  			},
    52  		}
    53  		ctlr := api.NewController(conf)
    54  		So(ctlr, ShouldNotBeNil)
    55  
    56  		err = CopyFiles(testImage, filepath.Join(tempDir, "zot-test"))
    57  		So(err, ShouldBeNil)
    58  
    59  		cm := NewControllerManager(ctlr)
    60  		cm.StartAndWait(port)
    61  		defer cm.StopServer()
    62  		// scan
    63  		scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log)
    64  
    65  		err = scanner.UpdateDB(context.Background())
    66  		So(err, ShouldBeNil)
    67  
    68  		cveMap, err := scanner.ScanImage(context.Background(), "zot-test:0.0.1")
    69  		So(err, ShouldBeNil)
    70  		So(cveMap, ShouldNotBeNil)
    71  	})
    72  }
    73  
    74  func TestScanningByDigest(t *testing.T) {
    75  	Convey("Scan the individual manifests inside an index", t, func() {
    76  		// start server
    77  		tempDir := t.TempDir()
    78  		port := GetFreePort()
    79  		baseURL := GetBaseURL(port)
    80  		conf := config.New()
    81  		conf.HTTP.Port = port
    82  		defaultVal := true
    83  		conf.Storage.RootDirectory = tempDir
    84  		conf.Extensions = &extconf.ExtensionConfig{
    85  			Search: &extconf.SearchConfig{
    86  				BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
    87  			},
    88  		}
    89  		ctlr := api.NewController(conf)
    90  		So(ctlr, ShouldNotBeNil)
    91  
    92  		cm := NewControllerManager(ctlr)
    93  		cm.StartAndWait(port)
    94  		defer cm.StopServer()
    95  		// push index with 2 manifests: one with vulns and one without
    96  		vulnImage := CreateDefaultVulnerableImage()
    97  
    98  		simpleImage := CreateRandomImage()
    99  
   100  		multiArch := deprecated.GetMultiarchImageForImages([]Image{simpleImage, //nolint:staticcheck
   101  			vulnImage})
   102  
   103  		err := UploadMultiarchImage(multiArch, baseURL, "multi-arch", "multi-arch-tag")
   104  		So(err, ShouldBeNil)
   105  
   106  		// scan
   107  		scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log)
   108  
   109  		ctx := context.Background()
   110  
   111  		err = scanner.UpdateDB(ctx)
   112  		So(err, ShouldBeNil)
   113  
   114  		cveMap, err := scanner.ScanImage(ctx, "multi-arch@"+vulnImage.DigestStr())
   115  		So(err, ShouldBeNil)
   116  		So(cveMap, ShouldContainKey, Vulnerability1ID)
   117  		So(cveMap, ShouldContainKey, Vulnerability2ID)
   118  		So(cveMap, ShouldContainKey, Vulnerability3ID)
   119  
   120  		cveMap, err = scanner.ScanImage(ctx, "multi-arch@"+simpleImage.DigestStr())
   121  		So(err, ShouldBeNil)
   122  		So(cveMap, ShouldBeEmpty)
   123  
   124  		cveMap, err = scanner.ScanImage(ctx, "multi-arch@"+multiArch.DigestStr())
   125  		So(err, ShouldBeNil)
   126  		So(cveMap, ShouldContainKey, Vulnerability1ID)
   127  		So(cveMap, ShouldContainKey, Vulnerability2ID)
   128  		So(cveMap, ShouldContainKey, Vulnerability3ID)
   129  
   130  		cveMap, err = scanner.ScanImage(ctx, "multi-arch:multi-arch-tag")
   131  		So(err, ShouldBeNil)
   132  		So(cveMap, ShouldContainKey, Vulnerability1ID)
   133  		So(cveMap, ShouldContainKey, Vulnerability2ID)
   134  		So(cveMap, ShouldContainKey, Vulnerability3ID)
   135  	})
   136  }
   137  
   138  func TestVulnerableLayer(t *testing.T) {
   139  	Convey("Vulnerable layer", t, func() {
   140  		vulnerableLayer, err := GetLayerWithVulnerability()
   141  		So(err, ShouldBeNil)
   142  
   143  		created, err := time.Parse(time.RFC3339, "2023-03-29T18:19:24Z")
   144  		So(err, ShouldBeNil)
   145  
   146  		config := ispec.Image{
   147  			Created: &created,
   148  			Platform: ispec.Platform{
   149  				Architecture: "amd64",
   150  				OS:           "linux",
   151  			},
   152  			Config: ispec.ImageConfig{
   153  				Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
   154  				Cmd: []string{"/bin/sh"},
   155  			},
   156  			RootFS: ispec.RootFS{
   157  				Type:    "layers",
   158  				DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
   159  			},
   160  		}
   161  
   162  		img := CreateImageWith().
   163  			LayerBlobs([][]byte{vulnerableLayer}).
   164  			ImageConfig(config).
   165  			Build()
   166  
   167  		tempDir := t.TempDir()
   168  
   169  		log := log.NewLogger("debug", "")
   170  		imageStore := local.NewImageStore(tempDir, false, false,
   171  			log, monitoring.NewMetricsServer(false, log), nil, nil)
   172  
   173  		storeController := storage.StoreController{
   174  			DefaultStore: imageStore,
   175  		}
   176  
   177  		err = WriteImageToFileSystem(img, "repo", img.DigestStr(), storeController)
   178  		So(err, ShouldBeNil)
   179  
   180  		params := boltdb.DBParameters{
   181  			RootDir: tempDir,
   182  		}
   183  		boltDriver, err := boltdb.GetBoltDriver(params)
   184  		So(err, ShouldBeNil)
   185  
   186  		metaDB, err := boltdb.New(boltDriver, log)
   187  		So(err, ShouldBeNil)
   188  
   189  		err = meta.ParseStorage(metaDB, storeController, log)
   190  		So(err, ShouldBeNil)
   191  
   192  		scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   193  
   194  		err = scanner.UpdateDB(context.Background())
   195  		So(err, ShouldBeNil)
   196  
   197  		cveMap, err := scanner.ScanImage(context.Background(), "repo@"+img.DigestStr())
   198  		So(err, ShouldBeNil)
   199  		t.Logf("cveMap: %v", cveMap)
   200  		// As of September 17 2023 there are 5 CVEs:
   201  		// CVE-2023-1255, CVE-2023-2650, CVE-2023-2975, CVE-2023-3817, CVE-2023-3446
   202  		// There may be more discovered in the future
   203  		So(len(cveMap), ShouldBeGreaterThanOrEqualTo, 5)
   204  		So(cveMap, ShouldContainKey, "CVE-2023-1255")
   205  		So(cveMap, ShouldContainKey, "CVE-2023-2650")
   206  		So(cveMap, ShouldContainKey, "CVE-2023-2975")
   207  		So(cveMap, ShouldContainKey, "CVE-2023-3817")
   208  		So(cveMap, ShouldContainKey, "CVE-2023-3446")
   209  	})
   210  }
   211  
   212  func TestScannerErrors(t *testing.T) {
   213  	Convey("Errors", t, func() {
   214  		storeController := storage.StoreController{}
   215  		metaDB := mocks.MetaDBMock{}
   216  		log := log.NewLogger("debug", "")
   217  
   218  		Convey("IsImageFormatScannable", func() {
   219  			storeController.DefaultStore = mocks.MockedImageStore{}
   220  			metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   221  				return types.ImageMeta{}, ErrTestError
   222  			}
   223  			scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   224  
   225  			_, err := scanner.IsImageFormatScannable("repo", godigest.FromString("dig").String())
   226  			So(err, ShouldNotBeNil)
   227  		})
   228  		Convey("IsImageMediaScannable", func() {
   229  			storeController.DefaultStore = mocks.MockedImageStore{}
   230  			metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   231  				return types.ImageMeta{}, ErrTestError
   232  			}
   233  			scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   234  
   235  			Convey("Manifest", func() {
   236  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageManifest)
   237  				So(err, ShouldNotBeNil)
   238  			})
   239  			Convey("Index", func() {
   240  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex)
   241  				So(err, ShouldNotBeNil)
   242  			})
   243  			Convey("Index with nil index", func() {
   244  				metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   245  					return types.ImageMeta{}, nil
   246  				}
   247  				scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   248  
   249  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex)
   250  				So(err, ShouldNotBeNil)
   251  			})
   252  			Convey("Index with good index", func() {
   253  				metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   254  					return types.ImageMeta{
   255  						Index: &ispec.Index{
   256  							Manifests: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
   257  						},
   258  						Manifests: []types.ManifestMeta{{Manifest: ispec.Manifest{
   259  							Layers: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
   260  						}}},
   261  					}, nil
   262  				}
   263  				scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   264  
   265  				_, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex)
   266  				So(err, ShouldBeNil)
   267  			})
   268  		})
   269  		Convey("ScanImage", func() {
   270  			storeController.DefaultStore = mocks.MockedImageStore{}
   271  			metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) {
   272  				return types.ImageMeta{}, ErrTestError
   273  			}
   274  
   275  			scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   276  
   277  			_, err := scanner.ScanImage(context.Background(), "image@"+godigest.FromString("digest").String())
   278  			So(err, ShouldNotBeNil)
   279  		})
   280  	})
   281  }