diff --git a/examples/README.md b/examples/README.md index c3af4416..4c950688 100644 --- a/examples/README.md +++ b/examples/README.md @@ -32,6 +32,7 @@ Examples of working configurations for various use cases are available [here](.. - [Storage Drivers](#storage-drivers) - [Specifying S3 credentials](#specifying-s3-credentials) - [Sync](#sync) + - [Search and CVE scanning (Trivy)](#search-and-cve-scanning-trivy) ## Network @@ -1164,3 +1165,18 @@ sync can also read the certificates directly under certDir: ### Sync's credentials Besides sync-auth.json file, zot also reads and uses docker credentials by default: https://docs.docker.com/reference/cli/docker/login/#description + +## Search and CVE scanning (Trivy) + +The `search` extension can include a `cve` section so zot downloads the [Trivy](https://github.com/aquasecurity/trivy) vulnerability database and exposes CVE data via the search API (for example GraphQL). + +A minimal configuration only sets how often the DB is refreshed; zot applies defaults for Trivy DB locations and severity selection: + +- [config-cve.json](config-cve.json) — `updateInterval` only; defaults are applied for the Trivy DB, Java DB (for language packages), and `vulnSeveritySources`. + +To set those options explicitly (for example to mirror standalone Trivy’s `--vuln-severity-source` behavior), use a `trivy` object under `cve`: + +- [config-cve-trivy.json](config-cve-trivy.json) — shows optional `dbRepository`, `javaDBRepository`, and `vulnSeveritySources`. + +`vulnSeveritySources` is a list of source names in priority order (for example `auto`, `nvd`, or vendor IDs such as `redhat`, `alpine`). If omitted, zot defaults it to `["auto"]`, consistent with the Trivy CLI. See [Trivy: severity selection](https://trivy.dev/docs/latest/scanner/vulnerability/#severity-selection). + diff --git a/examples/config-cve-trivy.json b/examples/config-cve-trivy.json new file mode 100644 index 00000000..823fad16 --- /dev/null +++ b/examples/config-cve-trivy.json @@ -0,0 +1,26 @@ +{ + "distSpecVersion": "1.1.1", + "storage": { + "rootDirectory": "/tmp/zot" + }, + "http": { + "address": "127.0.0.1", + "port": "8080" + }, + "log": { + "level": "debug" + }, + "extensions": { + "search": { + "enable": true, + "cve": { + "updateInterval": "24h", + "trivy": { + "dbRepository": "ghcr.io/aquasecurity/trivy-db", + "javaDBRepository": "ghcr.io/aquasecurity/trivy-java-db", + "vulnSeveritySources": ["auto"] + } + } + } + } +} diff --git a/pkg/cli/server/extensions_test.go b/pkg/cli/server/extensions_test.go index 3e2551b4..083eb975 100644 --- a/pkg/cli/server/extensions_test.go +++ b/pkg/cli/server/extensions_test.go @@ -967,7 +967,8 @@ func TestServeSearchEnabledDefaultCVEDB(t *testing.T) { // The default config handling logic will convert the 1h interval to a 2h interval substring := "\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":7200000000000,\"Trivy\":" + - "{\"DBRepository\":\"ghcr.io/aquasecurity/trivy-db\",\"JavaDBRepository\":\"ghcr.io/aquasecurity/trivy-java-db\"}}}" + "{\"DBRepository\":\"ghcr.io/aquasecurity/trivy-db\",\"JavaDBRepository\":\"ghcr.io/aquasecurity/trivy-java-db\"," + + "\"VulnSeveritySources\":[\"auto\"]}}}" found, err := ReadLogFileAndSearchString(logPath, substring, readLogFileTimeout) @@ -986,6 +987,76 @@ func TestServeSearchEnabledDefaultCVEDB(t *testing.T) { So(found, ShouldBeTrue) So(err, ShouldBeNil) }) + + Convey("VulnSeveritySources defaults to [auto] when trivy block is empty", t, func(c C) { + cfg := config.New() + + // mapstructure omits an all-empty CVE; at least updateInterval (or any non-zero cve/trivy + // field) must be present for CVE to unmarshal non-nil before defaults run. + content := `{ + "storage": { "rootDirectory": "/tmp/zot" }, + "http": { "address": "127.0.0.1", "port": "8080" }, + "extensions": { + "search": { + "enable": true, + "cve": { "updateInterval": "24h", "trivy": { } } + } + } + }` + configPath := MakeTempFileWithContent(t, "zot-test.json", content) + + err := cli.LoadConfiguration(cfg, configPath) + So(err, ShouldBeNil) + + So(cfg.Extensions, ShouldNotBeNil) + So(cfg.Extensions.Search, ShouldNotBeNil) + So(cfg.Extensions.Search.CVE, ShouldNotBeNil) + So(cfg.Extensions.Search.CVE.Trivy, ShouldNotBeNil) + So(cfg.Extensions.Search.CVE.Trivy.VulnSeveritySources, ShouldResemble, []string{"auto"}) + }) + + Convey("VulnSeveritySources respects explicit list", t, func(c C) { + cfg := config.New() + + content := `{ + "storage": { "rootDirectory": "/tmp/zot" }, + "http": { "address": "127.0.0.1", "port": "8080" }, + "extensions": { + "search": { + "enable": true, + "cve": { "trivy": { "vulnSeveritySources": ["nvd", "ghsa"] } } + } + } + }` + configPath := MakeTempFileWithContent(t, "zot-test.json", content) + + err := cli.LoadConfiguration(cfg, configPath) + So(err, ShouldBeNil) + + So(cfg.Extensions.Search.CVE.Trivy.VulnSeveritySources, ShouldResemble, []string{"nvd", "ghsa"}) + }) + + Convey("CVE with only updateInterval (no trivy key) gets VulnSeveritySources [auto]", t, func(c C) { + cfg := config.New() + + content := `{ + "storage": { "rootDirectory": "/tmp/zot" }, + "http": { "address": "127.0.0.1", "port": "8080" }, + "extensions": { + "search": { + "enable": true, + "cve": { "updateInterval": "24h" } + } + } + }` + configPath := MakeTempFileWithContent(t, "zot-test.json", content) + + err := cli.LoadConfiguration(cfg, configPath) + So(err, ShouldBeNil) + + So(cfg.Extensions.Search.CVE.Trivy, ShouldNotBeNil) + So(cfg.Extensions.Search.CVE.Trivy.VulnSeveritySources, ShouldResemble, []string{"auto"}) + }) } func TestServeSearchEnabledNoCVE(t *testing.T) { diff --git a/pkg/cli/server/root.go b/pkg/cli/server/root.go index fc4696da..815bc9db 100644 --- a/pkg/cli/server/root.go +++ b/pkg/cli/server/root.go @@ -891,6 +891,14 @@ func applyDefaultValues(config *config.Config, viperInstance *viper.Viper, logge config.Extensions.Search.CVE.Trivy.JavaDBRepository = defaultJavaDBDownloadURL } + + if len(config.Extensions.Search.CVE.Trivy.VulnSeveritySources) == 0 { + defaultVulnSeveritySources := []string{"auto"} + logger.Info().Strs("vulnSeveritySources", defaultVulnSeveritySources).Str("component", "config"). + Msg("using default trivy vulnerability severity sources.") + + config.Extensions.Search.CVE.Trivy.VulnSeveritySources = defaultVulnSeveritySources + } } } diff --git a/pkg/extensions/config/config.go b/pkg/extensions/config/config.go index 3764355f..15b442cd 100644 --- a/pkg/extensions/config/config.go +++ b/pkg/extensions/config/config.go @@ -60,6 +60,9 @@ type CVEConfig struct { type TrivyConfig struct { DBRepository string // default is "ghcr.io/aquasecurity/trivy-db" JavaDBRepository string // default is "ghcr.io/aquasecurity/trivy-java-db" + // VulnSeveritySources controls Trivy's severity source selection (same as Trivy's --vuln-severity-source). + // If empty, zot will default it to ["auto"]. + VulnSeveritySources []string } type MetricsConfig struct { diff --git a/pkg/extensions/extension_search.go b/pkg/extensions/extension_search.go index 9c2a2576..79f4deaa 100644 --- a/pkg/extensions/extension_search.go +++ b/pkg/extensions/extension_search.go @@ -39,10 +39,8 @@ func GetCveScanner(conf *config.Config, storeController storage.StoreController, } cveConfig := extensionsConfig.GetSearchCVEConfig() - dbRepository := cveConfig.Trivy.DBRepository - javaDBRepository := cveConfig.Trivy.JavaDBRepository - return cveinfo.NewScanner(storeController, metaDB, dbRepository, javaDBRepository, log) + return cveinfo.NewScanner(storeController, metaDB, cveConfig, log) } func EnableSearchExtension(conf *config.Config, storeController storage.StoreController, diff --git a/pkg/extensions/search/cve/cve.go b/pkg/extensions/search/cve/cve.go index 6830ce7c..fba0b2f9 100644 --- a/pkg/extensions/search/cve/cve.go +++ b/pkg/extensions/search/cve/cve.go @@ -12,6 +12,7 @@ import ( zerr "zotregistry.dev/zot/v2/errors" zcommon "zotregistry.dev/zot/v2/pkg/common" "zotregistry.dev/zot/v2/pkg/compat" + extconf "zotregistry.dev/zot/v2/pkg/extensions/config" cvemodel "zotregistry.dev/zot/v2/pkg/extensions/search/cve/model" "zotregistry.dev/zot/v2/pkg/extensions/search/cve/trivy" "zotregistry.dev/zot/v2/pkg/log" @@ -45,9 +46,9 @@ type BaseCveInfo struct { } func NewScanner(storeController storage.StoreController, metaDB mTypes.MetaDB, - dbRepository, javaDBRepository string, log log.Logger, + cveConfig *extconf.CVEConfig, log log.Logger, ) Scanner { - return trivy.NewScanner(storeController, metaDB, dbRepository, javaDBRepository, log) + return trivy.NewScanner(storeController, metaDB, cveConfig, log) } func NewCVEInfo(scanner Scanner, metaDB mTypes.MetaDB, log log.Logger) *BaseCveInfo { diff --git a/pkg/extensions/search/cve/cve_test.go b/pkg/extensions/search/cve/cve_test.go index c43369e8..5a676fe9 100644 --- a/pkg/extensions/search/cve/cve_test.go +++ b/pkg/extensions/search/cve/cve_test.go @@ -334,7 +334,11 @@ func TestImageFormat(t *testing.T) { err = meta.ParseStorage(metaDB, storeController, log) So(err, ShouldBeNil) - scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := cveinfo.NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, log) isValidImage, err := scanner.IsImageFormatScannable("zot-test", "") So(err, ShouldNotBeNil) @@ -407,7 +411,11 @@ func TestImageFormat(t *testing.T) { DefaultStore: mocks.MockedImageStore{}, } - scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := cveinfo.NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, log) isScanable, err := scanner.IsImageFormatScannable("repo", "tag") So(err, ShouldBeNil) diff --git a/pkg/extensions/search/cve/scan_test.go b/pkg/extensions/search/cve/scan_test.go index 649641c8..10e8e101 100644 --- a/pkg/extensions/search/cve/scan_test.go +++ b/pkg/extensions/search/cve/scan_test.go @@ -19,6 +19,7 @@ import ( zerr "zotregistry.dev/zot/v2/errors" "zotregistry.dev/zot/v2/pkg/api/config" zcommon "zotregistry.dev/zot/v2/pkg/common" + extconf "zotregistry.dev/zot/v2/pkg/extensions/config" "zotregistry.dev/zot/v2/pkg/extensions/monitoring" cveinfo "zotregistry.dev/zot/v2/pkg/extensions/search/cve" cvecache "zotregistry.dev/zot/v2/pkg/extensions/search/cve/cache" @@ -513,7 +514,11 @@ func TestScanGeneratorWithRealData(t *testing.T) { err = meta.ParseStorage(metaDB, storeController, logger) So(err, ShouldBeNil) - scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", logger) + scanner := cveinfo.NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, logger) err = scanner.UpdateDB(context.Background()) So(err, ShouldBeNil) diff --git a/pkg/extensions/search/cve/trivy/scanner.go b/pkg/extensions/search/cve/trivy/scanner.go index 00682da5..d27a52c5 100644 --- a/pkg/extensions/search/cve/trivy/scanner.go +++ b/pkg/extensions/search/cve/trivy/scanner.go @@ -20,6 +20,7 @@ import ( "github.com/aquasecurity/trivy/pkg/javadb" "github.com/aquasecurity/trivy/pkg/types" xos "github.com/aquasecurity/trivy/pkg/x/os" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" "github.com/google/go-containerregistry/pkg/name" regTypes "github.com/google/go-containerregistry/pkg/v1/types" godigest "github.com/opencontainers/go-digest" @@ -29,6 +30,7 @@ import ( zerr "zotregistry.dev/zot/v2/errors" zcommon "zotregistry.dev/zot/v2/pkg/common" "zotregistry.dev/zot/v2/pkg/compat" + extconf "zotregistry.dev/zot/v2/pkg/extensions/config" cvecache "zotregistry.dev/zot/v2/pkg/extensions/search/cve/cache" cvemodel "zotregistry.dev/zot/v2/pkg/extensions/search/cve/model" "zotregistry.dev/zot/v2/pkg/log" @@ -40,7 +42,9 @@ const cacheSize = 1000000 // getNewScanOptions sets trivy configuration values for our scans and returns them as // a trivy Options structure. -func getNewScanOptions(dir string, dbRepositoryRef, javaDBRepositoryRef name.Reference) *flag.Options { +func getNewScanOptions(dir string, dbRepositoryRef, javaDBRepositoryRef name.Reference, + vulnSeveritySources []dbTypes.SourceID, +) *flag.Options { scanOptions := flag.Options{ GlobalOptions: flag.GlobalOptions{ CacheDir: dir, @@ -61,6 +65,9 @@ func getNewScanOptions(dir string, dbRepositoryRef, javaDBRepositoryRef name.Ref SkipDBUpdate: true, SkipJavaDBUpdate: true, }, + VulnerabilityOptions: flag.VulnerabilityOptions{ + VulnSeveritySources: vulnSeveritySources, + }, ReportOptions: flag.ReportOptions{ Format: "table", Severities: []dbTypes.Severity{ @@ -90,11 +97,25 @@ type Scanner struct { cache *cvecache.CveCache dbRepositoryRef name.Reference javaDBRepositoryRef name.Reference + vulnSeveritySources []dbTypes.SourceID } func NewScanner(storeController storage.StoreController, - metaDB mTypes.MetaDB, dbRepository, javaDBRepository string, log log.Logger, + metaDB mTypes.MetaDB, cveConfig *extconf.CVEConfig, log log.Logger, ) *Scanner { + var trivyCfg *extconf.TrivyConfig + if cveConfig != nil && cveConfig.Trivy != nil { + trivyCfg = cveConfig.Trivy + } + + if trivyCfg == nil { + trivyCfg = &extconf.TrivyConfig{} + } + + dbRepository := trivyCfg.DBRepository + javaDBRepository := trivyCfg.JavaDBRepository + vulnSeveritySources := trivyCfg.VulnSeveritySources + // The logic to set defaults is similar to what trivy itself uses: // https://github.com/aquasecurity/trivy/blob/v0.51.4/pkg/flag/db_flags.go#L152 var dbRepositoryRef name.Reference @@ -126,13 +147,18 @@ func NewScanner(storeController storage.StoreController, subCveConfig := make(map[string]*flag.Options) + sevSources := xstrings.ToTSlice[dbTypes.SourceID](vulnSeveritySources) + if len(sevSources) == 0 { + sevSources = []dbTypes.SourceID{"auto"} + } + if storeController.DefaultStore != nil { imageStore := storeController.DefaultStore rootDir := imageStore.RootDir() cacheDir := path.Join(rootDir, "_trivy") - opts := getNewScanOptions(cacheDir, dbRepositoryRef, javaDBRepositoryRef) + opts := getNewScanOptions(cacheDir, dbRepositoryRef, javaDBRepositoryRef, sevSources) cveController.DefaultCveConfig = opts } @@ -142,7 +168,7 @@ func NewScanner(storeController storage.StoreController, rootDir := storage.RootDir() cacheDir := path.Join(rootDir, "_trivy") - opts := getNewScanOptions(cacheDir, dbRepositoryRef, javaDBRepositoryRef) + opts := getNewScanOptions(cacheDir, dbRepositoryRef, javaDBRepositoryRef, sevSources) subCveConfig[route] = opts } @@ -159,6 +185,7 @@ func NewScanner(storeController storage.StoreController, cache: cvecache.NewCveCache(cacheSize, log), dbRepositoryRef: dbRepositoryRef, javaDBRepositoryRef: javaDBRepositoryRef, + vulnSeveritySources: sevSources, } } diff --git a/pkg/extensions/search/cve/trivy/scanner_internal_test.go b/pkg/extensions/search/cve/trivy/scanner_internal_test.go index c9f7f1df..5dae0a9e 100644 --- a/pkg/extensions/search/cve/trivy/scanner_internal_test.go +++ b/pkg/extensions/search/cve/trivy/scanner_internal_test.go @@ -9,12 +9,14 @@ import ( "testing" "time" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" zerr "zotregistry.dev/zot/v2/errors" "zotregistry.dev/zot/v2/pkg/common" + extconf "zotregistry.dev/zot/v2/pkg/extensions/config" "zotregistry.dev/zot/v2/pkg/extensions/monitoring" cvecache "zotregistry.dev/zot/v2/pkg/extensions/search/cve/cache" "zotregistry.dev/zot/v2/pkg/extensions/search/cve/model" @@ -79,7 +81,11 @@ func TestMultipleStoragePath(t *testing.T) { metaDB, err := boltdb.New(boltDriver, log) So(err, ShouldBeNil) - scanner := NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, log) So(scanner.storeController.DefaultStore, ShouldNotBeNil) So(scanner.storeController.SubStore, ShouldNotBeNil) @@ -195,7 +201,11 @@ func TestTrivyLibraryErrors(t *testing.T) { img := "zot-test:0.0.1" //nolint:goconst // Download DB fails for invalid DB url - scanner := NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-not-db", "", log) + scanner := NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-not-db", + }, + }, log) ctx := context.Background() @@ -209,15 +219,23 @@ func TestTrivyLibraryErrors(t *testing.T) { So(err, ShouldWrap, zerr.ErrCVEDBNotFound) // Download DB fails for invalid Java DB - scanner = NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", - "ghcr.io/project-zot/trivy-not-db", log) + scanner = NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + JavaDBRepository: "ghcr.io/project-zot/trivy-not-db", + }, + }, log) err = scanner.UpdateDB(ctx) So(err, ShouldNotBeNil) // Download DB passes for valid Trivy DB url, and missing Trivy Java DB url // Download DB is necessary since DB download on scan is disabled - scanner = NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner = NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, log) // UpdateDB with good ctx err = scanner.UpdateDB(ctx) @@ -317,8 +335,12 @@ func TestImageScannable(t *testing.T) { storeController := storage.StoreController{} storeController.DefaultStore = store - scanner := NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", - "ghcr.io/project-zot/trivy-java-db", log) + scanner := NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + JavaDBRepository: "ghcr.io/project-zot/trivy-java-db", + }, + }, log) Convey("Valid image should be scannable", t, func() { result, err := scanner.IsImageFormatScannable("repo1", "valid") @@ -387,8 +409,12 @@ func TestTrivyDBUrl(t *testing.T) { // Ideally we would want to also test the default urls // But we are getting `response status code 429: toomanyrequests` from // `ghcr.io/aquasecurity/trivy-db` and `ghcr.io/aquasecurity/trivy-java-db` - scanner := NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", - "ghcr.io/project-zot/trivy-java-db", log) + scanner := NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + JavaDBRepository: "ghcr.io/project-zot/trivy-java-db", + }, + }, log) ctx := context.Background() @@ -480,6 +506,29 @@ func TestIsIndexScannableErrors(t *testing.T) { }) } +func TestVulnSeveritySourcesDefaulting(t *testing.T) { + Convey("NewScanner defaults VulnSeveritySources to auto when empty", t, func() { + scanner := NewScanner(storage.StoreController{}, nil, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, log.NewTestLogger()) + So(scanner, ShouldNotBeNil) + So(scanner.vulnSeveritySources, ShouldResemble, []dbTypes.SourceID{"auto"}) + }) + + Convey("NewScanner preserves provided VulnSeveritySources", t, func() { + scanner := NewScanner(storage.StoreController{}, nil, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + VulnSeveritySources: []string{"nvd", "ghsa"}, + }, + }, log.NewTestLogger()) + So(scanner, ShouldNotBeNil) + So(scanner.vulnSeveritySources, ShouldResemble, []dbTypes.SourceID{"nvd", "ghsa"}) + }) +} + func TestGetCVEReference(t *testing.T) { Convey("getCVEReference", t, func() { ref := getCVEReference("primary", []string{}) diff --git a/pkg/extensions/search/cve/trivy/scanner_test.go b/pkg/extensions/search/cve/trivy/scanner_test.go index ad9a5a67..398254eb 100644 --- a/pkg/extensions/search/cve/trivy/scanner_test.go +++ b/pkg/extensions/search/cve/trivy/scanner_test.go @@ -61,7 +61,11 @@ func TestScanBigTestFile(t *testing.T) { cm.StartAndWait(port) defer cm.StopServer() // scan - scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log) + scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, ctlr.Log) err = scanner.UpdateDB(context.Background()) So(err, ShouldBeNil) @@ -105,7 +109,11 @@ func TestScanningByDigest(t *testing.T) { So(err, ShouldBeNil) // scan - scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log) + scanner := trivy.NewScanner(ctlr.StoreController, ctlr.MetaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, ctlr.Log) ctx := context.Background() @@ -191,7 +199,11 @@ func TestVulnerableLayer(t *testing.T) { err = meta.ParseStorage(metaDB, storeController, log) So(err, ShouldBeNil) - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := trivy.NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, log) err = scanner.UpdateDB(context.Background()) So(err, ShouldBeNil) @@ -262,8 +274,12 @@ func TestVulnerableLayer(t *testing.T) { err = meta.ParseStorage(metaDB, storeController, log) So(err, ShouldBeNil) - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", - "ghcr.io/project-zot/trivy-java-db", log) + scanner := trivy.NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + JavaDBRepository: "ghcr.io/project-zot/trivy-java-db", + }, + }, log) err = scanner.UpdateDB(context.Background()) So(err, ShouldBeNil) @@ -343,7 +359,11 @@ func TestWithTempDirErrorHandling(t *testing.T) { err = meta.ParseStorage(metaDB, storeController, log) So(err, ShouldBeNil) - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := trivy.NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, log) // Clean up any existing temp directory first _ = xos.Cleanup() @@ -420,12 +440,20 @@ func TestScannerErrors(t *testing.T) { metaDB := mocks.MetaDBMock{} log := log.NewTestLogger() + testTrivyCVEConfig := func() *extconf.CVEConfig { + return &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + } + } + Convey("IsImageFormatScannable", func() { storeController.DefaultStore = mocks.MockedImageStore{} metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) { return types.ImageMeta{}, ErrTestError } - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := trivy.NewScanner(storeController, metaDB, testTrivyCVEConfig(), log) _, err := scanner.IsImageFormatScannable("repo", godigest.FromString("dig").String()) So(err, ShouldNotBeNil) @@ -435,7 +463,7 @@ func TestScannerErrors(t *testing.T) { metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) { return types.ImageMeta{}, ErrTestError } - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := trivy.NewScanner(storeController, metaDB, testTrivyCVEConfig(), log) Convey("Manifest", func() { _, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageManifest) @@ -449,7 +477,7 @@ func TestScannerErrors(t *testing.T) { metaDB.GetImageMetaFn = func(digest godigest.Digest) (types.ImageMeta, error) { return types.ImageMeta{}, nil } - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := trivy.NewScanner(storeController, metaDB, testTrivyCVEConfig(), log) _, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex) So(err, ShouldNotBeNil) @@ -465,7 +493,7 @@ func TestScannerErrors(t *testing.T) { }}}, }, nil } - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := trivy.NewScanner(storeController, metaDB, testTrivyCVEConfig(), log) _, err := scanner.IsImageMediaScannable("repo", godigest.FromString("dig").String(), ispec.MediaTypeImageIndex) So(err, ShouldBeNil) @@ -477,7 +505,7 @@ func TestScannerErrors(t *testing.T) { return types.ImageMeta{}, ErrTestError } - scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log) + scanner := trivy.NewScanner(storeController, metaDB, testTrivyCVEConfig(), log) _, err := scanner.ScanImage(context.Background(), "image@"+godigest.FromString("digest").String()) So(err, ShouldNotBeNil) diff --git a/pkg/extensions/search/cve/update_test.go b/pkg/extensions/search/cve/update_test.go index f6c649b6..19d57be0 100644 --- a/pkg/extensions/search/cve/update_test.go +++ b/pkg/extensions/search/cve/update_test.go @@ -13,6 +13,7 @@ import ( . "github.com/smartystreets/goconvey/convey" "zotregistry.dev/zot/v2/pkg/api/config" + extconf "zotregistry.dev/zot/v2/pkg/extensions/config" "zotregistry.dev/zot/v2/pkg/extensions/monitoring" cveinfo "zotregistry.dev/zot/v2/pkg/extensions/search/cve" "zotregistry.dev/zot/v2/pkg/log" @@ -55,7 +56,11 @@ func TestCVEDBGenerator(t *testing.T) { }, } - cveScanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", logger) + cveScanner := cveinfo.NewScanner(storeController, metaDB, &extconf.CVEConfig{ + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, logger) generator := cveinfo.NewDBUpdateTaskGenerator(time.Minute, cveScanner, logger) sch.SubmitGenerator(generator, 12000*time.Millisecond, scheduler.HighPriority) diff --git a/pkg/extensions/search/search_test.go b/pkg/extensions/search/search_test.go index 9b4992e5..16be67c7 100644 --- a/pkg/extensions/search/search_test.go +++ b/pkg/extensions/search/search_test.go @@ -702,8 +702,9 @@ func TestRepoListWithNewestImage(t *testing.T) { defer ctlr.Shutdown() + // Match a stable prefix; config logging may include additional Trivy fields (e.g. severity sources). substring := "{\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000," + - "\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"}}}" + "\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"" found, err := readFileAndSearchString(logPath, substring, 2*time.Minute) So(found, ShouldBeTrue) So(err, ShouldBeNil) @@ -3667,8 +3668,9 @@ func TestGlobalSearch(t *testing.T) { //nolint: gocyclo defer ctlr.Shutdown() // Wait for trivy db to download + // Match a stable prefix; config logging may include additional Trivy fields (e.g. severity sources). substring := "{\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000," + - "\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"}}}" + "\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"" found, err := readFileAndSearchString(logPath, substring, 2*time.Minute) So(found, ShouldBeTrue) So(err, ShouldBeNil) diff --git a/test/blackbox/upgrade.bats b/test/blackbox/upgrade.bats index 357f1440..196df773 100644 --- a/test/blackbox/upgrade.bats +++ b/test/blackbox/upgrade.bats @@ -419,7 +419,8 @@ JSON found=0 for i in "${lines[@]}" do - if [[ "$i" = *"CVE-2025-26519 UNKNOWN musl libc 0.9.13 through 1.2.5 before 1.2.6 h..."* ]]; then + # Severity can change with Trivy DB / vulnSeveritySources (e.g. auto); match CVE id + title only. + if [[ "$i" = *"CVE-2025-26519"* && "$i" = *"musl libc 0.9.13 through 1.2.5 before 1.2.6 h"* ]]; then found=1 fi done