zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/cli/client/cve_cmd_test.go (about) 1 //go:build search 2 // +build search 3 4 package client_test 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "os" 14 "path" 15 "regexp" 16 "strings" 17 "testing" 18 "time" 19 20 regTypes "github.com/google/go-containerregistry/pkg/v1/types" 21 godigest "github.com/opencontainers/go-digest" 22 ispec "github.com/opencontainers/image-spec/specs-go/v1" 23 . "github.com/smartystreets/goconvey/convey" 24 25 zerr "zotregistry.io/zot/errors" 26 "zotregistry.io/zot/pkg/api" 27 "zotregistry.io/zot/pkg/api/config" 28 "zotregistry.io/zot/pkg/cli/client" 29 zcommon "zotregistry.io/zot/pkg/common" 30 extconf "zotregistry.io/zot/pkg/extensions/config" 31 "zotregistry.io/zot/pkg/extensions/monitoring" 32 cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" 33 cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model" 34 "zotregistry.io/zot/pkg/log" 35 mTypes "zotregistry.io/zot/pkg/meta/types" 36 "zotregistry.io/zot/pkg/storage" 37 "zotregistry.io/zot/pkg/storage/local" 38 test "zotregistry.io/zot/pkg/test/common" 39 . "zotregistry.io/zot/pkg/test/image-utils" 40 "zotregistry.io/zot/pkg/test/mocks" 41 ociutils "zotregistry.io/zot/pkg/test/oci-utils" 42 ) 43 44 func TestNegativeServerResponse(t *testing.T) { 45 Convey("Test from real server without search endpoint", t, func() { 46 port := test.GetFreePort() 47 url := test.GetBaseURL(port) 48 conf := config.New() 49 conf.HTTP.Port = port 50 51 dir := t.TempDir() 52 53 srcStorageCtlr := ociutils.GetDefaultStoreController(dir, log.NewLogger("debug", "")) 54 err := WriteImageToFileSystem(CreateDefaultVulnerableImage(), "zot-cve-test", "0.0.1", srcStorageCtlr) 55 So(err, ShouldBeNil) 56 57 conf.Storage.RootDirectory = dir 58 trivyConfig := &extconf.TrivyConfig{ 59 DBRepository: "ghcr.io/project-zot/trivy-db", 60 } 61 cveConfig := &extconf.CVEConfig{ 62 UpdateInterval: 2, 63 Trivy: trivyConfig, 64 } 65 defaultVal := false 66 searchConfig := &extconf.SearchConfig{ 67 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 68 CVE: cveConfig, 69 } 70 conf.Extensions = &extconf.ExtensionConfig{ 71 Search: searchConfig, 72 } 73 74 logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") 75 if err != nil { 76 panic(err) 77 } 78 79 logPath := logFile.Name() 80 defer os.Remove(logPath) 81 82 writers := io.MultiWriter(os.Stdout, logFile) 83 84 ctlr := api.NewController(conf) 85 ctlr.Log.Logger = ctlr.Log.Output(writers) 86 87 cm := test.NewControllerManager(ctlr) 88 cm.StartAndWait(conf.HTTP.Port) 89 defer cm.StopServer() 90 91 _, err = test.ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second) 92 if err != nil { 93 panic(err) 94 } 95 96 Convey("Status Code Not Found", func() { 97 args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"} 98 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 99 defer os.Remove(configPath) 100 cveCmd := client.NewCVECommand(client.NewSearchService()) 101 buff := bytes.NewBufferString("") 102 cveCmd.SetOut(buff) 103 cveCmd.SetErr(buff) 104 cveCmd.SetArgs(args) 105 err = cveCmd.Execute() 106 So(err, ShouldNotBeNil) 107 So(err.Error(), ShouldContainSubstring, zerr.ErrExtensionNotEnabled.Error()) 108 }) 109 }) 110 111 Convey("Test non-existing manifest blob", t, func() { 112 port := test.GetFreePort() 113 url := test.GetBaseURL(port) 114 conf := config.New() 115 conf.HTTP.Port = port 116 117 dir := t.TempDir() 118 119 imageStore := local.NewImageStore(dir, false, false, 120 log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil) 121 122 storeController := storage.StoreController{ 123 DefaultStore: imageStore, 124 } 125 126 image := CreateRandomImage() 127 128 err := WriteImageToFileSystem(image, "zot-cve-test", "0.0.1", storeController) 129 So(err, ShouldBeNil) 130 131 err = os.RemoveAll(path.Join(dir, "zot-cve-test/blobs")) 132 if err != nil { 133 panic(err) 134 } 135 136 conf.Storage.RootDirectory = dir 137 trivyConfig := &extconf.TrivyConfig{ 138 DBRepository: "ghcr.io/project-zot/trivy-db", 139 } 140 cveConfig := &extconf.CVEConfig{ 141 UpdateInterval: 2, 142 Trivy: trivyConfig, 143 } 144 defaultVal := true 145 searchConfig := &extconf.SearchConfig{ 146 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 147 CVE: cveConfig, 148 } 149 conf.Extensions = &extconf.ExtensionConfig{ 150 Search: searchConfig, 151 } 152 153 logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") 154 if err != nil { 155 panic(err) 156 } 157 158 logPath := logFile.Name() 159 defer os.Remove(logPath) 160 161 writers := io.MultiWriter(os.Stdout, logFile) 162 163 ctlr := api.NewController(conf) 164 ctlr.Log.Logger = ctlr.Log.Output(writers) 165 166 ctx := context.Background() 167 168 if err := ctlr.Init(ctx); err != nil { 169 panic(err) 170 } 171 172 ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) 173 174 go func() { 175 if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { 176 panic(err) 177 } 178 }() 179 180 defer ctlr.Shutdown() 181 182 test.WaitTillServerReady(url) 183 184 _, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second) 185 if err != nil { 186 panic(err) 187 } 188 189 args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"} 190 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 191 defer os.Remove(configPath) 192 cveCmd := client.NewCVECommand(client.NewSearchService()) 193 buff := bytes.NewBufferString("") 194 cveCmd.SetOut(buff) 195 cveCmd.SetErr(buff) 196 cveCmd.SetArgs(args) 197 err = cveCmd.Execute() 198 So(err, ShouldNotBeNil) 199 }) 200 } 201 202 //nolint:dupl 203 func TestServerCVEResponse(t *testing.T) { 204 port := test.GetFreePort() 205 url := test.GetBaseURL(port) 206 conf := config.New() 207 conf.HTTP.Port = port 208 209 dir := t.TempDir() 210 211 conf.Storage.RootDirectory = dir 212 trivyConfig := &extconf.TrivyConfig{ 213 DBRepository: "ghcr.io/project-zot/trivy-db", 214 } 215 cveConfig := &extconf.CVEConfig{ 216 UpdateInterval: 2, 217 Trivy: trivyConfig, 218 } 219 defaultVal := true 220 searchConfig := &extconf.SearchConfig{ 221 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 222 CVE: cveConfig, 223 } 224 conf.Extensions = &extconf.ExtensionConfig{ 225 Search: searchConfig, 226 } 227 228 logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") 229 if err != nil { 230 panic(err) 231 } 232 233 logPath := logFile.Name() 234 defer os.Remove(logPath) 235 236 writers := io.MultiWriter(os.Stdout, logFile) 237 238 ctlr := api.NewController(conf) 239 ctlr.Log.Logger = ctlr.Log.Output(writers) 240 241 ctx := context.Background() 242 243 if err := ctlr.Init(ctx); err != nil { 244 panic(err) 245 } 246 247 ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) 248 249 go func() { 250 if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { 251 panic(err) 252 } 253 }() 254 255 defer ctlr.Shutdown() 256 257 test.WaitTillServerReady(url) 258 259 image := CreateDefaultImage() 260 261 err = UploadImage(image, url, "zot-cve-test", "0.0.1") 262 if err != nil { 263 panic(err) 264 } 265 266 _, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second) 267 if err != nil { 268 panic(err) 269 } 270 271 Convey("Test CVE by image name - GQL - positive", t, func() { 272 args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"} 273 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 274 defer os.Remove(configPath) 275 cveCmd := client.NewCVECommand(client.NewSearchService()) 276 buff := bytes.NewBufferString("") 277 cveCmd.SetOut(buff) 278 cveCmd.SetErr(buff) 279 cveCmd.SetArgs(args) 280 err = cveCmd.Execute() 281 space := regexp.MustCompile(`\s+`) 282 str := space.ReplaceAllString(buff.String(), " ") 283 str = strings.TrimSpace(str) 284 So(err, ShouldBeNil) 285 So(str, ShouldContainSubstring, "ID SEVERITY TITLE") 286 So(str, ShouldContainSubstring, "CVE") 287 }) 288 289 Convey("Test CVE by image name - GQL - search CVE by title in results", t, func() { 290 args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-C1", "--config", "cvetest"} 291 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 292 defer os.Remove(configPath) 293 cveCmd := client.NewCVECommand(client.NewSearchService()) 294 buff := bytes.NewBufferString("") 295 cveCmd.SetOut(buff) 296 cveCmd.SetErr(buff) 297 cveCmd.SetArgs(args) 298 err = cveCmd.Execute() 299 space := regexp.MustCompile(`\s+`) 300 str := space.ReplaceAllString(buff.String(), " ") 301 str = strings.TrimSpace(str) 302 So(err, ShouldBeNil) 303 So(str, ShouldContainSubstring, "ID SEVERITY TITLE") 304 So(str, ShouldContainSubstring, "CVE-C1") 305 So(str, ShouldNotContainSubstring, "CVE-2") 306 }) 307 308 Convey("Test CVE by image name - GQL - search CVE by id in results", t, func() { 309 args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-2", "--config", "cvetest"} 310 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 311 defer os.Remove(configPath) 312 cveCmd := client.NewCVECommand(client.NewSearchService()) 313 buff := bytes.NewBufferString("") 314 cveCmd.SetOut(buff) 315 cveCmd.SetErr(buff) 316 cveCmd.SetArgs(args) 317 err = cveCmd.Execute() 318 space := regexp.MustCompile(`\s+`) 319 str := space.ReplaceAllString(buff.String(), " ") 320 str = strings.TrimSpace(str) 321 So(err, ShouldBeNil) 322 So(str, ShouldContainSubstring, "ID SEVERITY TITLE") 323 So(str, ShouldContainSubstring, "CVE-2") 324 So(str, ShouldNotContainSubstring, "CVE-1") 325 }) 326 327 Convey("Test CVE by image name - GQL - search nonexistent CVE", t, func() { 328 args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-100", "--config", "cvetest"} 329 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 330 defer os.Remove(configPath) 331 cveCmd := client.NewCVECommand(client.NewSearchService()) 332 buff := bytes.NewBufferString("") 333 cveCmd.SetOut(buff) 334 cveCmd.SetErr(buff) 335 cveCmd.SetArgs(args) 336 err = cveCmd.Execute() 337 space := regexp.MustCompile(`\s+`) 338 str := space.ReplaceAllString(buff.String(), " ") 339 str = strings.TrimSpace(str) 340 So(err, ShouldBeNil) 341 So(str, ShouldContainSubstring, "No CVEs found for image") 342 }) 343 344 Convey("Test CVE by image name - GQL - invalid image", t, func() { 345 args := []string{"list", "invalid:0.0.1", "--config", "cvetest"} 346 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 347 defer os.Remove(configPath) 348 cveCmd := client.NewCVECommand(client.NewSearchService()) 349 buff := bytes.NewBufferString("") 350 cveCmd.SetOut(buff) 351 cveCmd.SetErr(buff) 352 cveCmd.SetArgs(args) 353 err = cveCmd.Execute() 354 So(err, ShouldNotBeNil) 355 }) 356 357 Convey("Test CVE by image name - GQL - invalid image name and tag", t, func() { 358 args := []string{"list", "invalid:", "--config", "cvetest"} 359 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 360 defer os.Remove(configPath) 361 cveCmd := client.NewCVECommand(client.NewSearchService()) 362 buff := bytes.NewBufferString("") 363 cveCmd.SetOut(buff) 364 cveCmd.SetErr(buff) 365 cveCmd.SetArgs(args) 366 err = cveCmd.Execute() 367 So(err, ShouldNotBeNil) 368 }) 369 370 Convey("Test CVE by image name - GQL - invalid output format", t, func() { 371 args := []string{"list", "zot-cve-test:0.0.1", "-f", "random", "--config", "cvetest"} 372 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 373 defer os.Remove(configPath) 374 cveCmd := client.NewCVECommand(client.NewSearchService()) 375 buff := bytes.NewBufferString("") 376 cveCmd.SetOut(buff) 377 cveCmd.SetErr(buff) 378 cveCmd.SetArgs(args) 379 err = cveCmd.Execute() 380 So(err, ShouldNotBeNil) 381 So(buff.String(), ShouldContainSubstring, "invalid output format") 382 }) 383 384 Convey("Test images by CVE ID - GQL - positive", t, func() { 385 args := []string{"affected", "CVE-2019-9923", "--config", "cvetest"} 386 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 387 defer os.Remove(configPath) 388 cveCmd := client.NewCVECommand(client.NewSearchService()) 389 buff := bytes.NewBufferString("") 390 cveCmd.SetOut(buff) 391 cveCmd.SetErr(buff) 392 cveCmd.SetArgs(args) 393 err := cveCmd.Execute() 394 space := regexp.MustCompile(`\s+`) 395 str := space.ReplaceAllString(buff.String(), " ") 396 str = strings.TrimSpace(str) 397 So(err, ShouldBeNil) 398 So(str, ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B") 399 }) 400 401 Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() { 402 args := []string{"affected", "CVE-invalid", "--config", "cvetest"} 403 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 404 defer os.Remove(configPath) 405 cveCmd := client.NewCVECommand(client.NewSearchService()) 406 buff := bytes.NewBufferString("") 407 cveCmd.SetOut(buff) 408 cveCmd.SetErr(buff) 409 cveCmd.SetArgs(args) 410 err := cveCmd.Execute() 411 space := regexp.MustCompile(`\s+`) 412 str := space.ReplaceAllString(buff.String(), " ") 413 str = strings.TrimSpace(str) 414 So(err, ShouldBeNil) 415 So(str, ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") 416 }) 417 418 Convey("Test images by CVE ID - GQL - invalid output format", t, func() { 419 args := []string{"affected", "CVE-2019-9923", "-f", "random", "--config", "cvetest"} 420 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 421 defer os.Remove(configPath) 422 cveCmd := client.NewCVECommand(client.NewSearchService()) 423 buff := bytes.NewBufferString("") 424 cveCmd.SetOut(buff) 425 cveCmd.SetErr(buff) 426 cveCmd.SetArgs(args) 427 err = cveCmd.Execute() 428 So(err, ShouldNotBeNil) 429 So(buff.String(), ShouldContainSubstring, "invalid output format") 430 }) 431 432 Convey("Test fixed tags by image name and CVE ID - GQL - positive", t, func() { 433 args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"} 434 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 435 defer os.Remove(configPath) 436 cveCmd := client.NewCVECommand(client.NewSearchService()) 437 buff := bytes.NewBufferString("") 438 cveCmd.SetOut(buff) 439 cveCmd.SetErr(buff) 440 cveCmd.SetArgs(args) 441 err := cveCmd.Execute() 442 space := regexp.MustCompile(`\s+`) 443 str := space.ReplaceAllString(buff.String(), " ") 444 str = strings.TrimSpace(str) 445 So(err, ShouldBeNil) 446 So(str, ShouldEqual, "") 447 }) 448 449 Convey("Test fixed tags by image name and CVE ID - GQL - random cve", t, func() { 450 args := []string{"fixed", "zot-cve-test", "random", "--config", "cvetest"} 451 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 452 defer os.Remove(configPath) 453 cveCmd := client.NewCVECommand(client.NewSearchService()) 454 buff := bytes.NewBufferString("") 455 cveCmd.SetOut(buff) 456 cveCmd.SetErr(buff) 457 cveCmd.SetArgs(args) 458 err := cveCmd.Execute() 459 space := regexp.MustCompile(`\s+`) 460 str := space.ReplaceAllString(buff.String(), " ") 461 str = strings.TrimSpace(str) 462 So(err, ShouldBeNil) 463 So(strings.TrimSpace(str), ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") 464 }) 465 466 Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() { 467 args := []string{"fixed", "zot-cv-test", "CVE-2019-20807", "--config", "cvetest"} 468 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 469 defer os.Remove(configPath) 470 cveCmd := client.NewCVECommand(client.NewSearchService()) 471 buff := bytes.NewBufferString("") 472 cveCmd.SetOut(buff) 473 cveCmd.SetErr(buff) 474 cveCmd.SetArgs(args) 475 err := cveCmd.Execute() 476 space := regexp.MustCompile(`\s+`) 477 str := space.ReplaceAllString(buff.String(), " ") 478 str = strings.TrimSpace(str) 479 So(err, ShouldNotBeNil) 480 So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") 481 }) 482 483 Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() { 484 args := []string{"fixed", "zot-cv-test:tag", "CVE-2019-20807", "--config", "cvetest"} 485 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 486 defer os.Remove(configPath) 487 cveCmd := client.NewCVECommand(client.NewSearchService()) 488 buff := bytes.NewBufferString("") 489 cveCmd.SetOut(buff) 490 cveCmd.SetErr(buff) 491 cveCmd.SetArgs(args) 492 err := cveCmd.Execute() 493 space := regexp.MustCompile(`\s+`) 494 str := space.ReplaceAllString(buff.String(), " ") 495 str = strings.TrimSpace(str) 496 So(err, ShouldNotBeNil) 497 So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") 498 }) 499 500 Convey("Test CVE by name and CVE ID - GQL - positive", t, func() { 501 args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "--config", "cvetest"} 502 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 503 defer os.Remove(configPath) 504 cveCmd := client.NewCVECommand(client.NewSearchService()) 505 buff := bytes.NewBufferString("") 506 cveCmd.SetOut(buff) 507 cveCmd.SetErr(buff) 508 cveCmd.SetArgs(args) 509 err := cveCmd.Execute() 510 space := regexp.MustCompile(`\s+`) 511 str := space.ReplaceAllString(buff.String(), " ") 512 So(err, ShouldBeNil) 513 So(strings.TrimSpace(str), ShouldEqual, 514 "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B") 515 }) 516 517 Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() { 518 args := []string{"affected", "CVE-20807", "--repo", "test", "--config", "cvetest"} 519 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 520 defer os.Remove(configPath) 521 cveCmd := client.NewCVECommand(client.NewSearchService()) 522 buff := bytes.NewBufferString("") 523 cveCmd.SetOut(buff) 524 cveCmd.SetErr(buff) 525 cveCmd.SetArgs(args) 526 err := cveCmd.Execute() 527 space := regexp.MustCompile(`\s+`) 528 str := space.ReplaceAllString(buff.String(), " ") 529 So(err, ShouldBeNil) 530 So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH SIGNED SIZE") 531 }) 532 533 Convey("Test CVE by name and CVE ID - GQL - invalid output format", t, func() { 534 args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "-f", "random", "--config", "cvetest"} 535 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) 536 defer os.Remove(configPath) 537 cveCmd := client.NewCVECommand(client.NewSearchService()) 538 buff := bytes.NewBufferString("") 539 cveCmd.SetOut(buff) 540 cveCmd.SetErr(buff) 541 cveCmd.SetArgs(args) 542 err = cveCmd.Execute() 543 So(err, ShouldNotBeNil) 544 So(buff.String(), ShouldContainSubstring, "invalid output format") 545 }) 546 } 547 548 func TestCVESort(t *testing.T) { 549 rootDir := t.TempDir() 550 port := test.GetFreePort() 551 baseURL := test.GetBaseURL(port) 552 conf := config.New() 553 conf.HTTP.Port = port 554 555 defaultVal := true 556 conf.Extensions = &extconf.ExtensionConfig{ 557 Search: &extconf.SearchConfig{ 558 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 559 CVE: &extconf.CVEConfig{ 560 UpdateInterval: 2, 561 Trivy: &extconf.TrivyConfig{ 562 DBRepository: "ghcr.io/project-zot/trivy-db", 563 }, 564 }, 565 }, 566 } 567 ctlr := api.NewController(conf) 568 ctlr.Config.Storage.RootDirectory = rootDir 569 570 image1 := CreateRandomImage() 571 572 storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log) 573 574 err := WriteImageToFileSystem(image1, "repo", "tag", storeController) 575 if err != nil { 576 t.FailNow() 577 } 578 579 ctx := context.Background() 580 581 if err := ctlr.Init(ctx); err != nil { 582 panic(err) 583 } 584 585 ctlr.CveScanner = mocks.CveScannerMock{ 586 ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { 587 return map[string]cvemodel.CVE{ 588 "CVE-2023-1255": { 589 ID: "CVE-2023-1255", 590 Severity: "LOW", 591 Title: "Input buffer over-read in AES-XTS implementation and testing", 592 }, 593 "CVE-2023-2650": { 594 ID: "CVE-2023-2650", 595 Severity: "MEDIUM", 596 Title: "Possible DoS translating ASN.1 object identifier and executer", 597 }, 598 "CVE-2023-2975": { 599 ID: "CVE-2023-2975", 600 Severity: "HIGH", 601 Title: "AES-SIV cipher implementation contains a bug that can break", 602 }, 603 "CVE-2023-3446": { 604 ID: "CVE-2023-3446", 605 Severity: "CRITICAL", 606 Title: "Excessive time spent checking DH keys and parenthesis", 607 }, 608 "CVE-2023-3817": { 609 ID: "CVE-2023-3817", 610 Severity: "MEDIUM", 611 Title: "Excessive time spent checking DH q parameter and arguments", 612 }, 613 }, nil 614 }, 615 } 616 617 go func() { 618 if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { 619 panic(err) 620 } 621 }() 622 623 defer ctlr.Shutdown() 624 625 test.WaitTillServerReady(baseURL) 626 627 space := regexp.MustCompile(`\s+`) 628 629 Convey("test sorting", t, func() { 630 args := []string{"list", "repo:tag", "--sort-by", "severity", "--url", baseURL} 631 cmd := client.NewCVECommand(client.NewSearchService()) 632 buff := bytes.NewBufferString("") 633 cmd.SetOut(buff) 634 cmd.SetErr(buff) 635 cmd.SetArgs(args) 636 err := cmd.Execute() 637 So(err, ShouldBeNil) 638 str := space.ReplaceAllString(buff.String(), " ") 639 actual := strings.TrimSpace(str) 640 So(actual, ShouldResemble, 641 "ID SEVERITY TITLE "+ 642 "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ 643 "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ 644 "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ 645 "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+ 646 "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...") 647 648 args = []string{"list", "repo:tag", "--sort-by", "alpha-asc", "--url", baseURL} 649 cmd = client.NewCVECommand(client.NewSearchService()) 650 buff = bytes.NewBufferString("") 651 cmd.SetOut(buff) 652 cmd.SetErr(buff) 653 cmd.SetArgs(args) 654 err = cmd.Execute() 655 So(err, ShouldBeNil) 656 str = space.ReplaceAllString(buff.String(), " ") 657 actual = strings.TrimSpace(str) 658 So(actual, ShouldResemble, 659 "ID SEVERITY TITLE "+ 660 "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat... "+ 661 "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ 662 "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ 663 "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ 664 "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ...") 665 666 args = []string{"list", "repo:tag", "--sort-by", "alpha-dsc", "--url", baseURL} 667 cmd = client.NewCVECommand(client.NewSearchService()) 668 buff = bytes.NewBufferString("") 669 cmd.SetOut(buff) 670 cmd.SetErr(buff) 671 cmd.SetArgs(args) 672 err = cmd.Execute() 673 So(err, ShouldBeNil) 674 str = space.ReplaceAllString(buff.String(), " ") 675 actual = strings.TrimSpace(str) 676 So(actual, ShouldResemble, 677 "ID SEVERITY TITLE "+ 678 "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+ 679 "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ 680 "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ 681 "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ 682 "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...") 683 }) 684 } 685 686 func getMockCveScanner(metaDB mTypes.MetaDB) cveinfo.Scanner { 687 // MetaDB loaded with initial data now mock the scanner 688 // Setup test CVE data in mock scanner 689 scanner := mocks.CveScannerMock{ 690 ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { 691 if strings.Contains(image, "zot-cve-test@sha256:db573b01") || 692 image == "zot-cve-test:0.0.1" { 693 return map[string]cvemodel.CVE{ 694 "CVE-1": { 695 ID: "CVE-1", 696 Severity: "CRITICAL", 697 Title: "Title for CVE-C1", 698 Description: "Description of CVE-1", 699 }, 700 "CVE-2019-9923": { 701 ID: "CVE-2019-9923", 702 Severity: "HIGH", 703 Title: "Title for CVE-2", 704 Description: "Description of CVE-2", 705 }, 706 "CVE-3": { 707 ID: "CVE-3", 708 Severity: "MEDIUM", 709 Title: "Title for CVE-3", 710 Description: "Description of CVE-3", 711 }, 712 "CVE-4": { 713 ID: "CVE-4", 714 Severity: "LOW", 715 Title: "Title for CVE-4", 716 Description: "Description of CVE-4", 717 }, 718 "CVE-5": { 719 ID: "CVE-5", 720 Severity: "UNKNOWN", 721 Title: "Title for CVE-5", 722 Description: "Description of CVE-5", 723 }, 724 }, nil 725 } 726 727 // By default the image has no vulnerabilities 728 return map[string]cvemodel.CVE{}, nil 729 }, 730 IsImageFormatScannableFn: func(repo string, reference string) (bool, error) { 731 // Almost same logic compared to actual Trivy specific implementation 732 imageDir := repo 733 inputTag := reference 734 735 repoMeta, err := metaDB.GetRepoMeta(context.Background(), imageDir) 736 if err != nil { 737 return false, err 738 } 739 740 manifestDigestStr := reference 741 742 if zcommon.IsTag(reference) { 743 var ok bool 744 745 descriptor, ok := repoMeta.Tags[inputTag] 746 if !ok { 747 return false, zerr.ErrTagMetaNotFound 748 } 749 750 manifestDigestStr = descriptor.Digest 751 } 752 753 manifestDigest, err := godigest.Parse(manifestDigestStr) 754 if err != nil { 755 return false, err 756 } 757 758 manifestData, err := metaDB.GetImageMeta(manifestDigest) 759 if err != nil { 760 return false, err 761 } 762 763 for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers { 764 switch imageLayer.MediaType { 765 case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer): 766 767 return true, nil 768 default: 769 770 return false, zerr.ErrScanNotSupported 771 } 772 } 773 774 return false, nil 775 }, 776 } 777 778 return &scanner 779 }