diff --git a/docs/docs/misconfiguration/options/others.md b/docs/docs/misconfiguration/options/others.md index 0e97435e0794..ec6a7efaac3c 100644 --- a/docs/docs/misconfiguration/options/others.md +++ b/docs/docs/misconfiguration/options/others.md @@ -2,21 +2,3 @@ !!! hint See also [Others](../../vulnerability/examples/others.md) in Vulnerability section. - -## File patterns -When a directory is given as an input, Trivy will recursively look for and test all files based on file patterns. -The default file patterns are [here](../custom/index.md). - -In addition to the default file patterns, the `--file-patterns` option takes regexp patterns to look for your files. -For example, it may be useful when your file name of Dockerfile doesn't match the default patterns. - -This can be repeated for specifying multiple file patterns. -Allowed values are here: - -- dockerfile -- yaml -- json -- toml -- hcl - -For more details, see [an example](https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/examples/misconf/file-patterns) \ No newline at end of file diff --git a/docs/docs/references/customization/config-file.md b/docs/docs/references/customization/config-file.md index e8943f899a64..710a7402168d 100644 --- a/docs/docs/references/customization/config-file.md +++ b/docs/docs/references/customization/config-file.md @@ -82,6 +82,11 @@ Available in client/server mode ```yaml scan: + # Same as '--file-patterns' + # Default is empty + file-patterns: + - + # Same as '--skip-dirs' # Default is empty skip-dirs: @@ -195,11 +200,6 @@ Available with misconfiguration scanning ```yaml misconfiguration: - # Same as '--file-patterns' - # Default is empty - file-patterns: - - - # Same as '--include-non-failures' # Default is false include-non-failures: false diff --git a/docs/docs/vulnerability/examples/others.md b/docs/docs/vulnerability/examples/others.md index 1b09aa121791..67e98fa7922a 100644 --- a/docs/docs/vulnerability/examples/others.md +++ b/docs/docs/vulnerability/examples/others.md @@ -16,6 +16,22 @@ If your image contains lock files which are not maintained by you, you can skip $ trivy image --skip-dirs /var/lib/gems/2.5.0/gems/fluent-plugin-detect-exceptions-0.0.13 --skip-dirs "/var/lib/gems/2.5.0/gems/http_parser.rb-0.6.0" quay.io/fluentd_elasticsearch/fluentd:v2.9.0 ``` +## File patterns +When a directory is given as an input, Trivy will recursively look for and test all files based on file patterns. +The default file patterns are [here](../custom/index.md). + +In addition to the default file patterns, the `--file-patterns` option takes regexp patterns to look for your files. +For example, it may be useful when your file name of Dockerfile doesn't match the default patterns. + +This can be repeated for specifying multiple file patterns. + +A file pattern contains the analyzer it is used for, and the pattern itself, joined by a semicolon. For example: +``` +--file-patterns "dockerfile:.*.docker" --file-patterns "yaml:deployment" --file-patterns "pip:requirements-.*\.txt" +``` + +For more details, see [an example](https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/examples/misconf/file-patterns) + ## Exit Code By default, `Trivy` exits with code 0 even when vulnerabilities are detected. Use the `--exit-code` option if you want to exit with a non-zero exit code. diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index e90e22de875a..95efad298ceb 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -447,6 +447,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand ListAllPackages: opts.ListAllPkgs, LicenseCategories: opts.LicenseCategories, + FilePatterns: opts.FilePatterns, } if slices.Contains(opts.SecurityChecks, types.SecurityCheckVulnerability) { @@ -463,7 +464,6 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi Namespaces: append(opts.PolicyNamespaces, defaultPolicyNamespaces...), PolicyPaths: opts.PolicyPaths, DataPaths: opts.DataPaths, - FilePatterns: opts.FilePatterns, HelmValues: opts.HelmValues, HelmValueFiles: opts.HelmValueFiles, HelmFileValues: opts.HelmFileValues, @@ -503,6 +503,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi DisabledAnalyzers: disabledAnalyzers(opts), SkipFiles: opts.SkipFiles, SkipDirs: opts.SkipDirs, + FilePatterns: opts.FilePatterns, InsecureSkipTLS: opts.Insecure, Offline: opts.OfflineScan, NoProgress: opts.NoProgress || opts.Quiet, diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index 3fd1b0078658..99d2ffdb6198 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -3,6 +3,7 @@ package all import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/buildinfo" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/command/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/deps" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/nuget" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/binary" diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index c8222278adac..02016b336346 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -5,6 +5,7 @@ import ( "errors" "io/fs" "os" + "regexp" "sort" "strings" "sync" @@ -91,6 +92,7 @@ type Opener func() (dio.ReadSeekCloserAt, error) type AnalyzerGroup struct { analyzers []analyzer configAnalyzers []configAnalyzer + filePatterns map[Type][]*regexp.Regexp } type AnalysisResult struct { @@ -266,12 +268,36 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type, return true } -func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type) AnalyzerGroup { +const separator = ":" + +func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type, filePatterns []string) (AnalyzerGroup, error) { if groupName == "" { groupName = GroupBuiltin } - var group AnalyzerGroup + group := AnalyzerGroup{ + filePatterns: map[Type][]*regexp.Regexp{}, + } + for _, p := range filePatterns { + // e.g. "dockerfile:my_dockerfile_*" + s := strings.SplitN(p, separator, 2) + if len(s) != 2 { + return group, xerrors.Errorf("invalid file pattern (%s)", p) + } + + fileType, pattern := s[0], s[1] + r, err := regexp.Compile(pattern) + if err != nil { + return group, xerrors.Errorf("invalid file regexp (%s): %w", p, err) + } + + if _, ok := group.filePatterns[Type(fileType)]; !ok { + group.filePatterns[Type(fileType)] = []*regexp.Regexp{} + } + + group.filePatterns[Type(fileType)] = append(group.filePatterns[Type(fileType)], r) + } + for analyzerType, a := range analyzers { if !belongToGroup(groupName, analyzerType, disabledAnalyzers, a) { continue @@ -286,7 +312,7 @@ func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type) AnalyzerGroup { group.configAnalyzers = append(group.configAnalyzers, a) } - return group + return group, nil } // AnalyzerVersions returns analyzer version identifier used for cache keys. @@ -313,14 +339,16 @@ func (ag AnalyzerGroup) AnalyzeFile(ctx context.Context, wg *sync.WaitGroup, lim return nil } + // filepath extracted from tar file doesn't have the prefix "/" + cleanPath := strings.TrimLeft(filePath, "/") + for _, a := range ag.analyzers { // Skip disabled analyzers if slices.Contains(disabled, a.Type()) { continue } - // filepath extracted from tar file doesn't have the prefix "/" - if !a.Required(strings.TrimLeft(filePath, "/"), info) { + if !ag.filePatternMatch(a.Type(), cleanPath) && !a.Required(cleanPath, info) { continue } rc, err := opener() @@ -375,3 +403,12 @@ func (ag AnalyzerGroup) AnalyzeImageConfig(targetOS types.OS, configBlob []byte) } return nil } + +func (ag AnalyzerGroup) filePatternMatch(analyzerType Type, filePath string) bool { + for _, pattern := range ag.filePatterns[analyzerType] { + if pattern.MatchString(filePath) { + return true + } + } + return false +} diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index c68a54c9dcbc..92b7dd9346b9 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -281,6 +281,7 @@ func TestAnalyzeFile(t *testing.T) { filePath string testFilePath string disabledAnalyzers []analyzer.Type + filePatterns []string } tests := []struct { name string @@ -377,6 +378,28 @@ func TestAnalyzeFile(t *testing.T) { }, want: &analyzer.AnalysisResult{}, }, + { + name: "happy path with library analyzer file pattern regex", + args: args{ + filePath: "/app/Gemfile-dev.lock", + testFilePath: "testdata/app/Gemfile.lock", + filePatterns: []string{"bundler:Gemfile(-.*)?\\.lock"}, + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: "bundler", + FilePath: "/app/Gemfile-dev.lock", + Libraries: []types.Package{ + { + Name: "actioncable", + Version: "5.2.3", + }, + }, + }, + }, + }, + }, { name: "ignore permission error", args: args{ @@ -393,6 +416,24 @@ func TestAnalyzeFile(t *testing.T) { }, wantErr: "unable to open /lib/apk/db/installed", }, + { + name: "sad path with broken file pattern regex", + args: args{ + filePath: "/app/Gemfile-dev.lock", + testFilePath: "testdata/app/Gemfile.lock", + filePatterns: []string{"bundler:Gemfile(-.*?\\.lock"}, + }, + wantErr: "error parsing regexp", + }, + { + name: "sad path with broken file pattern", + args: args{ + filePath: "/app/Gemfile-dev.lock", + testFilePath: "testdata/app/Gemfile.lock", + filePatterns: []string{"Gemfile(-.*)?\\.lock"}, + }, + wantErr: "invalid file pattern", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -400,7 +441,13 @@ func TestAnalyzeFile(t *testing.T) { limit := semaphore.NewWeighted(3) got := new(analyzer.AnalysisResult) - a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers) + a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers, tt.args.filePatterns) + if err != nil && tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) info, err := os.Stat(tt.args.testFilePath) require.NoError(t, err) @@ -440,6 +487,7 @@ func TestAnalyzeConfig(t *testing.T) { targetOS types.OS configBlob []byte disabledAnalyzers []analyzer.Type + filePatterns []string } tests := []struct { name string @@ -482,7 +530,8 @@ func TestAnalyzeConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers) + a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers, tt.args.filePatterns) + require.NoError(t, err) got := a.AnalyzeImageConfig(tt.args.targetOS, tt.args.configBlob) assert.Equal(t, tt.want, got) }) @@ -517,7 +566,8 @@ func TestAnalyzer_AnalyzerVersions(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled) + a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled, nil) + require.NoError(t, err) got := a.AnalyzerVersions() fmt.Printf("%v\n", got) assert.Equal(t, tt.want, got) @@ -549,7 +599,8 @@ func TestAnalyzer_ImageConfigAnalyzerVersions(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled) + a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled, nil) + require.NoError(t, err) got := a.ImageConfigAnalyzerVersions() assert.Equal(t, tt.want, got) }) diff --git a/pkg/fanal/analyzer/config/all/import.go b/pkg/fanal/analyzer/config/all/import.go new file mode 100644 index 000000000000..75b01e10e86f --- /dev/null +++ b/pkg/fanal/analyzer/config/all/import.go @@ -0,0 +1,9 @@ +package all + +import ( + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/dockerfile" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/helm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/json" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/yaml" +) diff --git a/pkg/fanal/analyzer/config/config.go b/pkg/fanal/analyzer/config/config.go index d32bcd653d9c..595e21cd131e 100644 --- a/pkg/fanal/analyzer/config/config.go +++ b/pkg/fanal/analyzer/config/config.go @@ -1,29 +1,13 @@ package config import ( - "regexp" "sort" - "strings" - - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/helm" - - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/dockerfile" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/json" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/yaml" - "github.com/aquasecurity/trivy/pkg/fanal/types" ) -const separator = ":" - type ScannerOption struct { Trace bool RegoOnly bool Namespaces []string - FilePatterns []string PolicyPaths []string DataPaths []string DisableEmbeddedPolicies bool @@ -37,44 +21,6 @@ type ScannerOption struct { func (o *ScannerOption) Sort() { sort.Strings(o.Namespaces) - sort.Strings(o.FilePatterns) sort.Strings(o.PolicyPaths) sort.Strings(o.DataPaths) } - -func RegisterConfigAnalyzers(filePatterns []string) error { - var dockerRegexp, jsonRegexp, yamlRegexp, helmRegexp *regexp.Regexp - for _, p := range filePatterns { - // e.g. "dockerfile:my_dockerfile_*" - s := strings.SplitN(p, separator, 2) - if len(s) != 2 { - return xerrors.Errorf("invalid file pattern (%s)", p) - } - fileType, pattern := s[0], s[1] - r, err := regexp.Compile(pattern) - if err != nil { - return xerrors.Errorf("invalid file regexp (%s): %w", p, err) - } - - switch fileType { - case types.Dockerfile: - dockerRegexp = r - case types.JSON: - jsonRegexp = r - case types.YAML: - yamlRegexp = r - case types.Helm: - helmRegexp = r - default: - return xerrors.Errorf("unknown file type: %s, pattern: %s", fileType, pattern) - } - } - - analyzer.RegisterAnalyzer(dockerfile.NewConfigAnalyzer(dockerRegexp)) - analyzer.RegisterAnalyzer(terraform.NewConfigAnalyzer()) - analyzer.RegisterAnalyzer(json.NewConfigAnalyzer(jsonRegexp)) - analyzer.RegisterAnalyzer(yaml.NewConfigAnalyzer(yamlRegexp)) - analyzer.RegisterAnalyzer(helm.NewConfigAnalyzer(helmRegexp)) - - return nil -} diff --git a/pkg/fanal/analyzer/config/config_test.go b/pkg/fanal/analyzer/config/config_test.go index 2376236e2788..3aac67e6f42b 100644 --- a/pkg/fanal/analyzer/config/config_test.go +++ b/pkg/fanal/analyzer/config/config_test.go @@ -10,10 +10,9 @@ import ( func TestScannerOption_Sort(t *testing.T) { type fields struct { - Namespaces []string - FilePatterns []string - PolicyPaths []string - DataPaths []string + Namespaces []string + PolicyPaths []string + DataPaths []string } tests := []struct { name string @@ -23,25 +22,22 @@ func TestScannerOption_Sort(t *testing.T) { { name: "happy path", fields: fields{ - Namespaces: []string{"main", "custom", "default"}, - FilePatterns: []string{"dockerfile:foo*", "yaml:yml_*"}, - PolicyPaths: []string{"policy"}, - DataPaths: []string{"data/b", "data/c", "data/a"}, + Namespaces: []string{"main", "custom", "default"}, + PolicyPaths: []string{"policy"}, + DataPaths: []string{"data/b", "data/c", "data/a"}, }, want: config.ScannerOption{ - Namespaces: []string{"custom", "default", "main"}, - FilePatterns: []string{"dockerfile:foo*", "yaml:yml_*"}, - PolicyPaths: []string{"policy"}, - DataPaths: []string{"data/a", "data/b", "data/c"}, + Namespaces: []string{"custom", "default", "main"}, + PolicyPaths: []string{"policy"}, + DataPaths: []string{"data/a", "data/b", "data/c"}, }, }, { name: "missing some fields", fields: fields{ - Namespaces: []string{"main"}, - FilePatterns: nil, - PolicyPaths: nil, - DataPaths: nil, + Namespaces: []string{"main"}, + PolicyPaths: nil, + DataPaths: nil, }, want: config.ScannerOption{ Namespaces: []string{"main"}, @@ -51,10 +47,9 @@ func TestScannerOption_Sort(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := config.ScannerOption{ - Namespaces: tt.fields.Namespaces, - FilePatterns: tt.fields.FilePatterns, - PolicyPaths: tt.fields.PolicyPaths, - DataPaths: tt.fields.DataPaths, + Namespaces: tt.fields.Namespaces, + PolicyPaths: tt.fields.PolicyPaths, + DataPaths: tt.fields.DataPaths, } o.Sort() diff --git a/pkg/fanal/analyzer/config/dockerfile/docker.go b/pkg/fanal/analyzer/config/dockerfile/docker.go index 5859e4687ee1..666bad5f3ffb 100644 --- a/pkg/fanal/analyzer/config/dockerfile/docker.go +++ b/pkg/fanal/analyzer/config/dockerfile/docker.go @@ -5,7 +5,6 @@ import ( "io" "os" "path/filepath" - "regexp" "strings" "golang.org/x/xerrors" @@ -14,21 +13,17 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) +func init() { + analyzer.RegisterAnalyzer(&dockerConfigAnalyzer{}) +} + const version = 1 var requiredFiles = []string{"Dockerfile", "Containerfile"} -type ConfigAnalyzer struct { - filePattern *regexp.Regexp -} - -func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { - return ConfigAnalyzer{ - filePattern: filePattern, - } -} +type dockerConfigAnalyzer struct{} -func (s ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { +func (s dockerConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { b, err := io.ReadAll(input.Content) if err != nil { return nil, xerrors.Errorf("failed to read %s: %w", input.FilePath, err) @@ -50,11 +45,7 @@ func (s ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) // Required does a case-insensitive check for filePath and returns true if // filePath equals/startsWith/hasExtension requiredFiles -func (s ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { - if s.filePattern != nil && s.filePattern.MatchString(filePath) { - return true - } - +func (s dockerConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { base := filepath.Base(filePath) ext := filepath.Ext(base) for _, file := range requiredFiles { @@ -69,10 +60,10 @@ func (s ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { return false } -func (s ConfigAnalyzer) Type() analyzer.Type { +func (s dockerConfigAnalyzer) Type() analyzer.Type { return analyzer.TypeDockerfile } -func (s ConfigAnalyzer) Version() int { +func (s dockerConfigAnalyzer) Version() int { return version } diff --git a/pkg/fanal/analyzer/config/dockerfile/docker_test.go b/pkg/fanal/analyzer/config/dockerfile/docker_test.go index dbcc9a6842b1..d81fcb04d492 100644 --- a/pkg/fanal/analyzer/config/dockerfile/docker_test.go +++ b/pkg/fanal/analyzer/config/dockerfile/docker_test.go @@ -1,16 +1,14 @@ -package dockerfile_test +package dockerfile import ( "context" "os" - "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/dockerfile" "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -68,7 +66,7 @@ COPY --from=build /bar /bar require.NoError(t, err) defer f.Close() - a := dockerfile.NewConfigAnalyzer(nil) + a := dockerConfigAnalyzer{} ctx := context.Background() got, err := a.Analyze(ctx, analyzer.AnalysisInput{ FilePath: tt.inputFile, @@ -88,10 +86,9 @@ COPY --from=build /bar /bar func Test_dockerConfigAnalyzer_Required(t *testing.T) { tests := []struct { - name string - filePattern *regexp.Regexp - filePath string - want bool + name string + filePath string + want bool }{ { name: "dockerfile", @@ -143,16 +140,10 @@ func Test_dockerConfigAnalyzer_Required(t *testing.T) { filePath: "deployment.json", want: false, }, - { - name: "file pattern", - filePattern: regexp.MustCompile(`foo*`), - filePath: "foo_file", - want: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := dockerfile.NewConfigAnalyzer(tt.filePattern) + s := dockerConfigAnalyzer{} got := s.Required(tt.filePath, nil) assert.Equal(t, tt.want, got) }) @@ -160,7 +151,7 @@ func Test_dockerConfigAnalyzer_Required(t *testing.T) { } func Test_dockerConfigAnalyzer_Type(t *testing.T) { - s := dockerfile.NewConfigAnalyzer(nil) + s := dockerConfigAnalyzer{} want := analyzer.TypeDockerfile got := s.Type() assert.Equal(t, want, got) diff --git a/pkg/fanal/analyzer/config/helm/helm.go b/pkg/fanal/analyzer/config/helm/helm.go index 129c4c36fb9a..ad3ebc687302 100644 --- a/pkg/fanal/analyzer/config/helm/helm.go +++ b/pkg/fanal/analyzer/config/helm/helm.go @@ -8,7 +8,6 @@ import ( "io" "os" "path/filepath" - "regexp" "strings" "golang.org/x/xerrors" @@ -18,21 +17,17 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) +func init() { + analyzer.RegisterAnalyzer(&helmConfigAnalyzer{}) +} + const version = 1 const maxTarSize = 209_715_200 // 200MB -type ConfigAnalyzer struct { - filePattern *regexp.Regexp -} +type helmConfigAnalyzer struct{} -func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { - return ConfigAnalyzer{ - filePattern: filePattern, - } -} - -func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { +func (a helmConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { if isArchive(input.FilePath) { if !isHelmChart(input.FilePath, input.Content) { return nil, nil @@ -62,11 +57,7 @@ func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) }, nil } -func (a ConfigAnalyzer) Required(filePath string, info os.FileInfo) bool { - if a.filePattern != nil && a.filePattern.MatchString(filePath) { - return true - } - +func (a helmConfigAnalyzer) Required(filePath string, info os.FileInfo) bool { if info.Size() > maxTarSize { // tarball is too big to be Helm chart - move on return false @@ -88,11 +79,11 @@ func (a ConfigAnalyzer) Required(filePath string, info os.FileInfo) bool { return false } -func (ConfigAnalyzer) Type() analyzer.Type { +func (helmConfigAnalyzer) Type() analyzer.Type { return analyzer.TypeHelm } -func (ConfigAnalyzer) Version() int { +func (helmConfigAnalyzer) Version() int { return version } diff --git a/pkg/fanal/analyzer/config/helm/helm_test.go b/pkg/fanal/analyzer/config/helm/helm_test.go index b41fbaf17d03..9e04e2e108b9 100644 --- a/pkg/fanal/analyzer/config/helm/helm_test.go +++ b/pkg/fanal/analyzer/config/helm/helm_test.go @@ -3,7 +3,6 @@ package helm import ( "context" "os" - "regexp" "testing" "github.com/stretchr/testify/assert" @@ -340,7 +339,7 @@ affinity: {} info, err := os.Stat(tt.inputFile) require.NoError(t, err) - a := NewConfigAnalyzer(nil) + a := helmConfigAnalyzer{} ctx := context.Background() got, err := a.Analyze(ctx, analyzer.AnalysisInput{ FilePath: tt.inputFile, @@ -361,10 +360,9 @@ affinity: {} func Test_helmConfigAnalyzer_Required(t *testing.T) { tests := []struct { - name string - filePattern *regexp.Regexp - filePath string - want bool + name string + filePath string + want bool }{ { name: "yaml", @@ -406,17 +404,10 @@ func Test_helmConfigAnalyzer_Required(t *testing.T) { filePath: "testdata/nope.tgz", want: true, // its a tarball after all }, - - { - name: "file pattern", - filePattern: regexp.MustCompile(`foo*`), - filePath: "foo_file", - want: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := NewConfigAnalyzer(tt.filePattern) + s := helmConfigAnalyzer{} info, _ := os.Stat(tt.filePath) @@ -427,7 +418,7 @@ func Test_helmConfigAnalyzer_Required(t *testing.T) { } func Test_helmConfigAnalyzer_Type(t *testing.T) { - s := NewConfigAnalyzer(nil) + s := helmConfigAnalyzer{} want := analyzer.TypeHelm got := s.Type() diff --git a/pkg/fanal/analyzer/config/json/json.go b/pkg/fanal/analyzer/config/json/json.go index e6c47a3ed621..b591c58c395c 100644 --- a/pkg/fanal/analyzer/config/json/json.go +++ b/pkg/fanal/analyzer/config/json/json.go @@ -5,7 +5,6 @@ import ( "io" "os" "path/filepath" - "regexp" "golang.org/x/xerrors" @@ -13,6 +12,10 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) +func init() { + analyzer.RegisterAnalyzer(&jsonConfigAnalyzer{}) +} + const version = 1 var ( @@ -20,17 +23,9 @@ var ( excludedFiles = []string{types.NpmPkgLock, types.NuGetPkgsLock, types.NuGetPkgsConfig} ) -type ConfigAnalyzer struct { - filePattern *regexp.Regexp -} - -func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { - return ConfigAnalyzer{ - filePattern: filePattern, - } -} +type jsonConfigAnalyzer struct{} -func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { +func (a jsonConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { b, err := io.ReadAll(input.Content) if err != nil { return nil, xerrors.Errorf("failed to read %s: %w", input.FilePath, err) @@ -50,11 +45,7 @@ func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) }, nil } -func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { - if a.filePattern != nil && a.filePattern.MatchString(filePath) { - return true - } - +func (a jsonConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { filename := filepath.Base(filePath) for _, excludedFile := range excludedFiles { if filename == excludedFile { @@ -65,10 +56,10 @@ func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { return filepath.Ext(filePath) == requiredExt } -func (ConfigAnalyzer) Type() analyzer.Type { +func (jsonConfigAnalyzer) Type() analyzer.Type { return analyzer.TypeJSON } -func (ConfigAnalyzer) Version() int { +func (jsonConfigAnalyzer) Version() int { return version } diff --git a/pkg/fanal/analyzer/config/json/json_test.go b/pkg/fanal/analyzer/config/json/json_test.go index a136b29d7512..014ca80ae3e1 100644 --- a/pkg/fanal/analyzer/config/json/json_test.go +++ b/pkg/fanal/analyzer/config/json/json_test.go @@ -1,16 +1,14 @@ -package json_test +package json import ( "context" "os" - "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/json" "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -133,7 +131,7 @@ func Test_jsonConfigAnalyzer_Analyze(t *testing.T) { require.NoError(t, err) defer f.Close() - s := json.NewConfigAnalyzer(nil) + s := jsonConfigAnalyzer{} ctx := context.Background() got, err := s.Analyze(ctx, analyzer.AnalysisInput{ @@ -154,10 +152,9 @@ func Test_jsonConfigAnalyzer_Analyze(t *testing.T) { func Test_jsonConfigAnalyzer_Required(t *testing.T) { tests := []struct { - name string - filePattern *regexp.Regexp - filePath string - want bool + name string + filePath string + want bool }{ { name: "json", @@ -174,16 +171,10 @@ func Test_jsonConfigAnalyzer_Required(t *testing.T) { filePath: "package-lock.json", want: false, }, - { - name: "file pattern", - filePattern: regexp.MustCompile(`foo*`), - filePath: "foo_file", - want: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := json.NewConfigAnalyzer(tt.filePattern) + s := jsonConfigAnalyzer{} got := s.Required(tt.filePath, nil) assert.Equal(t, tt.want, got) @@ -192,7 +183,7 @@ func Test_jsonConfigAnalyzer_Required(t *testing.T) { } func Test_jsonConfigAnalyzer_Type(t *testing.T) { - s := json.NewConfigAnalyzer(nil) + s := jsonConfigAnalyzer{} want := analyzer.TypeJSON got := s.Type() diff --git a/pkg/fanal/analyzer/config/terraform/terraform.go b/pkg/fanal/analyzer/config/terraform/terraform.go index 99d2a3b121e6..a0a89642aa41 100644 --- a/pkg/fanal/analyzer/config/terraform/terraform.go +++ b/pkg/fanal/analyzer/config/terraform/terraform.go @@ -13,19 +13,18 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) +func init() { + analyzer.RegisterAnalyzer(&terraformConfigAnalyzer{}) +} + const version = 1 var requiredExts = []string{".tf", ".tf.json"} -type ConfigAnalyzer struct { -} - -func NewConfigAnalyzer() ConfigAnalyzer { - return ConfigAnalyzer{} -} +type terraformConfigAnalyzer struct{} // Analyze returns a name of Terraform file -func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { +func (a terraformConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { b, err := io.ReadAll(input.Content) if err != nil { return nil, xerrors.Errorf("read error (%s): %w", input.FilePath, err) @@ -44,14 +43,14 @@ func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) }, nil } -func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { +func (a terraformConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { return slices.Contains(requiredExts, filepath.Ext(filePath)) } -func (ConfigAnalyzer) Type() analyzer.Type { +func (terraformConfigAnalyzer) Type() analyzer.Type { return analyzer.TypeTerraform } -func (ConfigAnalyzer) Version() int { +func (terraformConfigAnalyzer) Version() int { return version } diff --git a/pkg/fanal/analyzer/config/terraform/terraform_test.go b/pkg/fanal/analyzer/config/terraform/terraform_test.go index 225cb25b614f..4e9efa74047d 100644 --- a/pkg/fanal/analyzer/config/terraform/terraform_test.go +++ b/pkg/fanal/analyzer/config/terraform/terraform_test.go @@ -1,4 +1,4 @@ -package terraform_test +package terraform import ( "bytes" @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform" "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -41,7 +40,7 @@ func TestConfigAnalyzer_Analyze(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := terraform.ConfigAnalyzer{} + a := terraformConfigAnalyzer{} ctx := context.Background() got, err := a.Analyze(ctx, tt.input) @@ -75,7 +74,7 @@ func TestConfigAnalyzer_Required(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := terraform.ConfigAnalyzer{} + a := terraformConfigAnalyzer{} got := a.Required(tt.filePath, nil) assert.Equal(t, tt.want, got) }) diff --git a/pkg/fanal/analyzer/config/yaml/yaml.go b/pkg/fanal/analyzer/config/yaml/yaml.go index 6edb3c0e84a0..e25c1e30b062 100644 --- a/pkg/fanal/analyzer/config/yaml/yaml.go +++ b/pkg/fanal/analyzer/config/yaml/yaml.go @@ -5,7 +5,6 @@ import ( "io" "os" "path/filepath" - "regexp" "golang.org/x/xerrors" @@ -13,21 +12,17 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) +func init() { + analyzer.RegisterAnalyzer(&yamlConfigAnalyzer{}) +} + const version = 1 var requiredExts = []string{".yaml", ".yml"} -type ConfigAnalyzer struct { - filePattern *regexp.Regexp -} - -func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { - return ConfigAnalyzer{ - filePattern: filePattern, - } -} +type yamlConfigAnalyzer struct{} -func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { +func (a yamlConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { b, err := io.ReadAll(input.Content) if err != nil { return nil, xerrors.Errorf("failed to read %s: %w", input.FilePath, err) @@ -47,11 +42,7 @@ func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) }, nil } -func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { - if a.filePattern != nil && a.filePattern.MatchString(filePath) { - return true - } - +func (a yamlConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { ext := filepath.Ext(filePath) for _, required := range requiredExts { if ext == required { @@ -61,10 +52,10 @@ func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { return false } -func (ConfigAnalyzer) Type() analyzer.Type { +func (yamlConfigAnalyzer) Type() analyzer.Type { return analyzer.TypeYaml } -func (ConfigAnalyzer) Version() int { +func (yamlConfigAnalyzer) Version() int { return version } diff --git a/pkg/fanal/analyzer/config/yaml/yaml_test.go b/pkg/fanal/analyzer/config/yaml/yaml_test.go index e1dab274a6ec..c71ffa0b56d7 100644 --- a/pkg/fanal/analyzer/config/yaml/yaml_test.go +++ b/pkg/fanal/analyzer/config/yaml/yaml_test.go @@ -1,16 +1,14 @@ -package yaml_test +package yaml import ( "context" "os" - "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/yaml" "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -163,7 +161,7 @@ spec: require.NoError(t, err) defer f.Close() - a := yaml.NewConfigAnalyzer(nil) + a := yamlConfigAnalyzer{} ctx := context.Background() got, err := a.Analyze(ctx, analyzer.AnalysisInput{ FilePath: tt.inputFile, @@ -183,10 +181,9 @@ spec: func Test_yamlConfigAnalyzer_Required(t *testing.T) { tests := []struct { - name string - filePattern *regexp.Regexp - filePath string - want bool + name string + filePath string + want bool }{ { name: "yaml", @@ -203,16 +200,10 @@ func Test_yamlConfigAnalyzer_Required(t *testing.T) { filePath: "deployment.json", want: false, }, - { - name: "file pattern", - filePattern: regexp.MustCompile(`foo*`), - filePath: "foo_file", - want: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := yaml.NewConfigAnalyzer(tt.filePattern) + s := yamlConfigAnalyzer{} got := s.Required(tt.filePath, nil) assert.Equal(t, tt.want, got) @@ -221,7 +212,7 @@ func Test_yamlConfigAnalyzer_Required(t *testing.T) { } func Test_yamlConfigAnalyzer_Type(t *testing.T) { - s := yaml.NewConfigAnalyzer(nil) + s := yamlConfigAnalyzer{} want := analyzer.TypeYaml got := s.Type() diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index 530b1ba31021..cee5ad004690 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 + FilePatterns []string NoProgress bool Offline bool InsecureSkipTLS bool @@ -34,6 +35,7 @@ func (o *Option) Sort() { }) sort.Strings(o.SkipFiles) sort.Strings(o.SkipDirs) + sort.Strings(o.FilePatterns) } type Artifact interface { diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index 43049d5f2efc..028a0bed7ecd 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -15,7 +15,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" @@ -40,12 +39,6 @@ type Artifact struct { } func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { - misconf := opt.MisconfScannerOption - // Register config analyzers - if err := config.RegisterConfigAnalyzers(misconf.FilePatterns); err != nil { - return nil, xerrors.Errorf("config scanner error: %w", err) - } - // Initialize handlers handlerManager, err := handler.NewManager(opt) if err != nil { @@ -57,11 +50,16 @@ func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (a return nil, xerrors.Errorf("secret scanner error: %w", err) } + a, err := analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers, opt.FilePatterns) + if err != nil { + return nil, xerrors.Errorf("analyzer group error: %w", err) + } + return Artifact{ image: img, cache: c, walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs), - analyzer: analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers), + analyzer: a, handlerManager: handlerManager, artifactOption: opt, diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index 786704f6f0ed..5fd45975e856 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -19,6 +19,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/command/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/php/composer" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/bundler" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/licensing" diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index 15c727af9e15..8b0c3cf11865 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -14,7 +14,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" @@ -38,11 +37,6 @@ type Artifact struct { } func NewArtifact(rootPath string, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { - // Register config analyzers - if err := config.RegisterConfigAnalyzers(opt.MisconfScannerOption.FilePatterns); err != nil { - return nil, xerrors.Errorf("config analyzer error: %w", err) - } - handlerManager, err := handler.NewManager(opt) if err != nil { return nil, xerrors.Errorf("handler initialize error: %w", err) @@ -53,11 +47,16 @@ func NewArtifact(rootPath string, c cache.ArtifactCache, opt artifact.Option) (a return nil, xerrors.Errorf("secret scanner error: %w", err) } + a, err := analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers, opt.FilePatterns) + if err != nil { + return nil, xerrors.Errorf("analyzer group error: %w", err) + } + return Artifact{ rootPath: filepath.Clean(rootPath), cache: c, walker: walker.NewFS(buildAbsPaths(rootPath, opt.SkipFiles), buildAbsPaths(rootPath, opt.SkipDirs)), - analyzer: analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers), + analyzer: a, handlerManager: handlerManager, artifactOption: opt, diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index ec3422266a79..7c2c451abfc8 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -15,6 +15,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/pip" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" diff --git a/pkg/fanal/artifact/remote/git_test.go b/pkg/fanal/artifact/remote/git_test.go index b2a6a5a16a9f..f3d042f277e4 100644 --- a/pkg/fanal/artifact/remote/git_test.go +++ b/pkg/fanal/artifact/remote/git_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/cache/key.go b/pkg/fanal/cache/key.go index f65b9256d6a9..9399aa90b307 100644 --- a/pkg/fanal/cache/key.go +++ b/pkg/fanal/cache/key.go @@ -18,14 +18,15 @@ func CalcKey(id string, analyzerVersions, hookVersions map[string]int, artifactO h := sha256.New() - // Write ID, analyzer/handler versions, and skipped files/dirs + // Write ID, analyzer/handler versions, skipped files/dirs and file patterns keyBase := struct { ID string AnalyzerVersions map[string]int HookVersions map[string]int SkipFiles []string SkipDirs []string - }{id, analyzerVersions, hookVersions, artifactOpt.SkipFiles, artifactOpt.SkipDirs} + FilePatterns []string `json:",omitempty"` + }{id, analyzerVersions, hookVersions, artifactOpt.SkipFiles, artifactOpt.SkipDirs, artifactOpt.FilePatterns} if err := json.NewEncoder(h).Encode(keyBase); err != nil { return "", xerrors.Errorf("json encode error: %w", err) diff --git a/pkg/fanal/cache/key_test.go b/pkg/fanal/cache/key_test.go index 5da4f31ff9be..4b5bc6d96c00 100644 --- a/pkg/fanal/cache/key_test.go +++ b/pkg/fanal/cache/key_test.go @@ -78,7 +78,7 @@ func TestCalcKey(t *testing.T) { }, patterns: []string{""}, }, - want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", + want: "sha256:9b81e0bf3aa7809a0f41bc696f353fca5645bcb63b975ab30e23d81886df2e61", }, { name: "with single non empty string in file patterns", @@ -90,7 +90,7 @@ func TestCalcKey(t *testing.T) { }, patterns: []string{"test"}, }, - want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", + want: "sha256:7d91b2623ae4b5641a1f36efa59c774231efe8c28c27a03869894fd49b047fe8", }, { name: "with non empty followed by empty string in file patterns", @@ -102,7 +102,7 @@ func TestCalcKey(t *testing.T) { }, patterns: []string{"test", ""}, }, - want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", + want: "sha256:5c7f1555e95fc60cdaa7e92e99aee15ee7be356fad9e83f1c24a3be06713a5a8", }, { name: "with non empty preceded by empty string in file patterns", @@ -114,7 +114,7 @@ func TestCalcKey(t *testing.T) { }, patterns: []string{"", "test"}, }, - want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", + want: "sha256:5c7f1555e95fc60cdaa7e92e99aee15ee7be356fad9e83f1c24a3be06713a5a8", }, { name: "with policy", @@ -158,13 +158,13 @@ func TestCalcKey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { artifactOpt := artifact.Option{ - SkipFiles: tt.args.skipFiles, - SkipDirs: tt.args.skipDirs, + SkipFiles: tt.args.skipFiles, + SkipDirs: tt.args.skipDirs, + FilePatterns: tt.args.patterns, MisconfScannerOption: config.ScannerOption{ - FilePatterns: tt.args.patterns, - PolicyPaths: tt.args.policy, - DataPaths: tt.args.data, + PolicyPaths: tt.args.policy, + DataPaths: tt.args.data, }, } got, err := CalcKey(tt.args.key, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt) diff --git a/pkg/fanal/external/config_scan.go b/pkg/fanal/external/config_scan.go index 2843272b320e..1e1eefdb8e8c 100644 --- a/pkg/fanal/external/config_scan.go +++ b/pkg/fanal/external/config_scan.go @@ -11,6 +11,8 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" ) type ConfigScanner struct { diff --git a/pkg/fanal/handler/misconf/misconf.go b/pkg/fanal/handler/misconf/misconf.go index 0b27ee6dc81a..18cb7940e300 100644 --- a/pkg/fanal/handler/misconf/misconf.go +++ b/pkg/fanal/handler/misconf/misconf.go @@ -183,7 +183,7 @@ func newMisconfPostHandler(artifactOpt artifact.Option) (handler.PostHandler, er tfOpts := addTFOpts(opts, artifactOpt.MisconfScannerOption) return misconfPostHandler{ - filePatterns: artifactOpt.MisconfScannerOption.FilePatterns, + filePatterns: artifactOpt.FilePatterns, scanners: map[string]scanners.FSScanner{ types.Terraform: tfscanner.New(tfOpts...), types.CloudFormation: cfscanner.New(opts...), diff --git a/pkg/fanal/handler/misconf/misconf_test.go b/pkg/fanal/handler/misconf/misconf_test.go index c8948a094ed0..76c4a7c38b45 100644 --- a/pkg/fanal/handler/misconf/misconf_test.go +++ b/pkg/fanal/handler/misconf/misconf_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - misconf "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" @@ -56,7 +55,7 @@ func Test_Handle(t *testing.T) { result := &analyzer.AnalysisResult{ Files: tt.files, } - misconfHandler, err := newMisconfPostHandler(artifact.Option{MisconfScannerOption: misconf.ScannerOption{FilePatterns: tt.filePatterns}}) + misconfHandler, err := newMisconfPostHandler(artifact.Option{FilePatterns: tt.filePatterns}) assert.NoError(t, err) blobInfo := &types.BlobInfo{} diff --git a/pkg/flag/misconf_flags.go b/pkg/flag/misconf_flags.go index 855deae6e34e..69356d18e821 100644 --- a/pkg/flag/misconf_flags.go +++ b/pkg/flag/misconf_flags.go @@ -10,12 +10,6 @@ import ( // config-policy: "custom-policy/policy" // policy-namespaces: "user" var ( - FilePatternsFlag = Flag{ - Name: "file-patterns", - ConfigName: "misconfiguration.file-patterns", - Value: []string{}, - Usage: "specify config file patterns, available with '--security-checks config'", - } IncludeNonFailuresFlag = Flag{ Name: "include-non-failures", ConfigName: "misconfiguration.include-non-failures", @@ -87,7 +81,6 @@ var ( // MisconfFlagGroup composes common printer flag structs used for commands providing misconfinguration scanning. type MisconfFlagGroup struct { - FilePatterns *Flag IncludeNonFailures *Flag SkipPolicyUpdate *Flag // deprecated Trace *Flag @@ -106,7 +99,6 @@ type MisconfFlagGroup struct { } type MisconfOptions struct { - FilePatterns []string IncludeNonFailures bool SkipPolicyUpdate bool // deprecated Trace bool @@ -126,7 +118,6 @@ type MisconfOptions struct { func NewMisconfFlagGroup() *MisconfFlagGroup { return &MisconfFlagGroup{ - FilePatterns: &FilePatternsFlag, IncludeNonFailures: &IncludeNonFailuresFlag, SkipPolicyUpdate: &SkipPolicyUpdateFlag, Trace: &TraceFlag, @@ -147,7 +138,6 @@ func (f *MisconfFlagGroup) Name() string { func (f *MisconfFlagGroup) Flags() []*Flag { return []*Flag{ - f.FilePatterns, f.IncludeNonFailures, f.SkipPolicyUpdate, f.Trace, @@ -168,7 +158,6 @@ func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { log.Logger.Warn("'--skip-policy-update' is no longer necessary as the built-in policies are embedded into the binary") } return MisconfOptions{ - FilePatterns: getStringSlice(f.FilePatterns), IncludeNonFailures: getBool(f.IncludeNonFailures), Trace: getBool(f.Trace), diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index d9fa5c152f45..b4b6742fee6b 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -35,6 +35,12 @@ var ( Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret), Usage: "comma-separated list of what security issues to detect (vuln,config,secret)", } + FilePatternsFlag = Flag{ + Name: "file-patterns", + ConfigName: "scan.file-patterns", + Value: []string{}, + Usage: "specify config file patterns", + } ) type ScanFlagGroup struct { @@ -42,6 +48,7 @@ type ScanFlagGroup struct { SkipFiles *Flag OfflineScan *Flag SecurityChecks *Flag + FilePatterns *Flag } type ScanOptions struct { @@ -50,6 +57,7 @@ type ScanOptions struct { SkipFiles []string OfflineScan bool SecurityChecks []string + FilePatterns []string } func NewScanFlagGroup() *ScanFlagGroup { @@ -58,6 +66,7 @@ func NewScanFlagGroup() *ScanFlagGroup { SkipFiles: &SkipFilesFlag, OfflineScan: &OfflineScanFlag, SecurityChecks: &SecurityChecksFlag, + FilePatterns: &FilePatternsFlag, } } @@ -66,7 +75,7 @@ func (f *ScanFlagGroup) Name() string { } func (f *ScanFlagGroup) Flags() []*Flag { - return []*Flag{f.SkipDirs, f.SkipFiles, f.OfflineScan, f.SecurityChecks} + return []*Flag{f.SkipDirs, f.SkipFiles, f.OfflineScan, f.SecurityChecks, f.FilePatterns} } func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { @@ -85,6 +94,7 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { SkipFiles: getStringSlice(f.SkipFiles), OfflineScan: getBool(f.OfflineScan), SecurityChecks: securityChecks, + FilePatterns: getStringSlice(f.FilePatterns), }, nil } diff --git a/pkg/module/module_test.go b/pkg/module/module_test.go index 9fce407895af..d9faa044a606 100644 --- a/pkg/module/module_test.go +++ b/pkg/module/module_test.go @@ -92,7 +92,10 @@ func TestManager_Register(t *testing.T) { }() // Confirm the analyzer is registered - got := analyzer.NewAnalyzerGroup("", nil).AnalyzerVersions() + a, err := analyzer.NewAnalyzerGroup("", nil, nil) + require.NoError(t, err) + + got := a.AnalyzerVersions() assert.Equal(t, tt.wantAnalyzerVersions, got) // Confirm the post scanner is registered diff --git a/pkg/types/scanoptions.go b/pkg/types/scanoptions.go index 8a65b280a79d..7214b2c4f030 100644 --- a/pkg/types/scanoptions.go +++ b/pkg/types/scanoptions.go @@ -11,4 +11,5 @@ type ScanOptions struct { ScanRemovedPackages bool ListAllPackages bool LicenseCategories map[types.LicenseCategory][]string + FilePatterns []string }