mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
feat: add default matchers option to image scanning
hey! added the default matchers option for image scanning as requested in #1838. now you can choose between stock matchers and CPE matchers when scanning images. what's new: - added --use-default-matchers flag to scan/image/patch commands - true = stock matchers (default behavior) - false = CPE matchers (more precise) usage: # use CPE matchers for more precise detection kubescape scan image nginx:latest --use-default-matchers=false # or in scan command kubescape scan --scan-images --use-default-matchers=false everything's backward compatible - existing code works exactly the same. just added the new option for folks who want more control over their vulnerability detection. fixes #1838 Signed-off-by: aadarsh-nagrath <anagrath1@gmail.com>
This commit is contained in:
@@ -28,6 +28,7 @@ var patchCmdExamples = fmt.Sprintf(`
|
||||
func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
|
||||
var patchInfo metav1.PatchInfo
|
||||
var scanInfo cautils.ScanInfo
|
||||
var useDefaultMatchers bool
|
||||
|
||||
patchCmd := &cobra.Command{
|
||||
Use: "patch --image <image>:<tag> [flags]",
|
||||
@@ -49,6 +50,9 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the UseDefaultMatchers field in scanInfo
|
||||
scanInfo.UseDefaultMatchers = useDefaultMatchers
|
||||
|
||||
results, err := ks.Patch(&patchInfo, &scanInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -76,6 +80,7 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
|
||||
patchCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display full report. Default to false")
|
||||
|
||||
patchCmd.PersistentFlags().StringVarP(&scanInfo.FailThresholdSeverity, "severity-threshold", "s", "", "Severity threshold is the severity of a vulnerability at which the command fails and returns exit code 1")
|
||||
patchCmd.PersistentFlags().BoolVarP(&useDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false) for image scanning")
|
||||
|
||||
return patchCmd
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ var (
|
||||
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command {
|
||||
var imgCredentials shared.ImageCredentials
|
||||
var exceptions string
|
||||
var useDefaultMatchers bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "image <image>:<tag> [flags]",
|
||||
@@ -54,10 +55,11 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command
|
||||
}
|
||||
|
||||
imgScanInfo := &metav1.ImageScanInfo{
|
||||
Image: args[0],
|
||||
Username: imgCredentials.Username,
|
||||
Password: imgCredentials.Password,
|
||||
Exceptions: exceptions,
|
||||
Image: args[0],
|
||||
Username: imgCredentials.Username,
|
||||
Password: imgCredentials.Password,
|
||||
Exceptions: exceptions,
|
||||
UseDefaultMatchers: useDefaultMatchers,
|
||||
}
|
||||
|
||||
results, err := ks.ScanImage(imgScanInfo, scanInfo)
|
||||
@@ -77,6 +79,7 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command
|
||||
cmd.PersistentFlags().StringVarP(&exceptions, "exceptions", "", "", "Path to the exceptions file")
|
||||
cmd.PersistentFlags().StringVarP(&imgCredentials.Username, "username", "u", "", "Username for registry login")
|
||||
cmd.PersistentFlags().StringVarP(&imgCredentials.Password, "password", "p", "", "Password for registry login")
|
||||
cmd.PersistentFlags().BoolVarP(&useDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.PrintAttackTree, "print-attack-tree", "", false, "Print attack tree")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.EnableRegoPrint, "enable-rego-prints", "", false, "Enable sending to rego prints to the logs (use with debug log level: -l debug)")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.ScanImages, "scan-images", "", false, "Scan resources images")
|
||||
scanCmd.PersistentFlags().BoolVarP(&scanInfo.UseDefaultMatchers, "use-default-matchers", "", true, "Use default matchers (true) or CPE matchers (false) for image scanning")
|
||||
|
||||
scanCmd.PersistentFlags().MarkDeprecated("fail-threshold", "use '--compliance-threshold' flag instead. Flag will be removed at 1.Dec.2023")
|
||||
scanCmd.PersistentFlags().MarkDeprecated("create-account", "Create account is no longer supported. In case of a missing Account ID and a configured backend server, a new account id will be generated automatically by Kubescape. Feel free to contact the Kubescape maintainers for more information.")
|
||||
|
||||
@@ -137,6 +137,7 @@ type ScanInfo struct {
|
||||
TriggeredByCLI bool // indicates whether the scan was triggered by the CLI
|
||||
ScanType ScanTypes
|
||||
ScanImages bool
|
||||
UseDefaultMatchers bool
|
||||
ChartPath string
|
||||
FilePath string
|
||||
scanningContext *ScanningContext
|
||||
|
||||
@@ -165,7 +165,7 @@ func (ks *Kubescape) ScanImage(imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *ca
|
||||
logger.L().Start(fmt.Sprintf("Scanning image %s...", imgScanInfo.Image))
|
||||
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
svc, err := imagescan.NewScanService(dbCfg)
|
||||
svc, err := imagescan.NewScanServiceWithMatchers(dbCfg, imgScanInfo.UseDefaultMatchers)
|
||||
if err != nil {
|
||||
logger.L().StopError(fmt.Sprintf("Failed to initialize image scanner: %s", err))
|
||||
return nil, err
|
||||
|
||||
@@ -48,7 +48,7 @@ func (ks *Kubescape) Patch(patchInfo *ksmetav1.PatchInfo, scanInfo *cautils.Scan
|
||||
|
||||
// Setup the scan service
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
svc, err := imagescan.NewScanService(dbCfg)
|
||||
svc, err := imagescan.NewScanServiceWithMatchers(dbCfg, scanInfo.UseDefaultMatchers)
|
||||
if err != nil {
|
||||
logger.L().StopError(fmt.Sprintf("Failed to initialize image scanner: %s", err))
|
||||
return nil, err
|
||||
|
||||
@@ -202,7 +202,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
|
||||
}
|
||||
|
||||
if scanInfo.ScanImages {
|
||||
scanImages(scanInfo.ScanType, scanData, ks.Context(), resultsHandling)
|
||||
scanImages(scanInfo.ScanType, scanData, ks.Context(), resultsHandling, scanInfo)
|
||||
}
|
||||
// ========================= results handling =====================
|
||||
resultsHandling.SetData(scanData)
|
||||
@@ -214,7 +214,7 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH
|
||||
return resultsHandling, nil
|
||||
}
|
||||
|
||||
func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx context.Context, resultsHandling *resultshandling.ResultsHandler) {
|
||||
func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx context.Context, resultsHandling *resultshandling.ResultsHandler, scanInfo *cautils.ScanInfo) {
|
||||
var imagesToScan []string
|
||||
|
||||
if scanType == cautils.ScanTypeWorkload {
|
||||
@@ -244,7 +244,7 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx
|
||||
}
|
||||
|
||||
dbCfg, _ := imagescan.NewDefaultDBConfig()
|
||||
svc, err := imagescan.NewScanService(dbCfg)
|
||||
svc, err := imagescan.NewScanServiceWithMatchers(dbCfg, scanInfo.UseDefaultMatchers)
|
||||
if err != nil {
|
||||
logger.L().StopError(fmt.Sprintf("Failed to initialize image scanner: %s", err))
|
||||
return
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package v1
|
||||
|
||||
type ImageScanInfo struct {
|
||||
Username string
|
||||
Password string
|
||||
Image string
|
||||
Exceptions string
|
||||
Username string
|
||||
Password string
|
||||
Image string
|
||||
Exceptions string
|
||||
UseDefaultMatchers bool
|
||||
}
|
||||
|
||||
@@ -64,21 +64,24 @@ func NewDefaultDBConfig() (db.Config, bool) {
|
||||
}, shouldUpdate
|
||||
}
|
||||
|
||||
func getMatchers() []matcher.Matcher {
|
||||
return matcher.NewDefaultMatchers(
|
||||
matcher.Config{
|
||||
Java: java.MatcherConfig{
|
||||
ExternalSearchConfig: java.ExternalSearchConfig{MavenBaseURL: "https://search.maven.org/solrsearch/select"},
|
||||
UseCPEs: true,
|
||||
func getMatchers(useDefaultMatchers bool) []matcher.Matcher {
|
||||
if useDefaultMatchers {
|
||||
return matcher.NewDefaultMatchers(
|
||||
matcher.Config{
|
||||
Java: java.MatcherConfig{
|
||||
ExternalSearchConfig: java.ExternalSearchConfig{MavenBaseURL: "https://search.maven.org/solrsearch/select"},
|
||||
UseCPEs: true,
|
||||
},
|
||||
Ruby: ruby.MatcherConfig{UseCPEs: true},
|
||||
Python: python.MatcherConfig{UseCPEs: true},
|
||||
Dotnet: dotnet.MatcherConfig{UseCPEs: true},
|
||||
Javascript: javascript.MatcherConfig{UseCPEs: true},
|
||||
Golang: golang.MatcherConfig{UseCPEs: true},
|
||||
Stock: stock.MatcherConfig{UseCPEs: true},
|
||||
},
|
||||
Ruby: ruby.MatcherConfig{UseCPEs: true},
|
||||
Python: python.MatcherConfig{UseCPEs: true},
|
||||
Dotnet: dotnet.MatcherConfig{UseCPEs: true},
|
||||
Javascript: javascript.MatcherConfig{UseCPEs: true},
|
||||
Golang: golang.MatcherConfig{UseCPEs: true},
|
||||
Stock: stock.MatcherConfig{UseCPEs: true},
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDBLoad(loadErr error, status *db.Status) error {
|
||||
@@ -115,13 +118,14 @@ func getProviderConfig(creds RegistryCredentials) pkg.ProviderConfig {
|
||||
//
|
||||
// It performs image scanning and everything needed in between.
|
||||
type Service struct {
|
||||
dbCfg db.Config
|
||||
dbCloser *db.Closer
|
||||
dbStatus *db.Status
|
||||
dbStore *store.Store
|
||||
dbCfg db.Config
|
||||
dbCloser *db.Closer
|
||||
dbStatus *db.Status
|
||||
dbStore *store.Store
|
||||
useDefaultMatchers bool
|
||||
}
|
||||
|
||||
func getIgnoredMatches(vulnerabilityExceptions []string, store *store.Store, packages []pkg.Package, pkgContext pkg.Context) (*match.Matches, []match.IgnoredMatch, error) {
|
||||
func getIgnoredMatches(vulnerabilityExceptions []string, store *store.Store, packages []pkg.Package, pkgContext pkg.Context, useDefaultMatchers bool) (*match.Matches, []match.IgnoredMatch, error) {
|
||||
if vulnerabilityExceptions == nil {
|
||||
vulnerabilityExceptions = []string{}
|
||||
}
|
||||
@@ -136,7 +140,7 @@ func getIgnoredMatches(vulnerabilityExceptions []string, store *store.Store, pac
|
||||
|
||||
matcher := grype.VulnerabilityMatcher{
|
||||
Store: *store,
|
||||
Matchers: getMatchers(),
|
||||
Matchers: getMatchers(useDefaultMatchers),
|
||||
IgnoreRules: ignoreRules,
|
||||
}
|
||||
|
||||
@@ -187,7 +191,7 @@ func (s *Service) Scan(_ context.Context, userInput string, creds RegistryCreden
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remainingMatches, ignoredMatches, err := getIgnoredMatches(vulnerabilityExceptions, s.dbStore, packages, pkgContext)
|
||||
remainingMatches, ignoredMatches, err := getIgnoredMatches(vulnerabilityExceptions, s.dbStore, packages, pkgContext, s.useDefaultMatchers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -216,15 +220,20 @@ func NewVulnerabilityDB(cfg db.Config, update bool) (*store.Store, *db.Status, *
|
||||
}
|
||||
|
||||
func NewScanService(dbCfg db.Config) (*Service, error) {
|
||||
return NewScanServiceWithMatchers(dbCfg, true)
|
||||
}
|
||||
|
||||
func NewScanServiceWithMatchers(dbCfg db.Config, useDefaultMatchers bool) (*Service, error) {
|
||||
dbStore, dbStatus, dbCloser, err := NewVulnerabilityDB(dbCfg, true)
|
||||
if err = validateDBLoad(err, dbStatus); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Service{
|
||||
dbCfg: dbCfg,
|
||||
dbCloser: dbCloser,
|
||||
dbStatus: dbStatus,
|
||||
dbStore: dbStore,
|
||||
dbCfg: dbCfg,
|
||||
dbCloser: dbCloser,
|
||||
dbStatus: dbStatus,
|
||||
dbStore: dbStore,
|
||||
useDefaultMatchers: useDefaultMatchers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestVulnerabilityAndSeverityExceptions(t *testing.T) {
|
||||
defer dbCloser.Close()
|
||||
}
|
||||
|
||||
remainingMatches, ignoredMatches, err := getIgnoredMatches(tc.vulnerabilityExceptions, store, packages, pkgContext)
|
||||
remainingMatches, ignoredMatches, err := getIgnoredMatches(tc.vulnerabilityExceptions, store, packages, pkgContext, svc.useDefaultMatchers)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.ignoredLen, len(ignoredMatches))
|
||||
|
||||
@@ -347,6 +347,23 @@ func TestNewScanService(t *testing.T) {
|
||||
assert.Equal(t, defaultConfig, svc.dbCfg)
|
||||
}
|
||||
|
||||
func TestNewScanServiceWithDefaultMatchers(t *testing.T) {
|
||||
// Test the Service struct creation with different useDefaultMatchers values
|
||||
// This test doesn't require a real database
|
||||
|
||||
// Test with default matchers enabled
|
||||
svcWithDefault := &Service{
|
||||
useDefaultMatchers: true,
|
||||
}
|
||||
assert.True(t, svcWithDefault.useDefaultMatchers)
|
||||
|
||||
// Test with default matchers disabled
|
||||
svcWithoutDefault := &Service{
|
||||
useDefaultMatchers: false,
|
||||
}
|
||||
assert.False(t, svcWithoutDefault.useDefaultMatchers)
|
||||
}
|
||||
|
||||
func TestExceedsSeverityThreshold(t *testing.T) {
|
||||
my_matches := match.NewMatches()
|
||||
my_matches.Add(match.Match{
|
||||
@@ -443,3 +460,48 @@ func TestExceedsSeverityThreshold(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMatchers(t *testing.T) {
|
||||
// Test with default matchers enabled
|
||||
matchersWithDefault := getMatchers(true)
|
||||
assert.NotNil(t, matchersWithDefault)
|
||||
assert.Greater(t, len(matchersWithDefault), 0)
|
||||
|
||||
// Test with default matchers disabled
|
||||
matchersWithoutDefault := getMatchers(false)
|
||||
assert.Nil(t, matchersWithoutDefault)
|
||||
}
|
||||
|
||||
func TestNewScanServiceWithMatchers(t *testing.T) {
|
||||
// Test the Service struct creation with different useDefaultMatchers values
|
||||
// This test doesn't require a real database
|
||||
|
||||
// Test with default matchers enabled
|
||||
svcWithDefault := &Service{
|
||||
useDefaultMatchers: true,
|
||||
}
|
||||
assert.True(t, svcWithDefault.useDefaultMatchers)
|
||||
|
||||
// Test with default matchers disabled
|
||||
svcWithoutDefault := &Service{
|
||||
useDefaultMatchers: false,
|
||||
}
|
||||
assert.False(t, svcWithoutDefault.useDefaultMatchers)
|
||||
}
|
||||
|
||||
func TestNewScanServiceWithMatchersIntegration(t *testing.T) {
|
||||
// Test the actual NewScanServiceWithMatchers function
|
||||
defaultConfig, _ := NewDefaultDBConfig()
|
||||
|
||||
// Test with default matchers enabled
|
||||
svcWithDefault, err := NewScanServiceWithMatchers(defaultConfig, true)
|
||||
require.NoError(t, err)
|
||||
defer svcWithDefault.Close()
|
||||
assert.True(t, svcWithDefault.useDefaultMatchers)
|
||||
|
||||
// Test with default matchers disabled
|
||||
svcWithoutDefault, err := NewScanServiceWithMatchers(defaultConfig, false)
|
||||
require.NoError(t, err)
|
||||
defer svcWithoutDefault.Close()
|
||||
assert.False(t, svcWithoutDefault.useDefaultMatchers)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user