diff --git a/cmd/scan/image.go b/cmd/scan/image.go index 9730998aac..717c1d7849 100644 --- a/cmd/scan/image.go +++ b/cmd/scan/image.go @@ -31,6 +31,8 @@ var ( // getImageCmd returns the scan image command func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command { var imgCredentials shared.ImageCredentials + var exceptions []string + cmd := &cobra.Command{ Use: "image : [flags]", Short: "Scan an image for vulnerabilities", @@ -51,9 +53,10 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command } imgScanInfo := &metav1.ImageScanInfo{ - Image: args[0], - Username: imgCredentials.Username, - Password: imgCredentials.Password, + Image: args[0], + Username: imgCredentials.Username, + Password: imgCredentials.Password, + Exceptions: exceptions, } results, err := ks.ScanImage(context.Background(), imgScanInfo, scanInfo) @@ -69,6 +72,9 @@ func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Command }, } + // The exceptions flag + cmd.Flags().StringSliceVarP(&exceptions, "exceptions", "E", []string{}, "List of CVEs to exclude") + cmd.PersistentFlags().StringVarP(&imgCredentials.Username, "username", "u", "", "Username for registry login") cmd.PersistentFlags().StringVarP(&imgCredentials.Password, "password", "p", "", "Password for registry login") diff --git a/core/core/image_scan.go b/core/core/image_scan.go index e0f317abb9..35f1c41df6 100644 --- a/core/core/image_scan.go +++ b/core/core/image_scan.go @@ -23,7 +23,7 @@ func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageS Password: imgScanInfo.Password, } - scanResults, err := svc.Scan(ctx, imgScanInfo.Image, creds) + scanResults, err := svc.Scan(ctx, imgScanInfo.Image, creds, imgScanInfo.Exceptions) if err != nil { logger.L().StopError(fmt.Sprintf("Failed to scan image: %s", imgScanInfo.Image)) return nil, err diff --git a/core/core/patch.go b/core/core/patch.go index b5e1ed08db..c1a7728965 100644 --- a/core/core/patch.go +++ b/core/core/patch.go @@ -34,7 +34,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s Password: patchInfo.Password, } // Scan the image - scanResults, err := svc.Scan(ctx, patchInfo.Image, creds) + scanResults, err := svc.Scan(ctx, patchInfo.Image, creds, nil) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s logger.L().Start(fmt.Sprintf("Re-scanning image: %s", patchedImageName)) - scanResultsPatched, err := svc.Scan(ctx, patchedImageName, creds) + scanResultsPatched, err := svc.Scan(ctx, patchedImageName, creds, nil) if err != nil { return nil, err } diff --git a/core/core/scan.go b/core/core/scan.go index c9a8144b20..91ea2039e1 100644 --- a/core/core/scan.go +++ b/core/core/scan.go @@ -252,7 +252,7 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx func scanSingleImage(ctx context.Context, img string, svc imagescan.Service, resultsHandling *resultshandling.ResultsHandler) error { - scanResults, err := svc.Scan(ctx, img, imagescan.RegistryCredentials{}) + scanResults, err := svc.Scan(ctx, img, imagescan.RegistryCredentials{}, nil) if err != nil { return err } diff --git a/core/meta/datastructures/v1/image_scan.go b/core/meta/datastructures/v1/image_scan.go index 6586d2ff62..c97638c4c2 100644 --- a/core/meta/datastructures/v1/image_scan.go +++ b/core/meta/datastructures/v1/image_scan.go @@ -1,7 +1,8 @@ package v1 type ImageScanInfo struct { - Username string - Password string - Image string + Username string + Password string + Image string + Exceptions []string } diff --git a/pkg/imagescan/imagescan.go b/pkg/imagescan/imagescan.go index a61e132246..bc884ac830 100644 --- a/pkg/imagescan/imagescan.go +++ b/pkg/imagescan/imagescan.go @@ -10,6 +10,7 @@ import ( "github.com/anchore/grype/grype" "github.com/anchore/grype/grype/db" "github.com/anchore/grype/grype/grypeerr" + "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/matcher" "github.com/anchore/grype/grype/matcher/dotnet" "github.com/anchore/grype/grype/matcher/golang" @@ -117,7 +118,11 @@ type Service struct { dbCfg db.Config } -func (s *Service) Scan(ctx context.Context, userInput string, creds RegistryCredentials) (*models.PresenterConfig, error) { +func (s *Service) Scan(ctx context.Context, userInput string, creds RegistryCredentials, exceptions []string) (*models.PresenterConfig, error) { + if exceptions == nil { + exceptions = []string{} + } + var err error store, status, dbCloser, err := NewVulnerabilityDB(s.dbCfg, true) @@ -134,9 +139,18 @@ func (s *Service) Scan(ctx context.Context, userInput string, creds RegistryCred defer dbCloser.Close() } + var ignoreRules []match.IgnoreRule + for _, exception := range exceptions { + rule := match.IgnoreRule{ + Vulnerability: exception, + } + ignoreRules = append(ignoreRules, rule) + } + matcher := grype.VulnerabilityMatcher{ - Store: *store, - Matchers: getMatchers(), + Store: *store, + Matchers: getMatchers(), + IgnoreRules: ignoreRules, } remainingMatches, ignoredMatches, err := matcher.FindMatches(packages, pkgContext) diff --git a/pkg/imagescan/imagescan_test.go b/pkg/imagescan/imagescan_test.go index 57edbdbdc4..f4eb1898ee 100644 --- a/pkg/imagescan/imagescan_test.go +++ b/pkg/imagescan/imagescan_test.go @@ -1,6 +1,7 @@ package imagescan import ( + "context" "errors" "testing" "time" @@ -39,40 +40,42 @@ import ( // assert.IsType(t, Service{}, svc) // } -// func TestScan(t *testing.T) { -// tt := []struct { -// name string -// image string -// creds RegistryCredentials -// }{ -// { -// name: "Valid image name produces a non-nil scan result", -// image: "nginx", -// }, -// { -// name: "Scanning a valid image with provided credentials should produce a non-nil scan result", -// image: "nginx", -// creds: RegistryCredentials{ -// Username: "test", -// Password: "password", -// }, -// }, -// } +func TestScan(t *testing.T) { + dbCfg, _ := NewDefaultDBConfig() + svc := NewScanService(dbCfg) + ctx := context.Background() + image := "nginx" + creds := RegistryCredentials{} -// for _, tc := range tt { -// t.Run(tc.name, func(t *testing.T) { -// ctx := context.Background() -// dbCfg, _ := NewDefaultDBConfig() -// svc := NewScanService(dbCfg) -// creds := RegistryCredentials{} + tests := []struct { + name string + image string + exceptions []string + err error + }{ + { + name: "Without exceptions", + image: image, + exceptions: []string{}, + err: nil, + }, + { + name: "With exceptions", + image: image, + exceptions: []string{"CVE-2023-6879", "CVE-2023-45853"}, + err: nil, + }, + } -// scanResults, err := svc.Scan(ctx, tc.image, creds) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + scanResults, err := svc.Scan(ctx, tc.image, creds, tc.exceptions) -// assert.NoError(t, err) -// assert.IsType(t, &models.PresenterConfig{}, scanResults) -// }) -// } -// } + assert.NoError(t, err) + assert.IsType(t, &models.PresenterConfig{}, scanResults) + }) + } +} // fakeMetaProvider is a test double that fakes an actual MetadataProvider type fakeMetaProvider struct {