feat: move file patterns to a global level to be able to use it on any analyzer (#2539)

This commit is contained in:
jerbob92
2022-09-01 10:01:57 +02:00
committed by GitHub
parent 2580ea1583
commit 5f0bf1445a
35 changed files with 269 additions and 298 deletions

View File

@@ -2,21 +2,3 @@
!!! hint !!! hint
See also [Others](../../vulnerability/examples/others.md) in Vulnerability section. 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)

View File

@@ -82,6 +82,11 @@ Available in client/server mode
```yaml ```yaml
scan: scan:
# Same as '--file-patterns'
# Default is empty
file-patterns:
-
# Same as '--skip-dirs' # Same as '--skip-dirs'
# Default is empty # Default is empty
skip-dirs: skip-dirs:
@@ -195,11 +200,6 @@ Available with misconfiguration scanning
```yaml ```yaml
misconfiguration: misconfiguration:
# Same as '--file-patterns'
# Default is empty
file-patterns:
-
# Same as '--include-non-failures' # Same as '--include-non-failures'
# Default is false # Default is false
include-non-failures: false include-non-failures: false

View File

@@ -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 $ 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 ## Exit Code
By default, `Trivy` exits with code 0 even when vulnerabilities are detected. 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. Use the `--exit-code` option if you want to exit with a non-zero exit code.

View File

@@ -448,6 +448,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand
ListAllPackages: opts.ListAllPkgs, ListAllPackages: opts.ListAllPkgs,
LicenseCategories: opts.LicenseCategories, LicenseCategories: opts.LicenseCategories,
FilePatterns: opts.FilePatterns,
} }
if slices.Contains(opts.SecurityChecks, types.SecurityCheckVulnerability) { if slices.Contains(opts.SecurityChecks, types.SecurityCheckVulnerability) {
@@ -464,7 +465,6 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
Namespaces: append(opts.PolicyNamespaces, defaultPolicyNamespaces...), Namespaces: append(opts.PolicyNamespaces, defaultPolicyNamespaces...),
PolicyPaths: opts.PolicyPaths, PolicyPaths: opts.PolicyPaths,
DataPaths: opts.DataPaths, DataPaths: opts.DataPaths,
FilePatterns: opts.FilePatterns,
HelmValues: opts.HelmValues, HelmValues: opts.HelmValues,
HelmValueFiles: opts.HelmValueFiles, HelmValueFiles: opts.HelmValueFiles,
HelmFileValues: opts.HelmFileValues, HelmFileValues: opts.HelmFileValues,
@@ -504,6 +504,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
DisabledAnalyzers: disabledAnalyzers(opts), DisabledAnalyzers: disabledAnalyzers(opts),
SkipFiles: opts.SkipFiles, SkipFiles: opts.SkipFiles,
SkipDirs: opts.SkipDirs, SkipDirs: opts.SkipDirs,
FilePatterns: opts.FilePatterns,
InsecureSkipTLS: opts.Insecure, InsecureSkipTLS: opts.Insecure,
Offline: opts.OfflineScan, Offline: opts.OfflineScan,
NoProgress: opts.NoProgress || opts.Quiet, NoProgress: opts.NoProgress || opts.Quiet,

View File

@@ -3,6 +3,7 @@ package all
import ( import (
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/buildinfo" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/buildinfo"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/command/apk" _ "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/deps"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/nuget" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/nuget"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/binary" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/binary"

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"io/fs" "io/fs"
"os" "os"
"regexp"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@@ -91,6 +92,7 @@ type Opener func() (dio.ReadSeekCloserAt, error)
type AnalyzerGroup struct { type AnalyzerGroup struct {
analyzers []analyzer analyzers []analyzer
configAnalyzers []configAnalyzer configAnalyzers []configAnalyzer
filePatterns map[Type][]*regexp.Regexp
} }
type AnalysisResult struct { type AnalysisResult struct {
@@ -266,12 +268,36 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type,
return true return true
} }
func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type) AnalyzerGroup { const separator = ":"
func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type, filePatterns []string) (AnalyzerGroup, error) {
if groupName == "" { if groupName == "" {
groupName = GroupBuiltin 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 { for analyzerType, a := range analyzers {
if !belongToGroup(groupName, analyzerType, disabledAnalyzers, a) { if !belongToGroup(groupName, analyzerType, disabledAnalyzers, a) {
continue continue
@@ -286,7 +312,7 @@ func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type) AnalyzerGroup {
group.configAnalyzers = append(group.configAnalyzers, a) group.configAnalyzers = append(group.configAnalyzers, a)
} }
return group return group, nil
} }
// AnalyzerVersions returns analyzer version identifier used for cache keys. // 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 return nil
} }
// filepath extracted from tar file doesn't have the prefix "/"
cleanPath := strings.TrimLeft(filePath, "/")
for _, a := range ag.analyzers { for _, a := range ag.analyzers {
// Skip disabled analyzers // Skip disabled analyzers
if slices.Contains(disabled, a.Type()) { if slices.Contains(disabled, a.Type()) {
continue continue
} }
// filepath extracted from tar file doesn't have the prefix "/" if !ag.filePatternMatch(a.Type(), cleanPath) && !a.Required(cleanPath, info) {
if !a.Required(strings.TrimLeft(filePath, "/"), info) {
continue continue
} }
rc, err := opener() rc, err := opener()
@@ -375,3 +403,12 @@ func (ag AnalyzerGroup) AnalyzeImageConfig(targetOS types.OS, configBlob []byte)
} }
return nil 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
}

View File

@@ -281,6 +281,7 @@ func TestAnalyzeFile(t *testing.T) {
filePath string filePath string
testFilePath string testFilePath string
disabledAnalyzers []analyzer.Type disabledAnalyzers []analyzer.Type
filePatterns []string
} }
tests := []struct { tests := []struct {
name string name string
@@ -377,6 +378,28 @@ func TestAnalyzeFile(t *testing.T) {
}, },
want: &analyzer.AnalysisResult{}, 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", name: "ignore permission error",
args: args{ args: args{
@@ -393,6 +416,24 @@ func TestAnalyzeFile(t *testing.T) {
}, },
wantErr: "unable to open /lib/apk/db/installed", 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -400,7 +441,13 @@ func TestAnalyzeFile(t *testing.T) {
limit := semaphore.NewWeighted(3) limit := semaphore.NewWeighted(3)
got := new(analyzer.AnalysisResult) 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) info, err := os.Stat(tt.args.testFilePath)
require.NoError(t, err) require.NoError(t, err)
@@ -440,6 +487,7 @@ func TestAnalyzeConfig(t *testing.T) {
targetOS types.OS targetOS types.OS
configBlob []byte configBlob []byte
disabledAnalyzers []analyzer.Type disabledAnalyzers []analyzer.Type
filePatterns []string
} }
tests := []struct { tests := []struct {
name string name string
@@ -482,7 +530,8 @@ func TestAnalyzeConfig(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) got := a.AnalyzeImageConfig(tt.args.targetOS, tt.args.configBlob)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })
@@ -517,7 +566,8 @@ func TestAnalyzer_AnalyzerVersions(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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() got := a.AnalyzerVersions()
fmt.Printf("%v\n", got) fmt.Printf("%v\n", got)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
@@ -549,7 +599,8 @@ func TestAnalyzer_ImageConfigAnalyzerVersions(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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() got := a.ImageConfigAnalyzerVersions()
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })

View File

@@ -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"
)

View File

@@ -1,29 +1,13 @@
package config package config
import ( import (
"regexp"
"sort" "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 { type ScannerOption struct {
Trace bool Trace bool
RegoOnly bool RegoOnly bool
Namespaces []string Namespaces []string
FilePatterns []string
PolicyPaths []string PolicyPaths []string
DataPaths []string DataPaths []string
DisableEmbeddedPolicies bool DisableEmbeddedPolicies bool
@@ -37,44 +21,6 @@ type ScannerOption struct {
func (o *ScannerOption) Sort() { func (o *ScannerOption) Sort() {
sort.Strings(o.Namespaces) sort.Strings(o.Namespaces)
sort.Strings(o.FilePatterns)
sort.Strings(o.PolicyPaths) sort.Strings(o.PolicyPaths)
sort.Strings(o.DataPaths) 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
}

View File

@@ -11,7 +11,6 @@ import (
func TestScannerOption_Sort(t *testing.T) { func TestScannerOption_Sort(t *testing.T) {
type fields struct { type fields struct {
Namespaces []string Namespaces []string
FilePatterns []string
PolicyPaths []string PolicyPaths []string
DataPaths []string DataPaths []string
} }
@@ -24,13 +23,11 @@ func TestScannerOption_Sort(t *testing.T) {
name: "happy path", name: "happy path",
fields: fields{ fields: fields{
Namespaces: []string{"main", "custom", "default"}, Namespaces: []string{"main", "custom", "default"},
FilePatterns: []string{"dockerfile:foo*", "yaml:yml_*"},
PolicyPaths: []string{"policy"}, PolicyPaths: []string{"policy"},
DataPaths: []string{"data/b", "data/c", "data/a"}, DataPaths: []string{"data/b", "data/c", "data/a"},
}, },
want: config.ScannerOption{ want: config.ScannerOption{
Namespaces: []string{"custom", "default", "main"}, Namespaces: []string{"custom", "default", "main"},
FilePatterns: []string{"dockerfile:foo*", "yaml:yml_*"},
PolicyPaths: []string{"policy"}, PolicyPaths: []string{"policy"},
DataPaths: []string{"data/a", "data/b", "data/c"}, DataPaths: []string{"data/a", "data/b", "data/c"},
}, },
@@ -39,7 +36,6 @@ func TestScannerOption_Sort(t *testing.T) {
name: "missing some fields", name: "missing some fields",
fields: fields{ fields: fields{
Namespaces: []string{"main"}, Namespaces: []string{"main"},
FilePatterns: nil,
PolicyPaths: nil, PolicyPaths: nil,
DataPaths: nil, DataPaths: nil,
}, },
@@ -52,7 +48,6 @@ func TestScannerOption_Sort(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := config.ScannerOption{ o := config.ScannerOption{
Namespaces: tt.fields.Namespaces, Namespaces: tt.fields.Namespaces,
FilePatterns: tt.fields.FilePatterns,
PolicyPaths: tt.fields.PolicyPaths, PolicyPaths: tt.fields.PolicyPaths,
DataPaths: tt.fields.DataPaths, DataPaths: tt.fields.DataPaths,
} }

View File

@@ -5,7 +5,6 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -14,21 +13,17 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
func init() {
analyzer.RegisterAnalyzer(&dockerConfigAnalyzer{})
}
const version = 1 const version = 1
var requiredFiles = []string{"Dockerfile", "Containerfile"} var requiredFiles = []string{"Dockerfile", "Containerfile"}
type ConfigAnalyzer struct { type dockerConfigAnalyzer struct{}
filePattern *regexp.Regexp
}
func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { func (s dockerConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
return ConfigAnalyzer{
filePattern: filePattern,
}
}
func (s ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
b, err := io.ReadAll(input.Content) b, err := io.ReadAll(input.Content)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to read %s: %w", input.FilePath, err) 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 // Required does a case-insensitive check for filePath and returns true if
// filePath equals/startsWith/hasExtension requiredFiles // filePath equals/startsWith/hasExtension requiredFiles
func (s ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { func (s dockerConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool {
if s.filePattern != nil && s.filePattern.MatchString(filePath) {
return true
}
base := filepath.Base(filePath) base := filepath.Base(filePath)
ext := filepath.Ext(base) ext := filepath.Ext(base)
for _, file := range requiredFiles { for _, file := range requiredFiles {
@@ -69,10 +60,10 @@ func (s ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool {
return false return false
} }
func (s ConfigAnalyzer) Type() analyzer.Type { func (s dockerConfigAnalyzer) Type() analyzer.Type {
return analyzer.TypeDockerfile return analyzer.TypeDockerfile
} }
func (s ConfigAnalyzer) Version() int { func (s dockerConfigAnalyzer) Version() int {
return version return version
} }

View File

@@ -1,16 +1,14 @@
package dockerfile_test package dockerfile
import ( import (
"context" "context"
"os" "os"
"regexp"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/dockerfile"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
@@ -68,7 +66,7 @@ COPY --from=build /bar /bar
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
a := dockerfile.NewConfigAnalyzer(nil) a := dockerConfigAnalyzer{}
ctx := context.Background() ctx := context.Background()
got, err := a.Analyze(ctx, analyzer.AnalysisInput{ got, err := a.Analyze(ctx, analyzer.AnalysisInput{
FilePath: tt.inputFile, FilePath: tt.inputFile,
@@ -89,7 +87,6 @@ COPY --from=build /bar /bar
func Test_dockerConfigAnalyzer_Required(t *testing.T) { func Test_dockerConfigAnalyzer_Required(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
filePattern *regexp.Regexp
filePath string filePath string
want bool want bool
}{ }{
@@ -143,16 +140,10 @@ func Test_dockerConfigAnalyzer_Required(t *testing.T) {
filePath: "deployment.json", filePath: "deployment.json",
want: false, want: false,
}, },
{
name: "file pattern",
filePattern: regexp.MustCompile(`foo*`),
filePath: "foo_file",
want: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := dockerfile.NewConfigAnalyzer(tt.filePattern) s := dockerConfigAnalyzer{}
got := s.Required(tt.filePath, nil) got := s.Required(tt.filePath, nil)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })
@@ -160,7 +151,7 @@ func Test_dockerConfigAnalyzer_Required(t *testing.T) {
} }
func Test_dockerConfigAnalyzer_Type(t *testing.T) { func Test_dockerConfigAnalyzer_Type(t *testing.T) {
s := dockerfile.NewConfigAnalyzer(nil) s := dockerConfigAnalyzer{}
want := analyzer.TypeDockerfile want := analyzer.TypeDockerfile
got := s.Type() got := s.Type()
assert.Equal(t, want, got) assert.Equal(t, want, got)

View File

@@ -8,7 +8,6 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -18,21 +17,17 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
func init() {
analyzer.RegisterAnalyzer(&helmConfigAnalyzer{})
}
const version = 1 const version = 1
const maxTarSize = 209_715_200 // 200MB const maxTarSize = 209_715_200 // 200MB
type ConfigAnalyzer struct { type helmConfigAnalyzer struct{}
filePattern *regexp.Regexp
}
func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { func (a helmConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
return ConfigAnalyzer{
filePattern: filePattern,
}
}
func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
if isArchive(input.FilePath) { if isArchive(input.FilePath) {
if !isHelmChart(input.FilePath, input.Content) { if !isHelmChart(input.FilePath, input.Content) {
return nil, nil return nil, nil
@@ -62,11 +57,7 @@ func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput)
}, nil }, nil
} }
func (a ConfigAnalyzer) Required(filePath string, info os.FileInfo) bool { func (a helmConfigAnalyzer) Required(filePath string, info os.FileInfo) bool {
if a.filePattern != nil && a.filePattern.MatchString(filePath) {
return true
}
if info.Size() > maxTarSize { if info.Size() > maxTarSize {
// tarball is too big to be Helm chart - move on // tarball is too big to be Helm chart - move on
return false return false
@@ -88,11 +79,11 @@ func (a ConfigAnalyzer) Required(filePath string, info os.FileInfo) bool {
return false return false
} }
func (ConfigAnalyzer) Type() analyzer.Type { func (helmConfigAnalyzer) Type() analyzer.Type {
return analyzer.TypeHelm return analyzer.TypeHelm
} }
func (ConfigAnalyzer) Version() int { func (helmConfigAnalyzer) Version() int {
return version return version
} }

View File

@@ -3,7 +3,6 @@ package helm
import ( import (
"context" "context"
"os" "os"
"regexp"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -340,7 +339,7 @@ affinity: {}
info, err := os.Stat(tt.inputFile) info, err := os.Stat(tt.inputFile)
require.NoError(t, err) require.NoError(t, err)
a := NewConfigAnalyzer(nil) a := helmConfigAnalyzer{}
ctx := context.Background() ctx := context.Background()
got, err := a.Analyze(ctx, analyzer.AnalysisInput{ got, err := a.Analyze(ctx, analyzer.AnalysisInput{
FilePath: tt.inputFile, FilePath: tt.inputFile,
@@ -362,7 +361,6 @@ affinity: {}
func Test_helmConfigAnalyzer_Required(t *testing.T) { func Test_helmConfigAnalyzer_Required(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
filePattern *regexp.Regexp
filePath string filePath string
want bool want bool
}{ }{
@@ -406,17 +404,10 @@ func Test_helmConfigAnalyzer_Required(t *testing.T) {
filePath: "testdata/nope.tgz", filePath: "testdata/nope.tgz",
want: true, // its a tarball after all want: true, // its a tarball after all
}, },
{
name: "file pattern",
filePattern: regexp.MustCompile(`foo*`),
filePath: "foo_file",
want: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := NewConfigAnalyzer(tt.filePattern) s := helmConfigAnalyzer{}
info, _ := os.Stat(tt.filePath) info, _ := os.Stat(tt.filePath)
@@ -427,7 +418,7 @@ func Test_helmConfigAnalyzer_Required(t *testing.T) {
} }
func Test_helmConfigAnalyzer_Type(t *testing.T) { func Test_helmConfigAnalyzer_Type(t *testing.T) {
s := NewConfigAnalyzer(nil) s := helmConfigAnalyzer{}
want := analyzer.TypeHelm want := analyzer.TypeHelm
got := s.Type() got := s.Type()

View File

@@ -5,7 +5,6 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -13,6 +12,10 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
func init() {
analyzer.RegisterAnalyzer(&jsonConfigAnalyzer{})
}
const version = 1 const version = 1
var ( var (
@@ -20,17 +23,9 @@ var (
excludedFiles = []string{types.NpmPkgLock, types.NuGetPkgsLock, types.NuGetPkgsConfig} excludedFiles = []string{types.NpmPkgLock, types.NuGetPkgsLock, types.NuGetPkgsConfig}
) )
type ConfigAnalyzer struct { type jsonConfigAnalyzer struct{}
filePattern *regexp.Regexp
}
func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { func (a jsonConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
return ConfigAnalyzer{
filePattern: filePattern,
}
}
func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
b, err := io.ReadAll(input.Content) b, err := io.ReadAll(input.Content)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to read %s: %w", input.FilePath, err) 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 }, nil
} }
func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { func (a jsonConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool {
if a.filePattern != nil && a.filePattern.MatchString(filePath) {
return true
}
filename := filepath.Base(filePath) filename := filepath.Base(filePath)
for _, excludedFile := range excludedFiles { for _, excludedFile := range excludedFiles {
if filename == excludedFile { if filename == excludedFile {
@@ -65,10 +56,10 @@ func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool {
return filepath.Ext(filePath) == requiredExt return filepath.Ext(filePath) == requiredExt
} }
func (ConfigAnalyzer) Type() analyzer.Type { func (jsonConfigAnalyzer) Type() analyzer.Type {
return analyzer.TypeJSON return analyzer.TypeJSON
} }
func (ConfigAnalyzer) Version() int { func (jsonConfigAnalyzer) Version() int {
return version return version
} }

View File

@@ -1,16 +1,14 @@
package json_test package json
import ( import (
"context" "context"
"os" "os"
"regexp"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/json"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
@@ -133,7 +131,7 @@ func Test_jsonConfigAnalyzer_Analyze(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
s := json.NewConfigAnalyzer(nil) s := jsonConfigAnalyzer{}
ctx := context.Background() ctx := context.Background()
got, err := s.Analyze(ctx, analyzer.AnalysisInput{ got, err := s.Analyze(ctx, analyzer.AnalysisInput{
@@ -155,7 +153,6 @@ func Test_jsonConfigAnalyzer_Analyze(t *testing.T) {
func Test_jsonConfigAnalyzer_Required(t *testing.T) { func Test_jsonConfigAnalyzer_Required(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
filePattern *regexp.Regexp
filePath string filePath string
want bool want bool
}{ }{
@@ -174,16 +171,10 @@ func Test_jsonConfigAnalyzer_Required(t *testing.T) {
filePath: "package-lock.json", filePath: "package-lock.json",
want: false, want: false,
}, },
{
name: "file pattern",
filePattern: regexp.MustCompile(`foo*`),
filePath: "foo_file",
want: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := json.NewConfigAnalyzer(tt.filePattern) s := jsonConfigAnalyzer{}
got := s.Required(tt.filePath, nil) got := s.Required(tt.filePath, nil)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
@@ -192,7 +183,7 @@ func Test_jsonConfigAnalyzer_Required(t *testing.T) {
} }
func Test_jsonConfigAnalyzer_Type(t *testing.T) { func Test_jsonConfigAnalyzer_Type(t *testing.T) {
s := json.NewConfigAnalyzer(nil) s := jsonConfigAnalyzer{}
want := analyzer.TypeJSON want := analyzer.TypeJSON
got := s.Type() got := s.Type()

View File

@@ -13,19 +13,18 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
func init() {
analyzer.RegisterAnalyzer(&terraformConfigAnalyzer{})
}
const version = 1 const version = 1
var requiredExts = []string{".tf", ".tf.json"} var requiredExts = []string{".tf", ".tf.json"}
type ConfigAnalyzer struct { type terraformConfigAnalyzer struct{}
}
func NewConfigAnalyzer() ConfigAnalyzer {
return ConfigAnalyzer{}
}
// Analyze returns a name of Terraform file // 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) b, err := io.ReadAll(input.Content)
if err != nil { if err != nil {
return nil, xerrors.Errorf("read error (%s): %w", input.FilePath, err) 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 }, 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)) return slices.Contains(requiredExts, filepath.Ext(filePath))
} }
func (ConfigAnalyzer) Type() analyzer.Type { func (terraformConfigAnalyzer) Type() analyzer.Type {
return analyzer.TypeTerraform return analyzer.TypeTerraform
} }
func (ConfigAnalyzer) Version() int { func (terraformConfigAnalyzer) Version() int {
return version return version
} }

View File

@@ -1,4 +1,4 @@
package terraform_test package terraform
import ( import (
"bytes" "bytes"
@@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
@@ -41,7 +40,7 @@ func TestConfigAnalyzer_Analyze(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
a := terraform.ConfigAnalyzer{} a := terraformConfigAnalyzer{}
ctx := context.Background() ctx := context.Background()
got, err := a.Analyze(ctx, tt.input) got, err := a.Analyze(ctx, tt.input)
@@ -75,7 +74,7 @@ func TestConfigAnalyzer_Required(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
a := terraform.ConfigAnalyzer{} a := terraformConfigAnalyzer{}
got := a.Required(tt.filePath, nil) got := a.Required(tt.filePath, nil)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })

View File

@@ -5,7 +5,6 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -13,21 +12,17 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
func init() {
analyzer.RegisterAnalyzer(&yamlConfigAnalyzer{})
}
const version = 1 const version = 1
var requiredExts = []string{".yaml", ".yml"} var requiredExts = []string{".yaml", ".yml"}
type ConfigAnalyzer struct { type yamlConfigAnalyzer struct{}
filePattern *regexp.Regexp
}
func NewConfigAnalyzer(filePattern *regexp.Regexp) ConfigAnalyzer { func (a yamlConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
return ConfigAnalyzer{
filePattern: filePattern,
}
}
func (a ConfigAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
b, err := io.ReadAll(input.Content) b, err := io.ReadAll(input.Content)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to read %s: %w", input.FilePath, err) 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 }, nil
} }
func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { func (a yamlConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool {
if a.filePattern != nil && a.filePattern.MatchString(filePath) {
return true
}
ext := filepath.Ext(filePath) ext := filepath.Ext(filePath)
for _, required := range requiredExts { for _, required := range requiredExts {
if ext == required { if ext == required {
@@ -61,10 +52,10 @@ func (a ConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool {
return false return false
} }
func (ConfigAnalyzer) Type() analyzer.Type { func (yamlConfigAnalyzer) Type() analyzer.Type {
return analyzer.TypeYaml return analyzer.TypeYaml
} }
func (ConfigAnalyzer) Version() int { func (yamlConfigAnalyzer) Version() int {
return version return version
} }

View File

@@ -1,16 +1,14 @@
package yaml_test package yaml
import ( import (
"context" "context"
"os" "os"
"regexp"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/yaml"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
) )
@@ -163,7 +161,7 @@ spec:
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
a := yaml.NewConfigAnalyzer(nil) a := yamlConfigAnalyzer{}
ctx := context.Background() ctx := context.Background()
got, err := a.Analyze(ctx, analyzer.AnalysisInput{ got, err := a.Analyze(ctx, analyzer.AnalysisInput{
FilePath: tt.inputFile, FilePath: tt.inputFile,
@@ -184,7 +182,6 @@ spec:
func Test_yamlConfigAnalyzer_Required(t *testing.T) { func Test_yamlConfigAnalyzer_Required(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
filePattern *regexp.Regexp
filePath string filePath string
want bool want bool
}{ }{
@@ -203,16 +200,10 @@ func Test_yamlConfigAnalyzer_Required(t *testing.T) {
filePath: "deployment.json", filePath: "deployment.json",
want: false, want: false,
}, },
{
name: "file pattern",
filePattern: regexp.MustCompile(`foo*`),
filePath: "foo_file",
want: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := yaml.NewConfigAnalyzer(tt.filePattern) s := yamlConfigAnalyzer{}
got := s.Required(tt.filePath, nil) got := s.Required(tt.filePath, nil)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
@@ -221,7 +212,7 @@ func Test_yamlConfigAnalyzer_Required(t *testing.T) {
} }
func Test_yamlConfigAnalyzer_Type(t *testing.T) { func Test_yamlConfigAnalyzer_Type(t *testing.T) {
s := yaml.NewConfigAnalyzer(nil) s := yamlConfigAnalyzer{}
want := analyzer.TypeYaml want := analyzer.TypeYaml
got := s.Type() got := s.Type()

View File

@@ -16,6 +16,7 @@ type Option struct {
DisabledHandlers []types.HandlerType DisabledHandlers []types.HandlerType
SkipFiles []string SkipFiles []string
SkipDirs []string SkipDirs []string
FilePatterns []string
NoProgress bool NoProgress bool
Offline bool Offline bool
InsecureSkipTLS bool InsecureSkipTLS bool
@@ -34,6 +35,7 @@ func (o *Option) Sort() {
}) })
sort.Strings(o.SkipFiles) sort.Strings(o.SkipFiles)
sort.Strings(o.SkipDirs) sort.Strings(o.SkipDirs)
sort.Strings(o.FilePatterns)
} }
type Artifact interface { type Artifact interface {

View File

@@ -15,7 +15,6 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "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/analyzer/secret"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/cache" "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) { 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 // Initialize handlers
handlerManager, err := handler.NewManager(opt) handlerManager, err := handler.NewManager(opt)
if err != nil { 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) 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{ return Artifact{
image: img, image: img,
cache: c, cache: c,
walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs), walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs),
analyzer: analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers), analyzer: a,
handlerManager: handlerManager, handlerManager: handlerManager,
artifactOption: opt, artifactOption: opt,

View File

@@ -19,6 +19,7 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/command/apk" _ "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/php/composer"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/bundler" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/bundler"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/licensing" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/licensing"

View File

@@ -14,7 +14,6 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "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/analyzer/secret"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/cache" "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) { 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) handlerManager, err := handler.NewManager(opt)
if err != nil { if err != nil {
return nil, xerrors.Errorf("handler initialize error: %w", err) 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) 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{ return Artifact{
rootPath: filepath.Clean(rootPath), rootPath: filepath.Clean(rootPath),
cache: c, cache: c,
walker: walker.NewFS(buildAbsPaths(rootPath, opt.SkipFiles), buildAbsPaths(rootPath, opt.SkipDirs)), walker: walker.NewFS(buildAbsPaths(rootPath, opt.SkipFiles), buildAbsPaths(rootPath, opt.SkipDirs)),
analyzer: analyzer.NewAnalyzerGroup(opt.AnalyzerGroup, opt.DisabledAnalyzers), analyzer: a,
handlerManager: handlerManager, handlerManager: handlerManager,
artifactOption: opt, artifactOption: opt,

View File

@@ -15,6 +15,7 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/cache"
"github.com/aquasecurity/trivy/pkg/fanal/types" "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/language/python/pip"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk"

View File

@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/cache"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"

View File

@@ -18,14 +18,15 @@ func CalcKey(id string, analyzerVersions, hookVersions map[string]int, artifactO
h := sha256.New() 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 { keyBase := struct {
ID string ID string
AnalyzerVersions map[string]int AnalyzerVersions map[string]int
HookVersions map[string]int HookVersions map[string]int
SkipFiles []string SkipFiles []string
SkipDirs []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 { if err := json.NewEncoder(h).Encode(keyBase); err != nil {
return "", xerrors.Errorf("json encode error: %w", err) return "", xerrors.Errorf("json encode error: %w", err)

View File

@@ -78,7 +78,7 @@ func TestCalcKey(t *testing.T) {
}, },
patterns: []string{""}, patterns: []string{""},
}, },
want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", want: "sha256:9b81e0bf3aa7809a0f41bc696f353fca5645bcb63b975ab30e23d81886df2e61",
}, },
{ {
name: "with single non empty string in file patterns", name: "with single non empty string in file patterns",
@@ -90,7 +90,7 @@ func TestCalcKey(t *testing.T) {
}, },
patterns: []string{"test"}, patterns: []string{"test"},
}, },
want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", want: "sha256:7d91b2623ae4b5641a1f36efa59c774231efe8c28c27a03869894fd49b047fe8",
}, },
{ {
name: "with non empty followed by empty string in file patterns", name: "with non empty followed by empty string in file patterns",
@@ -102,7 +102,7 @@ func TestCalcKey(t *testing.T) {
}, },
patterns: []string{"test", ""}, patterns: []string{"test", ""},
}, },
want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", want: "sha256:5c7f1555e95fc60cdaa7e92e99aee15ee7be356fad9e83f1c24a3be06713a5a8",
}, },
{ {
name: "with non empty preceded by empty string in file patterns", name: "with non empty preceded by empty string in file patterns",
@@ -114,7 +114,7 @@ func TestCalcKey(t *testing.T) {
}, },
patterns: []string{"", "test"}, patterns: []string{"", "test"},
}, },
want: "sha256:d69f13df33f4c159b4ea54c1967384782fcefb5e2a19af35f4cd6d2896e9285e", want: "sha256:5c7f1555e95fc60cdaa7e92e99aee15ee7be356fad9e83f1c24a3be06713a5a8",
}, },
{ {
name: "with policy", name: "with policy",
@@ -160,9 +160,9 @@ func TestCalcKey(t *testing.T) {
artifactOpt := artifact.Option{ artifactOpt := artifact.Option{
SkipFiles: tt.args.skipFiles, SkipFiles: tt.args.skipFiles,
SkipDirs: tt.args.skipDirs, SkipDirs: tt.args.skipDirs,
FilePatterns: tt.args.patterns,
MisconfScannerOption: config.ScannerOption{ MisconfScannerOption: config.ScannerOption{
FilePatterns: tt.args.patterns,
PolicyPaths: tt.args.policy, PolicyPaths: tt.args.policy,
DataPaths: tt.args.data, DataPaths: tt.args.data,
}, },

View File

@@ -11,6 +11,8 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/artifact/local" "github.com/aquasecurity/trivy/pkg/fanal/artifact/local"
"github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/cache"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all"
) )
type ConfigScanner struct { type ConfigScanner struct {

View File

@@ -183,7 +183,7 @@ func newMisconfPostHandler(artifactOpt artifact.Option) (handler.PostHandler, er
tfOpts := addTFOpts(opts, artifactOpt.MisconfScannerOption) tfOpts := addTFOpts(opts, artifactOpt.MisconfScannerOption)
return misconfPostHandler{ return misconfPostHandler{
filePatterns: artifactOpt.MisconfScannerOption.FilePatterns, filePatterns: artifactOpt.FilePatterns,
scanners: map[string]scanners.FSScanner{ scanners: map[string]scanners.FSScanner{
types.Terraform: tfscanner.New(tfOpts...), types.Terraform: tfscanner.New(tfOpts...),
types.CloudFormation: cfscanner.New(opts...), types.CloudFormation: cfscanner.New(opts...),

View File

@@ -6,7 +6,6 @@ import (
"testing" "testing"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "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/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -56,7 +55,7 @@ func Test_Handle(t *testing.T) {
result := &analyzer.AnalysisResult{ result := &analyzer.AnalysisResult{
Files: tt.files, 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) assert.NoError(t, err)
blobInfo := &types.BlobInfo{} blobInfo := &types.BlobInfo{}

View File

@@ -10,12 +10,6 @@ import (
// config-policy: "custom-policy/policy" // config-policy: "custom-policy/policy"
// policy-namespaces: "user" // policy-namespaces: "user"
var ( var (
FilePatternsFlag = Flag{
Name: "file-patterns",
ConfigName: "misconfiguration.file-patterns",
Value: []string{},
Usage: "specify config file patterns, available with '--security-checks config'",
}
IncludeNonFailuresFlag = Flag{ IncludeNonFailuresFlag = Flag{
Name: "include-non-failures", Name: "include-non-failures",
ConfigName: "misconfiguration.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. // MisconfFlagGroup composes common printer flag structs used for commands providing misconfinguration scanning.
type MisconfFlagGroup struct { type MisconfFlagGroup struct {
FilePatterns *Flag
IncludeNonFailures *Flag IncludeNonFailures *Flag
SkipPolicyUpdate *Flag // deprecated SkipPolicyUpdate *Flag // deprecated
Trace *Flag Trace *Flag
@@ -106,7 +99,6 @@ type MisconfFlagGroup struct {
} }
type MisconfOptions struct { type MisconfOptions struct {
FilePatterns []string
IncludeNonFailures bool IncludeNonFailures bool
SkipPolicyUpdate bool // deprecated SkipPolicyUpdate bool // deprecated
Trace bool Trace bool
@@ -126,7 +118,6 @@ type MisconfOptions struct {
func NewMisconfFlagGroup() *MisconfFlagGroup { func NewMisconfFlagGroup() *MisconfFlagGroup {
return &MisconfFlagGroup{ return &MisconfFlagGroup{
FilePatterns: &FilePatternsFlag,
IncludeNonFailures: &IncludeNonFailuresFlag, IncludeNonFailures: &IncludeNonFailuresFlag,
SkipPolicyUpdate: &SkipPolicyUpdateFlag, SkipPolicyUpdate: &SkipPolicyUpdateFlag,
Trace: &TraceFlag, Trace: &TraceFlag,
@@ -147,7 +138,6 @@ func (f *MisconfFlagGroup) Name() string {
func (f *MisconfFlagGroup) Flags() []*Flag { func (f *MisconfFlagGroup) Flags() []*Flag {
return []*Flag{ return []*Flag{
f.FilePatterns,
f.IncludeNonFailures, f.IncludeNonFailures,
f.SkipPolicyUpdate, f.SkipPolicyUpdate,
f.Trace, 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") log.Logger.Warn("'--skip-policy-update' is no longer necessary as the built-in policies are embedded into the binary")
} }
return MisconfOptions{ return MisconfOptions{
FilePatterns: getStringSlice(f.FilePatterns),
IncludeNonFailures: getBool(f.IncludeNonFailures), IncludeNonFailures: getBool(f.IncludeNonFailures),
Trace: getBool(f.Trace), Trace: getBool(f.Trace),

View File

@@ -35,6 +35,12 @@ var (
Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret), Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret),
Usage: "comma-separated list of what security issues to detect (vuln,config,secret)", 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 { type ScanFlagGroup struct {
@@ -42,6 +48,7 @@ type ScanFlagGroup struct {
SkipFiles *Flag SkipFiles *Flag
OfflineScan *Flag OfflineScan *Flag
SecurityChecks *Flag SecurityChecks *Flag
FilePatterns *Flag
} }
type ScanOptions struct { type ScanOptions struct {
@@ -50,6 +57,7 @@ type ScanOptions struct {
SkipFiles []string SkipFiles []string
OfflineScan bool OfflineScan bool
SecurityChecks []string SecurityChecks []string
FilePatterns []string
} }
func NewScanFlagGroup() *ScanFlagGroup { func NewScanFlagGroup() *ScanFlagGroup {
@@ -58,6 +66,7 @@ func NewScanFlagGroup() *ScanFlagGroup {
SkipFiles: &SkipFilesFlag, SkipFiles: &SkipFilesFlag,
OfflineScan: &OfflineScanFlag, OfflineScan: &OfflineScanFlag,
SecurityChecks: &SecurityChecksFlag, SecurityChecks: &SecurityChecksFlag,
FilePatterns: &FilePatternsFlag,
} }
} }
@@ -66,7 +75,7 @@ func (f *ScanFlagGroup) Name() string {
} }
func (f *ScanFlagGroup) Flags() []*Flag { 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) { func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) {
@@ -85,6 +94,7 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) {
SkipFiles: getStringSlice(f.SkipFiles), SkipFiles: getStringSlice(f.SkipFiles),
OfflineScan: getBool(f.OfflineScan), OfflineScan: getBool(f.OfflineScan),
SecurityChecks: securityChecks, SecurityChecks: securityChecks,
FilePatterns: getStringSlice(f.FilePatterns),
}, nil }, nil
} }

View File

@@ -92,7 +92,10 @@ func TestManager_Register(t *testing.T) {
}() }()
// Confirm the analyzer is registered // 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) assert.Equal(t, tt.wantAnalyzerVersions, got)
// Confirm the post scanner is registered // Confirm the post scanner is registered

View File

@@ -11,4 +11,5 @@ type ScanOptions struct {
ScanRemovedPackages bool ScanRemovedPackages bool
ListAllPackages bool ListAllPackages bool
LicenseCategories map[types.LicenseCategory][]string LicenseCategories map[types.LicenseCategory][]string
FilePatterns []string
} }