Skip to content

Commit

Permalink
refactor: add a new interface for initializing analyzers (#2835)
Browse files Browse the repository at this point in the history
Signed-off-by: knqyf263 <knqyf263@gmail.com>
  • Loading branch information
knqyf263 committed Sep 12, 2022
1 parent 63c3b8e commit 2de903c
Show file tree
Hide file tree
Showing 16 changed files with 182 additions and 101 deletions.
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 @@ -2142,8 +2142,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

0 comments on commit 2de903c

Please sign in to comment.