Files
trivy/analyzer/analyzer_test.go
Teppei Fukuda bcc0218200 feat: add post handler (fanal#460)
Co-authored-by: Liam Galvin <liam.galvin@aquasec.com>
Co-authored-by: Liam Galvin <liamgalvin@protonmail.com>
2022-05-09 22:43:39 +03:00

558 lines
12 KiB
Go

package analyzer_test
import (
"context"
"errors"
"fmt"
"os"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/semaphore"
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/analyzer"
aos "github.com/aquasecurity/fanal/analyzer/os"
"github.com/aquasecurity/fanal/types"
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
_ "github.com/aquasecurity/fanal/analyzer/command/apk"
_ "github.com/aquasecurity/fanal/analyzer/language/ruby/bundler"
_ "github.com/aquasecurity/fanal/analyzer/os/alpine"
_ "github.com/aquasecurity/fanal/analyzer/os/ubuntu"
_ "github.com/aquasecurity/fanal/analyzer/pkg/apk"
_ "github.com/aquasecurity/fanal/analyzer/repo/apk"
_ "github.com/aquasecurity/fanal/handler/all"
)
type mockConfigAnalyzer struct{}
func (mockConfigAnalyzer) Required(targetOS types.OS) bool {
return targetOS.Family == "alpine"
}
func (mockConfigAnalyzer) Analyze(targetOS types.OS, configBlob []byte) ([]types.Package, error) {
if string(configBlob) != `foo` {
return nil, errors.New("error")
}
return []types.Package{
{Name: "musl", Version: "1.1.24-r2"},
}, nil
}
func (mockConfigAnalyzer) Type() analyzer.Type {
return analyzer.Type("test")
}
func (mockConfigAnalyzer) Version() int {
return 1
}
func TestMain(m *testing.M) {
analyzer.RegisterConfigAnalyzer(mockConfigAnalyzer{})
os.Exit(m.Run())
}
func TestAnalysisResult_Merge(t *testing.T) {
type fields struct {
m sync.Mutex
OS *types.OS
PackageInfos []types.PackageInfo
Applications []types.Application
}
type args struct {
new *analyzer.AnalysisResult
}
tests := []struct {
name string
fields fields
args args
want analyzer.AnalysisResult
}{
{
name: "happy path",
fields: fields{
OS: &types.OS{
Family: aos.Debian,
Name: "9.8",
},
PackageInfos: []types.PackageInfo{
{
FilePath: "var/lib/dpkg/status.d/libc",
Packages: []types.Package{
{Name: "libc", Version: "1.2.3"},
},
},
},
Applications: []types.Application{
{
Type: "bundler",
FilePath: "app/Gemfile.lock",
Libraries: []types.Package{
{
Name: "rails",
Version: "5.0.0",
},
},
},
},
},
args: args{
new: &analyzer.AnalysisResult{
PackageInfos: []types.PackageInfo{
{
FilePath: "var/lib/dpkg/status.d/openssl",
Packages: []types.Package{
{Name: "openssl", Version: "1.1.1"},
},
},
},
Applications: []types.Application{
{
Type: "bundler",
FilePath: "app2/Gemfile.lock",
Libraries: []types.Package{
{
Name: "nokogiri",
Version: "1.0.0",
},
},
},
},
},
},
want: analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Debian,
Name: "9.8",
},
PackageInfos: []types.PackageInfo{
{
FilePath: "var/lib/dpkg/status.d/libc",
Packages: []types.Package{
{Name: "libc", Version: "1.2.3"},
},
},
{
FilePath: "var/lib/dpkg/status.d/openssl",
Packages: []types.Package{
{Name: "openssl", Version: "1.1.1"},
},
},
},
Applications: []types.Application{
{
Type: "bundler",
FilePath: "app/Gemfile.lock",
Libraries: []types.Package{
{
Name: "rails",
Version: "5.0.0",
},
},
},
{
Type: "bundler",
FilePath: "app2/Gemfile.lock",
Libraries: []types.Package{
{
Name: "nokogiri",
Version: "1.0.0",
},
},
},
},
},
},
{
name: "redhat must be replaced with oracle",
fields: fields{
OS: &types.OS{
Family: aos.RedHat, // this must be overwritten
Name: "8.0",
},
},
args: args{
new: &analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Oracle,
Name: "8.0",
},
},
},
want: analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Oracle,
Name: "8.0",
},
},
},
{
name: "debian must be replaced with ubuntu",
fields: fields{
OS: &types.OS{
Family: aos.Debian, // this must be overwritten
Name: "9.0",
},
},
args: args{
new: &analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Ubuntu,
Name: "18.04",
},
},
},
want: analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Ubuntu,
Name: "18.04",
},
},
},
{
name: "alpine OS needs to be extended with apk repositories",
fields: fields{
OS: &types.OS{
Family: aos.Alpine,
Name: "3.15.3",
},
},
args: args{
new: &analyzer.AnalysisResult{
Repository: &types.Repository{
Family: aos.Alpine,
Release: "edge",
},
},
},
want: analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Alpine,
Name: "3.15.3",
},
Repository: &types.Repository{
Family: aos.Alpine,
Release: "edge",
},
},
},
{
name: "alpine must not be replaced with oracle",
fields: fields{
OS: &types.OS{
Family: aos.Alpine, // this must not be overwritten
Name: "3.11",
},
},
args: args{
new: &analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Oracle,
Name: "8.0",
},
},
},
want: analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Alpine, // this must not be overwritten
Name: "3.11",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := analyzer.AnalysisResult{
OS: tt.fields.OS,
PackageInfos: tt.fields.PackageInfos,
Applications: tt.fields.Applications,
}
r.Merge(tt.args.new)
assert.Equal(t, tt.want, r)
})
}
}
func TestAnalyzeFile(t *testing.T) {
type args struct {
filePath string
testFilePath string
disabledAnalyzers []analyzer.Type
}
tests := []struct {
name string
args args
want *analyzer.AnalysisResult
wantErr string
}{
{
name: "happy path with os analyzer",
args: args{
filePath: "/etc/alpine-release",
testFilePath: "testdata/etc/alpine-release",
},
want: &analyzer.AnalysisResult{
OS: &types.OS{
Family: "alpine",
Name: "3.11.6",
},
},
},
{
name: "happy path with disabled os analyzer",
args: args{
filePath: "/etc/alpine-release",
testFilePath: "testdata/etc/alpine-release",
disabledAnalyzers: []analyzer.Type{analyzer.TypeAlpine},
},
want: &analyzer.AnalysisResult{},
},
{
name: "happy path with package analyzer",
args: args{
filePath: "/lib/apk/db/installed",
testFilePath: "testdata/lib/apk/db/installed",
},
want: &analyzer.AnalysisResult{
PackageInfos: []types.PackageInfo{
{
FilePath: "/lib/apk/db/installed",
Packages: []types.Package{
{Name: "musl", Version: "1.1.24-r2", SrcName: "musl", SrcVersion: "1.1.24-r2", License: "MIT"},
},
},
},
SystemInstalledFiles: []string{
"lib/libc.musl-x86_64.so.1",
"lib/ld-musl-x86_64.so.1",
},
},
},
{
name: "happy path with disabled package analyzer",
args: args{
filePath: "/lib/apk/db/installed",
testFilePath: "testdata/lib/apk/db/installed",
disabledAnalyzers: []analyzer.Type{analyzer.TypeApk},
},
want: &analyzer.AnalysisResult{},
},
{
name: "happy path with library analyzer",
args: args{
filePath: "/app/Gemfile.lock",
testFilePath: "testdata/app/Gemfile.lock",
},
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: "bundler",
FilePath: "/app/Gemfile.lock",
Libraries: []types.Package{
{
Name: "actioncable",
Version: "5.2.3",
},
},
},
},
},
},
{
name: "happy path with invalid os information",
args: args{
filePath: "/etc/lsb-release",
testFilePath: "testdata/etc/hostname",
},
want: &analyzer.AnalysisResult{},
},
{
name: "happy path with a directory",
args: args{
filePath: "/etc/lsb-release",
testFilePath: "testdata/etc",
},
want: &analyzer.AnalysisResult{},
},
{
name: "ignore permission error",
args: args{
filePath: "/etc/alpine-release",
testFilePath: "testdata/no-permission",
},
want: &analyzer.AnalysisResult{},
},
{
name: "sad path with opener error",
args: args{
filePath: "/lib/apk/db/installed",
testFilePath: "testdata/error",
},
wantErr: "unable to open /lib/apk/db/installed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var wg sync.WaitGroup
limit := semaphore.NewWeighted(3)
got := new(analyzer.AnalysisResult)
a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers)
info, err := os.Stat(tt.args.testFilePath)
require.NoError(t, err)
ctx := context.Background()
err = a.AnalyzeFile(ctx, &wg, limit, got, "", tt.args.filePath, info,
func() (dio.ReadSeekCloserAt, error) {
if tt.args.testFilePath == "testdata/error" {
return nil, xerrors.New("error")
} else if tt.args.testFilePath == "testdata/no-permission" {
os.Chmod(tt.args.testFilePath, 0000)
t.Cleanup(func() {
os.Chmod(tt.args.testFilePath, 0644)
})
}
return os.Open(tt.args.testFilePath)
},
nil, analyzer.AnalysisOptions{},
)
wg.Wait()
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestAnalyzeConfig(t *testing.T) {
type args struct {
targetOS types.OS
configBlob []byte
disabledAnalyzers []analyzer.Type
}
tests := []struct {
name string
args args
want []types.Package
}{
{
name: "happy path",
args: args{
targetOS: types.OS{
Family: "alpine",
Name: "3.11.6",
},
configBlob: []byte("foo"),
},
want: []types.Package{
{Name: "musl", Version: "1.1.24-r2"},
},
},
{
name: "non-target OS",
args: args{
targetOS: types.OS{
Family: "debian",
Name: "9.2",
},
configBlob: []byte("foo"),
},
},
{
name: "Analyze returns an error",
args: args{
targetOS: types.OS{
Family: "alpine",
Name: "3.11.6",
},
configBlob: []byte("bar"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.args.disabledAnalyzers)
got := a.AnalyzeImageConfig(tt.args.targetOS, tt.args.configBlob)
assert.Equal(t, tt.want, got)
})
}
}
func TestAnalyzer_AnalyzerVersions(t *testing.T) {
tests := []struct {
name string
disabled []analyzer.Type
want map[string]int
}{
{
name: "happy path",
disabled: []analyzer.Type{},
want: map[string]int{
"alpine": 1,
"apk-repo": 1,
"apk": 1,
"bundler": 1,
"ubuntu": 1,
},
},
{
name: "disable analyzers",
disabled: []analyzer.Type{analyzer.TypeAlpine, analyzer.TypeApkRepo, analyzer.TypeUbuntu},
want: map[string]int{
"apk": 1,
"bundler": 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled)
got := a.AnalyzerVersions()
fmt.Printf("%v\n", got)
assert.Equal(t, tt.want, got)
})
}
}
func TestAnalyzer_ImageConfigAnalyzerVersions(t *testing.T) {
tests := []struct {
name string
disabled []analyzer.Type
want map[string]int
}{
{
name: "happy path",
disabled: []analyzer.Type{},
want: map[string]int{
"apk-command": 1,
"test": 1,
},
},
{
name: "disable analyzers",
disabled: []analyzer.Type{analyzer.TypeAlpine, analyzer.TypeApkCommand},
want: map[string]int{
"test": 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, tt.disabled)
got := a.ImageConfigAnalyzerVersions()
assert.Equal(t, tt.want, got)
})
}
}