Skip to content

Commit bcdc0bb

Browse files
authoredNov 21, 2024··
feat(go): construct dependencies in the parser (#7973)
Signed-off-by: knqyf263 <knqyf263@gmail.com>
1 parent e0f2054 commit bcdc0bb

File tree

3 files changed

+125
-32
lines changed

3 files changed

+125
-32
lines changed
 

‎pkg/dependency/parser/golang/binary/parse.go

+47-20
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import (
99
"sort"
1010
"strings"
1111

12+
"github.com/samber/lo"
1213
"github.com/spf13/pflag"
1314
"golang.org/x/mod/semver"
1415
"golang.org/x/xerrors"
1516

17+
"github.com/aquasecurity/trivy/pkg/dependency"
1618
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
1719
"github.com/aquasecurity/trivy/pkg/log"
1820
xio "github.com/aquasecurity/trivy/pkg/x/io"
@@ -64,27 +66,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
6466
pkgs := make(ftypes.Packages, 0, len(info.Deps)+2)
6567
pkgs = append(pkgs, ftypes.Package{
6668
// Add the Go version used to build this binary.
69+
ID: dependency.ID(ftypes.GoBinary, "stdlib", stdlibVersion),
6770
Name: "stdlib",
6871
Version: stdlibVersion,
6972
Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages.
7073
})
7174

72-
// There are times when gobinaries don't contain Main information.
73-
// e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.)
74-
if info.Main.Path != "" {
75-
pkgs = append(pkgs, ftypes.Package{
76-
// Add main module
77-
Name: info.Main.Path,
78-
// Only binaries installed with `go install` contain semver version of the main module.
79-
// Other binaries use the `(devel)` version, but still may contain a stamped version
80-
// set via `go build -ldflags='-X main.version=<semver>'`, so we fallback to this as.
81-
// as a secondary source.
82-
// See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477.
83-
Version: cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)),
84-
Relationship: ftypes.RelationshipRoot,
85-
})
86-
}
87-
8875
for _, dep := range info.Deps {
8976
// binaries with old go version may incorrectly add module in Deps
9077
// In this case Path == "", Version == "Devel"
@@ -98,14 +85,49 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
9885
mod = dep.Replace
9986
}
10087

88+
version := p.checkVersion(mod.Path, mod.Version)
10189
pkgs = append(pkgs, ftypes.Package{
102-
Name: mod.Path,
103-
Version: p.checkVersion(mod.Path, mod.Version),
90+
ID: dependency.ID(ftypes.GoBinary, mod.Path, version),
91+
Name: mod.Path,
92+
Version: version,
93+
Relationship: ftypes.RelationshipUnknown,
94+
})
95+
}
96+
97+
// There are times when gobinaries don't contain Main information.
98+
// e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.)
99+
var deps []ftypes.Dependency
100+
if info.Main.Path != "" {
101+
// Only binaries installed with `go install` contain semver version of the main module.
102+
// Other binaries use the `(devel)` version, but still may contain a stamped version
103+
// set via `go build -ldflags='-X main.version=<semver>'`, so we fallback to this as.
104+
// as a secondary source.
105+
// See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477.
106+
version := cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags))
107+
root := ftypes.Package{
108+
ID: dependency.ID(ftypes.GoBinary, info.Main.Path, version),
109+
Name: info.Main.Path,
110+
Version: version,
111+
Relationship: ftypes.RelationshipRoot,
112+
}
113+
114+
depIDs := lo.Map(pkgs, func(pkg ftypes.Package, _ int) string {
115+
return pkg.ID
104116
})
117+
sort.Strings(depIDs)
118+
119+
deps = []ftypes.Dependency{
120+
{
121+
ID: root.ID,
122+
DependsOn: depIDs, // Consider all packages as dependencies of the main module.
123+
},
124+
}
125+
// Add main module
126+
pkgs = append(pkgs, root)
105127
}
106128

107129
sort.Sort(pkgs)
108-
return pkgs, nil, nil
130+
return pkgs, deps, nil
109131
}
110132

111133
// 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 {
153175
// [1]: Versions that use prefixes from `defaultPrefixes`
154176
// [2]: Other versions
155177
var foundVersions = make([][]string, 3)
156-
defaultPrefixes := []string{"main", "common", "version", "cmd"}
178+
defaultPrefixes := []string{
179+
"main",
180+
"common",
181+
"version",
182+
"cmd",
183+
}
157184
for key, val := range x {
158185
// It's valid to set the -X flags with quotes so we trim any that might
159186
// have been provided: Ex:

‎pkg/dependency/parser/golang/binary/parse_test.go

+67-12
Original file line numberDiff line numberDiff line change
@@ -14,111 +14,166 @@ import (
1414
func TestParse(t *testing.T) {
1515
wantPkgs := []ftypes.Package{
1616
{
17+
ID: "github.com/aquasecurity/test",
1718
Name: "github.com/aquasecurity/test",
1819
Version: "",
1920
Relationship: ftypes.RelationshipRoot,
2021
},
2122
{
23+
ID: "stdlib@v1.15.2",
2224
Name: "stdlib",
2325
Version: "v1.15.2",
2426
Relationship: ftypes.RelationshipDirect,
2527
},
2628
{
29+
ID: "github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46",
2730
Name: "github.com/aquasecurity/go-pep440-version",
2831
Version: "v0.0.0-20210121094942-22b2f8951d46",
2932
},
3033
{
34+
ID: "github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492",
3135
Name: "github.com/aquasecurity/go-version",
3236
Version: "v0.0.0-20210121072130-637058cfe492",
3337
},
3438
{
39+
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
3540
Name: "golang.org/x/xerrors",
3641
Version: "v0.0.0-20200804184101-5ec99f83aff1",
3742
},
3843
}
44+
wantDeps := []ftypes.Dependency{
45+
{
46+
ID: "github.com/aquasecurity/test",
47+
DependsOn: []string{
48+
"github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46",
49+
"github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492",
50+
"golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
51+
"stdlib@v1.15.2",
52+
},
53+
},
54+
}
3955

4056
tests := []struct {
4157
name string
4258
inputFile string
43-
want []ftypes.Package
59+
wantPkgs []ftypes.Package
60+
wantDeps []ftypes.Dependency
4461
wantErr string
4562
}{
4663
{
4764
name: "ELF",
4865
inputFile: "testdata/test.elf",
49-
want: wantPkgs,
66+
wantPkgs: wantPkgs,
67+
wantDeps: wantDeps,
5068
},
5169
{
5270
name: "PE",
5371
inputFile: "testdata/test.exe",
54-
want: wantPkgs,
72+
wantPkgs: wantPkgs,
73+
wantDeps: wantDeps,
5574
},
5675
{
5776
name: "Mach-O",
5877
inputFile: "testdata/test.macho",
59-
want: wantPkgs,
78+
wantPkgs: wantPkgs,
79+
wantDeps: wantDeps,
6080
},
6181
{
6282
name: "with replace directive",
6383
inputFile: "testdata/replace.elf",
64-
want: []ftypes.Package{
84+
wantPkgs: []ftypes.Package{
6585
{
86+
ID: "github.com/ebati/trivy-mod-parse",
6687
Name: "github.com/ebati/trivy-mod-parse",
6788
Version: "",
6889
Relationship: ftypes.RelationshipRoot,
6990
},
7091
{
92+
ID: "stdlib@v1.16.4",
7193
Name: "stdlib",
7294
Version: "v1.16.4",
7395
Relationship: ftypes.RelationshipDirect,
7496
},
7597
{
98+
ID: "github.com/davecgh/go-spew@v1.1.1",
7699
Name: "github.com/davecgh/go-spew",
77100
Version: "v1.1.1",
78101
},
79102
{
103+
ID: "github.com/go-sql-driver/mysql@v1.5.0",
80104
Name: "github.com/go-sql-driver/mysql",
81105
Version: "v1.5.0",
82106
},
83107
},
108+
wantDeps: []ftypes.Dependency{
109+
{
110+
ID: "github.com/ebati/trivy-mod-parse",
111+
DependsOn: []string{
112+
"github.com/davecgh/go-spew@v1.1.1",
113+
"github.com/go-sql-driver/mysql@v1.5.0",
114+
"stdlib@v1.16.4",
115+
},
116+
},
117+
},
84118
},
85119
{
86120
name: "with semver main module version",
87121
inputFile: "testdata/semver-main-module-version.macho",
88-
want: []ftypes.Package{
122+
wantPkgs: []ftypes.Package{
89123
{
124+
ID: "go.etcd.io/bbolt@v1.3.5",
90125
Name: "go.etcd.io/bbolt",
91126
Version: "v1.3.5",
92127
Relationship: ftypes.RelationshipRoot,
93128
},
94129
{
130+
ID: "stdlib@v1.20.6",
95131
Name: "stdlib",
96132
Version: "v1.20.6",
97133
Relationship: ftypes.RelationshipDirect,
98134
},
99135
},
136+
wantDeps: []ftypes.Dependency{
137+
{
138+
ID: "go.etcd.io/bbolt@v1.3.5",
139+
DependsOn: []string{
140+
"stdlib@v1.20.6",
141+
},
142+
},
143+
},
100144
},
101145
{
102146
name: "with -ldflags=\"-X main.version=v1.0.0\"",
103147
inputFile: "testdata/main-version-via-ldflags.elf",
104-
want: []ftypes.Package{
148+
wantPkgs: []ftypes.Package{
105149
{
150+
ID: "github.com/aquasecurity/test@v1.0.0",
106151
Name: "github.com/aquasecurity/test",
107152
Version: "v1.0.0",
108153
Relationship: ftypes.RelationshipRoot,
109154
},
110155
{
156+
ID: "stdlib@v1.22.1",
111157
Name: "stdlib",
112158
Version: "v1.22.1",
113159
Relationship: ftypes.RelationshipDirect,
114160
},
115161
},
162+
wantDeps: []ftypes.Dependency{
163+
{
164+
ID: "github.com/aquasecurity/test@v1.0.0",
165+
DependsOn: []string{
166+
"stdlib@v1.22.1",
167+
},
168+
},
169+
},
116170
},
117171
{
118172
name: "goexperiment",
119173
inputFile: "testdata/goexperiment",
120-
want: []ftypes.Package{
174+
wantPkgs: []ftypes.Package{
121175
{
176+
ID: "stdlib@v1.22.1",
122177
Name: "stdlib",
123178
Version: "v1.22.1",
124179
Relationship: ftypes.RelationshipDirect,
@@ -137,15 +192,15 @@ func TestParse(t *testing.T) {
137192
require.NoError(t, err)
138193
defer f.Close()
139194

140-
got, _, err := binary.NewParser().Parse(f)
195+
gotPkgs, gotDeps, err := binary.NewParser().Parse(f)
141196
if tt.wantErr != "" {
142-
require.Error(t, err)
143-
assert.Contains(t, err.Error(), tt.wantErr)
197+
assert.ErrorContains(t, err, tt.wantErr)
144198
return
145199
}
146200

147201
require.NoError(t, err)
148-
assert.Equal(t, tt.want, got)
202+
assert.Equal(t, tt.wantPkgs, gotPkgs)
203+
assert.Equal(t, tt.wantDeps, gotDeps)
149204
})
150205
}
151206
}

‎pkg/fanal/analyzer/language/golang/binary/binary_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,35 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) {
3030
FilePath: "testdata/executable_gobinary",
3131
Packages: types.Packages{
3232
{
33+
ID: "github.com/aquasecurity/test",
3334
Name: "github.com/aquasecurity/test",
3435
Version: "",
3536
Relationship: types.RelationshipRoot,
37+
DependsOn: []string{
38+
"github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46",
39+
"github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492",
40+
"golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
41+
"stdlib@v1.15.2",
42+
},
3643
},
3744
{
45+
ID: "stdlib@v1.15.2",
3846
Name: "stdlib",
3947
Version: "v1.15.2",
4048
Relationship: types.RelationshipDirect,
4149
},
4250
{
51+
ID: "github.com/aquasecurity/go-pep440-version@v0.0.0-20210121094942-22b2f8951d46",
4352
Name: "github.com/aquasecurity/go-pep440-version",
4453
Version: "v0.0.0-20210121094942-22b2f8951d46",
4554
},
4655
{
56+
ID: "github.com/aquasecurity/go-version@v0.0.0-20210121072130-637058cfe492",
4757
Name: "github.com/aquasecurity/go-version",
4858
Version: "v0.0.0-20210121072130-637058cfe492",
4959
},
5060
{
61+
ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
5162
Name: "golang.org/x/xerrors",
5263
Version: "v0.0.0-20200804184101-5ec99f83aff1",
5364
},

0 commit comments

Comments
 (0)
Please sign in to comment.