From c5e7370e7f4f0ab8f25bae9579643bd0bee469a4 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Fri, 13 Jan 2023 12:17:25 +0100 Subject: [PATCH] feat: allow restricting filesystem walk to specific folders --- .../configuration/cli/trivy_config.md | 1 + .../configuration/cli/trivy_filesystem.md | 1 + .../configuration/cli/trivy_image.md | 1 + .../configuration/cli/trivy_kubernetes.md | 1 + .../configuration/cli/trivy_repository.md | 1 + .../configuration/cli/trivy_rootfs.md | 1 + .../configuration/cli/trivy_sbom.md | 1 + .../references/configuration/cli/trivy_vm.md | 1 + pkg/commands/app.go | 1 + pkg/commands/artifact/run.go | 1 + pkg/commands/artifact/scanner.go | 4 +- pkg/fanal/artifact/artifact.go | 2 + pkg/fanal/artifact/image/image.go | 2 +- pkg/fanal/artifact/local/fs.go | 2 +- pkg/fanal/cache/key.go | 3 +- pkg/fanal/walker/fs.go | 5 +- pkg/fanal/walker/fs_test.go | 3 +- pkg/fanal/walker/tar.go | 4 +- pkg/fanal/walker/tar_test.go | 3 +- pkg/fanal/walker/vm.go | 4 +- pkg/fanal/walker/walk.go | 43 +++++++++- pkg/fanal/walker/walk_test.go | 86 ++++++++++++++++++- pkg/flag/scan_flags.go | 11 +++ pkg/flag/scan_flags_test.go | 3 + 24 files changed, 168 insertions(+), 17 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 0d26452a10b1..2a75b1c4dec0 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -30,6 +30,7 @@ trivy config [flags] DIR --include-non-failures include successes and exceptions, available with '--scanners misconfig' --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 89d034caaa4d..4e783178b0ee 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -54,6 +54,7 @@ trivy filesystem [flags] PATH --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 10ac0518944b..cb28d3252300 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -72,6 +72,7 @@ trivy image [flags] IMAGE_NAME --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 93d44ad04c3e..0ffda0b3a66c 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -64,6 +64,7 @@ trivy kubernetes [flags] { cluster | all | specific resources like kubectl. eg: --no-progress suppress progress bar --node-collector-namespace string specify the namespace in which the node-collector job should be deployed (default "trivy-temp") --offline-scan do not issue API requests to identify dependencies + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index a88e9be5bf30..ade0207e9f32 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -54,6 +54,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index d04ee44ba113..97946f3f5f4f 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -56,6 +56,7 @@ trivy rootfs [flags] ROOTDIR --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 9f899ac977cb..5ccf13e8b141 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -41,6 +41,7 @@ trivy sbom [flags] SBOM_PATH --list-all-pkgs enabling the option will output all packages regardless of vulnerability --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index eb6506c7585d..d5657b0fcba3 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -50,6 +50,7 @@ trivy vm [flags] VM_IMAGE --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies + --only-dirs strings specify the directories where the traversal is allowed -o, --output string output file name --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") diff --git a/pkg/commands/app.go b/pkg/commands/app.go index fe39fb57925c..31ac64e297eb 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -637,6 +637,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { // Enable only '--skip-dirs' and '--skip-files' and disable other flags SkipDirs: &flag.SkipDirsFlag, SkipFiles: &flag.SkipFilesFlag, + OnlyDirs: &flag.OnlyDirsFlag, FilePatterns: &flag.FilePatternsFlag, } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 0d935f86e016..cebd4187f132 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -629,6 +629,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi DisabledAnalyzers: disabledAnalyzers(opts), SkipFiles: opts.SkipFiles, SkipDirs: opts.SkipDirs, + OnlyDirs: opts.OnlyDirs, FilePatterns: opts.FilePatterns, Offline: opts.OfflineScan, NoProgress: opts.NoProgress || opts.Quiet, diff --git a/pkg/commands/artifact/scanner.go b/pkg/commands/artifact/scanner.go index e5946193b789..2aeb98515d98 100644 --- a/pkg/commands/artifact/scanner.go +++ b/pkg/commands/artifact/scanner.go @@ -111,7 +111,7 @@ func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner // vmStandaloneScanner initializes a VM scanner in standalone mode func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { // TODO: The walker should be initialized in initializeVMScanner after https://github.com/aquasecurity/trivy/pull/5180 - w := walker.NewVM(conf.ArtifactOption.SkipFiles, conf.ArtifactOption.SkipDirs) + w := walker.NewVM(conf.ArtifactOption.SkipFiles, conf.ArtifactOption.SkipDirs, conf.ArtifactOption.OnlyDirs) s, cleanup, err := initializeVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, w, conf.ArtifactOption) if err != nil { @@ -123,7 +123,7 @@ func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scann // vmRemoteScanner initializes a VM scanner in client/server mode func vmRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { // TODO: The walker should be initialized in initializeVMScanner after https://github.com/aquasecurity/trivy/pull/5180 - w := walker.NewVM(conf.ArtifactOption.SkipFiles, conf.ArtifactOption.SkipDirs) + w := walker.NewVM(conf.ArtifactOption.SkipFiles, conf.ArtifactOption.SkipDirs, conf.ArtifactOption.OnlyDirs) s, cleanup, err := initializeRemoteVMScanner(ctx, conf.Target, conf.ArtifactCache, w, conf.ServerOption, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote vm scanner: %w", err) diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index bcb3250f8b7a..67a7c6ad21ff 100644 --- a/pkg/fanal/artifact/artifact.go +++ b/pkg/fanal/artifact/artifact.go @@ -16,6 +16,7 @@ type Option struct { DisabledHandlers []types.HandlerType SkipFiles []string SkipDirs []string + OnlyDirs []string FilePatterns []string NoProgress bool Insecure bool @@ -78,6 +79,7 @@ func (o *Option) Sort() { sort.Strings(o.SkipFiles) sort.Strings(o.SkipDirs) sort.Strings(o.FilePatterns) + sort.Strings(o.OnlyDirs) } type Artifact interface { diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index 782d13a86097..5208ecebe2a4 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -62,7 +62,7 @@ func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (a return Artifact{ image: img, cache: c, - walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs), + walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs, opt.OnlyDirs), analyzer: a, configAnalyzer: ca, handlerManager: handlerManager, diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index 8d7409cdfae3..c769a503ca53 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -47,7 +47,7 @@ func NewArtifact(rootPath string, c cache.ArtifactCache, opt artifact.Option) (a return Artifact{ rootPath: filepath.ToSlash(filepath.Clean(rootPath)), cache: c, - walker: walker.NewFS(buildPathsToSkip(rootPath, opt.SkipFiles), buildPathsToSkip(rootPath, opt.SkipDirs), + walker: walker.NewFS(buildPathsToSkip(rootPath, opt.SkipFiles), buildPathsToSkip(rootPath, opt.SkipDirs), buildPathsToSkip(rootPath, opt.OnlyDirs), opt.Parallel, opt.WalkOption.ErrorCallback), analyzer: a, handlerManager: handlerManager, diff --git a/pkg/fanal/cache/key.go b/pkg/fanal/cache/key.go index f9258caa866b..a580a8a1843f 100644 --- a/pkg/fanal/cache/key.go +++ b/pkg/fanal/cache/key.go @@ -29,8 +29,9 @@ func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[str HookVersions map[string]int SkipFiles []string SkipDirs []string + OnlyDirs []string `json:",omitempty"` FilePatterns []string `json:",omitempty"` - }{id, analyzerVersions, hookVersions, artifactOpt.SkipFiles, artifactOpt.SkipDirs, artifactOpt.FilePatterns} + }{id, analyzerVersions, hookVersions, artifactOpt.SkipFiles, artifactOpt.SkipDirs, artifactOpt.OnlyDirs, artifactOpt.FilePatterns} if err := json.NewEncoder(h).Encode(keyBase); err != nil { return "", xerrors.Errorf("json encode error: %w", err) diff --git a/pkg/fanal/walker/fs.go b/pkg/fanal/walker/fs.go index 863d3486997f..5bdb160c350c 100644 --- a/pkg/fanal/walker/fs.go +++ b/pkg/fanal/walker/fs.go @@ -20,7 +20,7 @@ type FS struct { errCallback ErrorCallback } -func NewFS(skipFiles, skipDirs []string, parallel int, errCallback ErrorCallback) FS { +func NewFS(skipFiles, skipDirs, onlyDirs []string, parallel int, errCallback ErrorCallback) FS { if errCallback == nil { errCallback = func(pathname string, err error) error { // ignore permission errors @@ -33,8 +33,7 @@ func NewFS(skipFiles, skipDirs []string, parallel int, errCallback ErrorCallback } return FS{ - walker: newWalker(skipFiles, skipDirs), - parallel: parallel, + walker: newWalker(skipFiles, skipDirs, onlyDirs), errCallback: errCallback, } } diff --git a/pkg/fanal/walker/fs_test.go b/pkg/fanal/walker/fs_test.go index aa84f6d23503..e562e337008e 100644 --- a/pkg/fanal/walker/fs_test.go +++ b/pkg/fanal/walker/fs_test.go @@ -18,6 +18,7 @@ func TestDir_Walk(t *testing.T) { type fields struct { skipFiles []string skipDirs []string + onlyDirs []string errCallback walker.ErrorCallback } tests := []struct { @@ -93,7 +94,7 @@ func TestDir_Walk(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := walker.NewFS(tt.fields.skipFiles, tt.fields.skipDirs, 1, tt.fields.errCallback) + w := walker.NewFS(tt.fields.skipFiles, tt.fields.skipDirs, tt.fields.onlyDirs, 1, tt.fields.errCallback) err := w.Walk(tt.rootDir, tt.analyzeFn) if tt.wantErr != "" { diff --git a/pkg/fanal/walker/tar.go b/pkg/fanal/walker/tar.go index d548d06f9c0a..4b300f5af931 100644 --- a/pkg/fanal/walker/tar.go +++ b/pkg/fanal/walker/tar.go @@ -25,10 +25,10 @@ type LayerTar struct { threshold int64 } -func NewLayerTar(skipFiles, skipDirs []string) LayerTar { +func NewLayerTar(skipFiles, skipDirs, onlyDirs []string) LayerTar { threshold := defaultSizeThreshold return LayerTar{ - walker: newWalker(skipFiles, skipDirs), + walker: newWalker(skipFiles, skipDirs, onlyDirs), threshold: threshold, } } diff --git a/pkg/fanal/walker/tar_test.go b/pkg/fanal/walker/tar_test.go index fed2f9efea7d..69fbfed3a56b 100644 --- a/pkg/fanal/walker/tar_test.go +++ b/pkg/fanal/walker/tar_test.go @@ -18,6 +18,7 @@ func TestLayerTar_Walk(t *testing.T) { type fields struct { skipFiles []string skipDirs []string + onlyDirs []string } tests := []struct { name string @@ -81,7 +82,7 @@ func TestLayerTar_Walk(t *testing.T) { f, err := os.Open("testdata/test.tar") require.NoError(t, err) - w := walker.NewLayerTar(tt.fields.skipFiles, tt.fields.skipDirs) + w := walker.NewLayerTar(tt.fields.skipFiles, tt.fields.skipDirs, tt.fields.onlyDirs) gotOpqDirs, gotWhFiles, err := w.Walk(f, tt.analyzeFn) if tt.wantErr != "" { diff --git a/pkg/fanal/walker/vm.go b/pkg/fanal/walker/vm.go index a45a0bf362d8..6aacc6f97fdb 100644 --- a/pkg/fanal/walker/vm.go +++ b/pkg/fanal/walker/vm.go @@ -39,10 +39,10 @@ type VM struct { analyzeFn WalkFunc } -func NewVM(skipFiles, skipDirs []string) *VM { +func NewVM(skipFiles, skipDirs, onlyDirs []string) *VM { threshold := defaultSizeThreshold return &VM{ - walker: newWalker(skipFiles, skipDirs), + walker: newWalker(skipFiles, skipDirs, onlyDirs), threshold: threshold, } } diff --git a/pkg/fanal/walker/walk.go b/pkg/fanal/walker/walk.go index cebc86ee76d7..3730e968393c 100644 --- a/pkg/fanal/walker/walk.go +++ b/pkg/fanal/walker/walk.go @@ -29,9 +29,15 @@ type WalkFunc func(filePath string, info os.FileInfo, opener analyzer.Opener) er type walker struct { skipFiles []string skipDirs []string + onlyDirs []pattern } -func newWalker(skipFiles, skipDirs []string) walker { +type pattern struct { + pattern string + base string +} + +func newWalker(skipFiles, skipDirs, onlyDirs []string) walker { var cleanSkipFiles, cleanSkipDirs []string for _, skipFile := range skipFiles { skipFile = filepath.ToSlash(filepath.Clean(skipFile)) @@ -45,9 +51,18 @@ func newWalker(skipFiles, skipDirs []string) walker { cleanSkipDirs = append(cleanSkipDirs, skipDir) } + var cleanOnlyDirs []pattern + for _, onlyDir := range onlyDirs { + onlyDir = filepath.ToSlash(filepath.Clean(onlyDir)) + onlyDir = strings.TrimLeft(onlyDir, "/") + base, _ := doublestar.SplitPattern(onlyDir) + cleanOnlyDirs = append(cleanOnlyDirs, pattern{base: base, pattern: onlyDir}) + } + return walker{ skipFiles: cleanSkipFiles, skipDirs: cleanSkipDirs, + onlyDirs: cleanOnlyDirs, } } @@ -64,6 +79,21 @@ func (w *walker) shouldSkipFile(filePath string) bool { return true } } + + if len(w.onlyDirs) > 0 { + dir := filepath.ToSlash(filepath.Dir(filePath)) + for _, onlyDir := range w.onlyDirs { + match, err := doublestar.Match(onlyDir.pattern, dir) + if err != nil { + return false // return early if bad pattern + } else if match { + log.Logger.Debugf("Skipping file: %s", filePath) + return false + } + } + return true + } + return false } @@ -86,5 +116,16 @@ func (w *walker) shouldSkipDir(dir string) bool { } } + if dir != "." && len(w.onlyDirs) > 0 { + for _, onlyDir := range w.onlyDirs { + if onlyDir.base == "." || strings.HasPrefix(onlyDir.base+"/", dir+"/") || strings.HasPrefix(dir+"/", onlyDir.base+"/") { + return false + } + } + + log.Logger.Debugf("Skipping directory: %s", dir) + return true + } + return false } diff --git a/pkg/fanal/walker/walk_test.go b/pkg/fanal/walker/walk_test.go index 4d52117aa1ce..ffcf862a7e26 100644 --- a/pkg/fanal/walker/walk_test.go +++ b/pkg/fanal/walker/walk_test.go @@ -54,7 +54,7 @@ func Test_shouldSkipFile(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprint(i), func(t *testing.T) { - w := newWalker(tc.skipFiles, nil) + w := newWalker(tc.skipFiles, nil, nil) for file, skipResult := range tc.skipMap { assert.Equal(t, skipResult, w.shouldSkipFile(filepath.ToSlash(filepath.Clean(file))), fmt.Sprintf("skipFiles: %s, file: %s", tc.skipFiles, file)) } @@ -115,10 +115,92 @@ func Test_shouldSkipDir(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprint(i), func(t *testing.T) { - w := newWalker(nil, tc.skipDirs) + w := newWalker(nil, tc.skipDirs, nil) for dir, skipResult := range tc.skipMap { assert.Equal(t, skipResult, w.shouldSkipDir(filepath.ToSlash(filepath.Clean(dir))), fmt.Sprintf("skipDirs: %s, dir: %s", tc.skipDirs, dir)) } }) } } + +func Test_onlyDir(t *testing.T) { + testCases := []struct { + skipDirs []string + onlyDirs []string + skipMap map[string][2]bool + }{ + { + skipDirs: nil, + onlyDirs: []string{"/etc/**"}, + skipMap: map[string][2]bool{ + "/etc/foo/bar": {false, false}, + "/var/log/bar": {true, true}, + }, + }, + { + skipDirs: nil, + onlyDirs: []string{"/**"}, + skipMap: map[string][2]bool{ + "/foo": {false, false}, + "/etc/foo": {false, false}, + "/var/log/bar": {false, false}, + }, + }, + { + skipDirs: nil, + onlyDirs: []string{"/*"}, + skipMap: map[string][2]bool{ + "/foo": {false, false}, + "/etc/foo": {false, false}, + "/etc/foo/bar": {true, false}, + }, + }, + { + skipDirs: nil, + onlyDirs: []string{"/etc/foo/*"}, + skipMap: map[string][2]bool{ + "/etc/foo/bar": {true, false}, + "/etc/foo2/bar": {true, false}, + "/var/etc/foo2/bar": {true, true}, + }, + }, + { + onlyDirs: []string{"/*/bar"}, + skipMap: map[string][2]bool{ + "/etc/bar/bar": {false, false}, + "/etc/foo/bar": {true, false}, + "/etc/bar/foo": {false, false}, + "/etc/foo/bar/foo": {true, false}, + }, + }, + { + onlyDirs: []string{"**/bar"}, + skipMap: map[string][2]bool{ + "/etc/bar/bar": {false, false}, + "/etc/foo/bar": {true, false}, + "/etc/bar/foo": {false, false}, + "/etc/foo/bar/foo": {false, false}, + }, + }, + { + onlyDirs: []string{"**/foo/*/bar"}, + skipMap: map[string][2]bool{ + "/foo/foo/bar/bar": {false, false}, + "/etc/foo/bar": {true, false}, + "/etc/bar/foo": {true, false}, + "/etc/foo/bar/bar": {true, false}, + }, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprint(i), func(t *testing.T) { + w := newWalker(nil, tc.skipDirs, tc.onlyDirs) + for file, skipResult := range tc.skipMap { + // todo: add check on skip dir + assert.Equal(t, skipResult[0], w.shouldSkipFile(filepath.ToSlash(filepath.Clean(file))), fmt.Sprintf("skipDirs: %s, dir: %s", tc.skipDirs, file)) + assert.Equal(t, skipResult[1], w.shouldSkipDir(filepath.ToSlash(filepath.Clean(file))), fmt.Sprintf("skipDirs: %s, dir: %s", tc.skipDirs, file)) + } + }) + } +} diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index 0464436a4f11..96b07b282ef6 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -21,6 +21,12 @@ var ( Default: []string{}, Usage: "specify the files or glob patterns to skip", } + OnlyDirsFlag = Flag{ + Name: "only-dirs", + ConfigName: "scan.only-dirs", + Default: []string{}, + Usage: "specify the directories where the traversal is allowed", + } OfflineScanFlag = Flag{ Name: "offline-scan", ConfigName: "scan.offline", @@ -104,6 +110,7 @@ var ( type ScanFlagGroup struct { SkipDirs *Flag SkipFiles *Flag + OnlyDirs *Flag OfflineScan *Flag Scanners *Flag FilePatterns *Flag @@ -118,6 +125,7 @@ type ScanOptions struct { Target string SkipDirs []string SkipFiles []string + OnlyDirs []string OfflineScan bool Scanners types.Scanners FilePatterns []string @@ -131,6 +139,7 @@ func NewScanFlagGroup() *ScanFlagGroup { return &ScanFlagGroup{ SkipDirs: &SkipDirsFlag, SkipFiles: &SkipFilesFlag, + OnlyDirs: &OnlyDirsFlag, OfflineScan: &OfflineScanFlag, Scanners: &ScannersFlag, FilePatterns: &FilePatternsFlag, @@ -150,6 +159,7 @@ func (f *ScanFlagGroup) Flags() []*Flag { return []*Flag{ f.SkipDirs, f.SkipFiles, + f.OnlyDirs, f.OfflineScan, f.Scanners, f.FilePatterns, @@ -177,6 +187,7 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { Target: target, SkipDirs: getStringSlice(f.SkipDirs), SkipFiles: getStringSlice(f.SkipFiles), + OnlyDirs: getStringSlice(f.OnlyDirs), OfflineScan: getBool(f.OfflineScan), Scanners: getUnderlyingStringSlice[types.Scanner](f.Scanners), FilePatterns: getStringSlice(f.FilePatterns), diff --git a/pkg/flag/scan_flags_test.go b/pkg/flag/scan_flags_test.go index 7c9d2ba42457..4f17e23bd54f 100644 --- a/pkg/flag/scan_flags_test.go +++ b/pkg/flag/scan_flags_test.go @@ -15,6 +15,7 @@ func TestScanFlagGroup_ToOptions(t *testing.T) { type fields struct { skipDirs []string skipFiles []string + onlyDirs []string offlineScan bool scanners string } @@ -111,6 +112,7 @@ func TestScanFlagGroup_ToOptions(t *testing.T) { t.Run(tt.name, func(t *testing.T) { viper.Set(flag.SkipDirsFlag.ConfigName, tt.fields.skipDirs) viper.Set(flag.SkipFilesFlag.ConfigName, tt.fields.skipFiles) + viper.Set(flag.OnlyDirsFlag.ConfigName, tt.fields.onlyDirs) viper.Set(flag.OfflineScanFlag.ConfigName, tt.fields.offlineScan) viper.Set(flag.ScannersFlag.ConfigName, tt.fields.scanners) @@ -118,6 +120,7 @@ func TestScanFlagGroup_ToOptions(t *testing.T) { f := &flag.ScanFlagGroup{ SkipDirs: &flag.SkipDirsFlag, SkipFiles: &flag.SkipFilesFlag, + OnlyDirs: &flag.OnlyDirsFlag, OfflineScan: &flag.OfflineScanFlag, Scanners: &flag.ScannersFlag, }