Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add a new interface for initializing analyzers #2835

Merged
merged 6 commits into from Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -64,7 +64,7 @@ require (
go.etcd.io/bbolt v1.3.6
go.uber.org/zap v1.22.0
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f
google.golang.org/protobuf v1.28.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Expand Up @@ -2136,8 +2136,9 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.7.0 h1:Hdks0L0hgznZLG9nzXb8vZ0rRvqNvAcgAp84y7Mwkgw=
gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM=
Expand Down
3 changes: 1 addition & 2 deletions pkg/commands/artifact/run.go
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/aquasecurity/trivy/pkg/commands/operation"
"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"
"github.com/aquasecurity/trivy/pkg/flag"
Expand Down Expand Up @@ -516,7 +515,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
MisconfScannerOption: configScannerOptions,

// For secret scanning
SecretScannerOption: secret.ScannerOption{
SecretScannerOption: analyzer.SecretScannerOption{
ConfigPath: opts.SecretConfigPath,
},
},
Expand Down
64 changes: 52 additions & 12 deletions pkg/fanal/analyzer/analyzer.go
Expand Up @@ -32,17 +32,29 @@ var (
ErrNoPkgsDetected = xerrors.New("no packages detected")
)

type AnalysisInput struct {
Dir string
FilePath string
Info os.FileInfo
Content dio.ReadSeekerAt
//////////////////////
// Analyzer options //
//////////////////////

// AnalyzerOptions is used to initialize analyzers
type AnalyzerOptions struct {
Group Group
FilePatterns []string
DisabledAnalyzers []Type
SecretScannerOption SecretScannerOption
}

Options AnalysisOptions
type SecretScannerOption struct {
ConfigPath string
}

type AnalysisOptions struct {
Offline bool
////////////////
// Interfaces //
////////////////

// Initializer represents analyzers that need to take parameters from users
type Initializer interface {
Init(AnalyzerOptions) error
}

type analyzer interface {
Expand All @@ -59,6 +71,10 @@ type configAnalyzer interface {
Required(osFound types.OS) bool
}

////////////////////
// Analyzer group //
////////////////////

type Group string

const GroupBuiltin Group = "builtin"
Expand Down Expand Up @@ -95,6 +111,23 @@ type AnalyzerGroup struct {
filePatterns map[Type][]*regexp.Regexp
}

///////////////////////////
// Analyzer input/output //
///////////////////////////

type AnalysisInput struct {
Dir string
FilePath string
Info os.FileInfo
Content dio.ReadSeekerAt

Options AnalysisOptions
}

type AnalysisOptions struct {
Offline bool
}

type AnalysisResult struct {
m sync.Mutex
OS *types.OS
Expand Down Expand Up @@ -270,15 +303,16 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type,

const separator = ":"

func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type, filePatterns []string) (AnalyzerGroup, error) {
func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) {
groupName := opt.Group
if groupName == "" {
groupName = GroupBuiltin
}

group := AnalyzerGroup{
filePatterns: map[Type][]*regexp.Regexp{},
}
for _, p := range filePatterns {
for _, p := range opt.FilePatterns {
// e.g. "dockerfile:my_dockerfile_*"
s := strings.SplitN(p, separator, 2)
if len(s) != 2 {
Expand All @@ -299,14 +333,20 @@ func NewAnalyzerGroup(groupName Group, disabledAnalyzers []Type, filePatterns []
}

for analyzerType, a := range analyzers {
if !belongToGroup(groupName, analyzerType, disabledAnalyzers, a) {
if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) {
continue
}
// Initialize only scanners that have Init()
if ini, ok := a.(Initializer); ok {
if err := ini.Init(opt); err != nil {
return AnalyzerGroup{}, xerrors.Errorf("analyzer initialization error: %w", err)
}
}
group.analyzers = append(group.analyzers, a)
}

for analyzerType, a := range configAnalyzers {
if slices.Contains(disabledAnalyzers, analyzerType) {
if slices.Contains(opt.DisabledAnalyzers, analyzerType) {
continue
}
group.configAnalyzers = append(group.configAnalyzers, a)
Expand Down
18 changes: 14 additions & 4 deletions pkg/fanal/analyzer/analyzer_test.go
Expand Up @@ -441,7 +441,10 @@ func TestAnalyzeFile(t *testing.T) {
limit := semaphore.NewWeighted(3)

got := new(analyzer.AnalysisResult)
a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers, tt.args.filePatterns)
a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{
FilePatterns: tt.args.filePatterns,
DisabledAnalyzers: tt.args.disabledAnalyzers,
})
if err != nil && tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
Expand Down Expand Up @@ -530,7 +533,10 @@ func TestAnalyzeConfig(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers, tt.args.filePatterns)
a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{
FilePatterns: tt.args.filePatterns,
DisabledAnalyzers: tt.args.disabledAnalyzers,
})
require.NoError(t, err)
got := a.AnalyzeImageConfig(tt.args.targetOS, tt.args.configBlob)
assert.Equal(t, tt.want, got)
Expand Down Expand Up @@ -566,7 +572,9 @@ func TestAnalyzer_AnalyzerVersions(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled, nil)
a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{
DisabledAnalyzers: tt.disabled,
})
require.NoError(t, err)
got := a.AnalyzerVersions()
fmt.Printf("%v\n", got)
Expand Down Expand Up @@ -599,7 +607,9 @@ func TestAnalyzer_ImageConfigAnalyzerVersions(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a, err := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled, nil)
a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{
DisabledAnalyzers: tt.disabled,
})
require.NoError(t, err)
got := a.ImageConfigAnalyzerVersions()
assert.Equal(t, tt.want, got)
Expand Down
47 changes: 28 additions & 19 deletions pkg/fanal/analyzer/secret/secret.go
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"
"strings"

"github.com/samber/lo"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"

Expand All @@ -18,6 +19,9 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types"
)

// To make sure SecretAnalyzer implements analyzer.Initializer
var _ analyzer.Initializer = &SecretAnalyzer{}

const version = 1

var (
Expand All @@ -37,8 +41,9 @@ var (
}
)

type ScannerOption struct {
ConfigPath string
func init() {
// The scanner will be initialized later via InitScanner()
analyzer.RegisterAnalyzer(NewSecretAnalyzer(secret.Scanner{}, ""))
}

// SecretAnalyzer is an analyzer for secrets
Expand All @@ -47,27 +52,31 @@ type SecretAnalyzer struct {
configPath string
}

func RegisterSecretAnalyzer(opt ScannerOption) error {
a, err := newSecretAnalyzer(opt.ConfigPath)
if err != nil {
return xerrors.Errorf("secret scanner init error: %w", err)
func NewSecretAnalyzer(s secret.Scanner, configPath string) *SecretAnalyzer {
return &SecretAnalyzer{
scanner: s,
configPath: configPath,
}
analyzer.RegisterAnalyzer(a)
return nil
}

func newSecretAnalyzer(configPath string) (SecretAnalyzer, error) {
s, err := secret.NewScanner(configPath)
// Init initializes and sets a secret scanner
func (a *SecretAnalyzer) Init(opt analyzer.AnalyzerOptions) error {
if opt.SecretScannerOption.ConfigPath == a.configPath && !lo.IsEmpty(a.scanner) {
// This check is for tools importing Trivy and customize analyzers
// Never reach here in Trivy OSS
return nil
}
configPath := opt.SecretScannerOption.ConfigPath
c, err := secret.ParseConfig(configPath)
if err != nil {
return SecretAnalyzer{}, xerrors.Errorf("secret scanner error: %w", err)
return xerrors.Errorf("secret config error: %w", err)
}
return SecretAnalyzer{
scanner: s,
configPath: configPath,
}, nil
a.scanner = secret.NewScanner(c)
a.configPath = configPath
return nil
}

func (a SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
// Do not scan binaries
binary, err := isBinary(input.Content, input.Info.Size())
if binary || err != nil {
Expand Down Expand Up @@ -121,7 +130,7 @@ func isBinary(content dio.ReadSeekerAt, fileSize int64) (bool, error) {
return false, nil
}

func (a SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool {
func (a *SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool {
// Skip small files
if fi.Size() < 10 {
return false
Expand Down Expand Up @@ -161,10 +170,10 @@ func (a SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool {
return true
}

func (a SecretAnalyzer) Type() analyzer.Type {
func (a *SecretAnalyzer) Type() analyzer.Type {
return analyzer.TypeSecret
}

func (a SecretAnalyzer) Version() int {
func (a *SecretAnalyzer) Version() int {
return version
}
11 changes: 8 additions & 3 deletions pkg/fanal/analyzer/secret/secret_test.go
@@ -1,4 +1,4 @@
package secret
package secret_test

import (
"context"
Expand All @@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret"
"github.com/aquasecurity/trivy/pkg/fanal/types"
)

Expand Down Expand Up @@ -150,7 +151,10 @@ func TestSecretAnalyzer(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a, err := newSecretAnalyzer(tt.configPath)
a := &secret.SecretAnalyzer{}
err := a.Init(analyzer.AnalyzerOptions{
SecretScannerOption: analyzer.SecretScannerOption{ConfigPath: tt.configPath},
})
require.NoError(t, err)
content, err := os.Open(tt.filePath)
require.NoError(t, err)
Expand Down Expand Up @@ -205,7 +209,8 @@ func TestSecretRequire(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a, err := newSecretAnalyzer("")
a := secret.SecretAnalyzer{}
err := a.Init(analyzer.AnalyzerOptions{})
require.NoError(t, err)

fi, err := os.Stat(tt.filePath)
Expand Down
3 changes: 1 addition & 2 deletions pkg/fanal/artifact/artifact.go
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
misconf "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret"
"github.com/aquasecurity/trivy/pkg/fanal/types"
)

Expand All @@ -26,7 +25,7 @@ type Option struct {
RepoTag string

MisconfScannerOption misconf.ScannerOption
SecretScannerOption secret.ScannerOption
SecretScannerOption analyzer.SecretScannerOption
}

func (o *Option) Sort() {
Expand Down