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 }