From bcdc0bbf1f63777ff79d3ecadb8d4f916f376b7d Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 21 Nov 2024 20:05:16 +0900 Subject: [PATCH] feat(go): construct dependencies in the parser (#7973) Signed-off-by: knqyf263 --- pkg/dependency/parser/golang/binary/parse.go | 67 +++++++++++----- .../parser/golang/binary/parse_test.go | 79 ++++++++++++++++--- .../language/golang/binary/binary_test.go | 11 +++ 3 files changed, 125 insertions(+), 32 deletions(-) diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index 2a7a2a128de7..617303dd5497 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -9,10 +9,12 @@ import ( "sort" "strings" + "github.com/samber/lo" "github.com/spf13/pflag" "golang.org/x/mod/semver" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -64,27 +66,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc pkgs := make(ftypes.Packages, 0, len(info.Deps)+2) pkgs = append(pkgs, ftypes.Package{ // Add the Go version used to build this binary. + ID: dependency.ID(ftypes.GoBinary, "stdlib", stdlibVersion), Name: "stdlib", Version: stdlibVersion, Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. }) - // There are times when gobinaries don't contain Main information. - // e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.) - if info.Main.Path != "" { - pkgs = append(pkgs, ftypes.Package{ - // Add main module - Name: info.Main.Path, - // Only binaries installed with `go install` contain semver version of the main module. - // Other binaries use the `(devel)` version, but still may contain a stamped version - // set via `go build -ldflags='-X main.version='`, so we fallback to this as. - // as a secondary source. - // See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477. - Version: cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)), - Relationship: ftypes.RelationshipRoot, - }) - } - for _, dep := range info.Deps { // binaries with old go version may incorrectly add module in Deps // In this case Path == "", Version == "Devel" @@ -98,14 +85,49 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc mod = dep.Replace } + version := p.checkVersion(mod.Path, mod.Version) pkgs = append(pkgs, ftypes.Package{ - Name: mod.Path, - Version: p.checkVersion(mod.Path, mod.Version), + ID: dependency.ID(ftypes.GoBinary, mod.Path, version), + Name: mod.Path, + Version: version, + Relationship: ftypes.RelationshipUnknown, + }) + } + + // There are times when gobinaries don't contain Main information. + // e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.) + var deps []ftypes.Dependency + if info.Main.Path != "" { + // Only binaries installed with `go install` contain semver version of the main module. + // Other binaries use the `(devel)` version, but still may contain a stamped version + // set via `go build -ldflags='-X main.version='`, so we fallback to this as. + // as a secondary source. + // See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477. + version := cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)) + root := ftypes.Package{ + ID: dependency.ID(ftypes.GoBinary, info.Main.Path, version), + Name: info.Main.Path, + Version: version, + Relationship: ftypes.RelationshipRoot, + } + + depIDs := lo.Map(pkgs, func(pkg ftypes.Package, _ int) string { + return pkg.ID }) + sort.Strings(depIDs) + + deps = []ftypes.Dependency{ + { + ID: root.ID, + DependsOn: depIDs, // Consider all packages as dependencies of the main module. + }, + } + // Add main module + pkgs = append(pkgs, root) } sort.Sort(pkgs) - return pkgs, nil, nil + return pkgs, deps, nil } // checkVersion detects `(devel)` versions, removes them and adds a debug message about it. @@ -153,7 +175,12 @@ func (p *Parser) ParseLDFlags(name string, flags []string) string { // [1]: Versions that use prefixes from `defaultPrefixes` // [2]: Other versions var foundVersions = make([][]string, 3) - defaultPrefixes := []string{"main", "common", "version", "cmd"} + defaultPrefixes := []string{ + "main", + "common", + "version", + "cmd", + } for key, val := range x { // It's valid to set the -X flags with quotes so we trim any that might // have been provided: Ex: diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index aade2a32cf24..eebb652a2a4d 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -14,111 +14,166 @@ import ( func TestParse(t *testing.T) { wantPkgs := []ftypes.Package{ { + ID: "github.com/aquasecurity/test", Name: "github.com/aquasecurity/test", Version: "", Relationship: ftypes.RelationshipRoot, }, { + ID: "stdlib@v1.15.2", Name: "stdlib", Version: "v1.15.2", Relationship: ftypes.RelationshipDirect, }, { + ID: "github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46", Name: "github.com/aquasecurity/go-pep440-version", Version: "v0.0.0-20210121094942-22b2f8951d46", }, { + ID: "github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492", Name: "github.com/aquasecurity/go-version", Version: "v0.0.0-20210121072130-637058cfe492", }, { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "v0.0.0-20200804184101-5ec99f83aff1", }, } + wantDeps := []ftypes.Dependency{ + { + ID: "github.com/aquasecurity/test", + DependsOn: []string{ + "github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46", + "github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492", + "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + "stdlib@v1.15.2", + }, + }, + } tests := []struct { name string inputFile string - want []ftypes.Package + wantPkgs []ftypes.Package + wantDeps []ftypes.Dependency wantErr string }{ { name: "ELF", inputFile: "testdata/test.elf", - want: wantPkgs, + wantPkgs: wantPkgs, + wantDeps: wantDeps, }, { name: "PE", inputFile: "testdata/test.exe", - want: wantPkgs, + wantPkgs: wantPkgs, + wantDeps: wantDeps, }, { name: "Mach-O", inputFile: "testdata/test.macho", - want: wantPkgs, + wantPkgs: wantPkgs, + wantDeps: wantDeps, }, { name: "with replace directive", inputFile: "testdata/replace.elf", - want: []ftypes.Package{ + wantPkgs: []ftypes.Package{ { + ID: "github.com/ebati/trivy-mod-parse", Name: "github.com/ebati/trivy-mod-parse", Version: "", Relationship: ftypes.RelationshipRoot, }, { + ID: "stdlib@v1.16.4", Name: "stdlib", Version: "v1.16.4", Relationship: ftypes.RelationshipDirect, }, { + ID: "github.com/davecgh/go-spew@v1.1.1", Name: "github.com/davecgh/go-spew", Version: "v1.1.1", }, { + ID: "github.com/go-sql-driver/mysql@v1.5.0", Name: "github.com/go-sql-driver/mysql", Version: "v1.5.0", }, }, + wantDeps: []ftypes.Dependency{ + { + ID: "github.com/ebati/trivy-mod-parse", + DependsOn: []string{ + "github.com/davecgh/go-spew@v1.1.1", + "github.com/go-sql-driver/mysql@v1.5.0", + "stdlib@v1.16.4", + }, + }, + }, }, { name: "with semver main module version", inputFile: "testdata/semver-main-module-version.macho", - want: []ftypes.Package{ + wantPkgs: []ftypes.Package{ { + ID: "go.etcd.io/bbolt@v1.3.5", Name: "go.etcd.io/bbolt", Version: "v1.3.5", Relationship: ftypes.RelationshipRoot, }, { + ID: "stdlib@v1.20.6", Name: "stdlib", Version: "v1.20.6", Relationship: ftypes.RelationshipDirect, }, }, + wantDeps: []ftypes.Dependency{ + { + ID: "go.etcd.io/bbolt@v1.3.5", + DependsOn: []string{ + "stdlib@v1.20.6", + }, + }, + }, }, { name: "with -ldflags=\"-X main.version=v1.0.0\"", inputFile: "testdata/main-version-via-ldflags.elf", - want: []ftypes.Package{ + wantPkgs: []ftypes.Package{ { + ID: "github.com/aquasecurity/test@v1.0.0", Name: "github.com/aquasecurity/test", Version: "v1.0.0", Relationship: ftypes.RelationshipRoot, }, { + ID: "stdlib@v1.22.1", Name: "stdlib", Version: "v1.22.1", Relationship: ftypes.RelationshipDirect, }, }, + wantDeps: []ftypes.Dependency{ + { + ID: "github.com/aquasecurity/test@v1.0.0", + DependsOn: []string{ + "stdlib@v1.22.1", + }, + }, + }, }, { name: "goexperiment", inputFile: "testdata/goexperiment", - want: []ftypes.Package{ + wantPkgs: []ftypes.Package{ { + ID: "stdlib@v1.22.1", Name: "stdlib", Version: "v1.22.1", Relationship: ftypes.RelationshipDirect, @@ -137,15 +192,15 @@ func TestParse(t *testing.T) { require.NoError(t, err) defer f.Close() - got, _, err := binary.NewParser().Parse(f) + gotPkgs, gotDeps, err := binary.NewParser().Parse(f) if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) - assert.Equal(t, tt.want, got) + assert.Equal(t, tt.wantPkgs, gotPkgs) + assert.Equal(t, tt.wantDeps, gotDeps) }) } } diff --git a/pkg/fanal/analyzer/language/golang/binary/binary_test.go b/pkg/fanal/analyzer/language/golang/binary/binary_test.go index 041d43e1b45b..650968667e06 100644 --- a/pkg/fanal/analyzer/language/golang/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/golang/binary/binary_test.go @@ -30,24 +30,35 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/executable_gobinary", Packages: types.Packages{ { + ID: "github.com/aquasecurity/test", Name: "github.com/aquasecurity/test", Version: "", Relationship: types.RelationshipRoot, + DependsOn: []string{ + "github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46", + "github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492", + "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + "stdlib@v1.15.2", + }, }, { + ID: "stdlib@v1.15.2", Name: "stdlib", Version: "v1.15.2", Relationship: types.RelationshipDirect, }, { + ID: "github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46", Name: "github.com/aquasecurity/go-pep440-version", Version: "v0.0.0-20210121094942-22b2f8951d46", }, { + ID: "github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492", Name: "github.com/aquasecurity/go-version", Version: "v0.0.0-20210121072130-637058cfe492", }, { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "v0.0.0-20200804184101-5ec99f83aff1", },