feat: Add TrivyConfig.VulnSeveritySources (Trivy's --vuln-severity-source) (#3943)

And default it to ["auto"] when unset, with an info log from applyDefaultValues.

Refactor CVE NewScanner to take *CVEConfig instead of separate DB repository
strings so the full Trivy block is available to the scanner.

Extend CLI and search tests for the new field and logged config; document
CVE/Trivy in examples/README and add examples/config-cve-trivy.json.

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
This commit is contained in:
Andrei Aaron
2026-04-08 09:39:26 +03:00
committed by GitHub
parent c6289ec5ba
commit 451e7b8e47
15 changed files with 285 additions and 37 deletions
+3
View File
@@ -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 {
+1 -3
View File
@@ -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,
+3 -2
View File
@@ -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 {
+10 -2
View File
@@ -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)
+6 -1
View File
@@ -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)
+31 -4
View File
@@ -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,
}
}
@@ -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{})
+39 -11
View File
@@ -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)
+6 -1
View File
@@ -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)
+4 -2
View File
@@ -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)