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 }