Skip to content

Commit c4022d6

Browse files
feat(vex): consider root component for relationships (#6313)
Co-authored-by: DmitriyLewen <[email protected]>
1 parent 3177924 commit c4022d6

File tree

7 files changed

+180
-60
lines changed

7 files changed

+180
-60
lines changed

pkg/result/filter.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"golang.org/x/xerrors"
1515

1616
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
17+
"github.com/aquasecurity/trivy/pkg/sbom/core"
18+
sbomio "github.com/aquasecurity/trivy/pkg/sbom/io"
1719
"github.com/aquasecurity/trivy/pkg/types"
1820
"github.com/aquasecurity/trivy/pkg/vex"
1921
)
@@ -87,11 +89,16 @@ func filterByVEX(report types.Report, opt FilterOption) error {
8789
return nil
8890
}
8991

92+
bom, err := sbomio.NewEncoder(core.Options{}).Encode(report)
93+
if err != nil {
94+
return xerrors.Errorf("unable to encode the SBOM: %w", err)
95+
}
96+
9097
for i, result := range report.Results {
9198
if len(result.Vulnerabilities) == 0 {
9299
continue
93100
}
94-
vexDoc.Filter(&report.Results[i])
101+
vexDoc.Filter(&report.Results[i], bom)
95102
}
96103
return nil
97104
}

pkg/vex/csaf.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/aquasecurity/trivy/pkg/log"
1010
"github.com/aquasecurity/trivy/pkg/purl"
11+
"github.com/aquasecurity/trivy/pkg/sbom/core"
1112
"github.com/aquasecurity/trivy/pkg/types"
1213
)
1314

@@ -23,7 +24,7 @@ func newCSAF(advisory csaf.Advisory) VEX {
2324
}
2425
}
2526

26-
func (v *CSAF) Filter(result *types.Result) {
27+
func (v *CSAF) Filter(result *types.Result, _ *core.BOM) {
2728
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
2829
found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool {
2930
return string(*item.CVE) == vuln.VulnerabilityID

pkg/vex/cyclonedx.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func newCycloneDX(sbom *core.BOM, vex *cdx.BOM) *CycloneDX {
4545
}
4646
}
4747

48-
func (v *CycloneDX) Filter(result *types.Result) {
48+
func (v *CycloneDX) Filter(result *types.Result, _ *core.BOM) {
4949
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
5050
stmt, ok := lo.Find(v.statements, func(item Statement) bool {
5151
return item.VulnerabilityID == vuln.VulnerabilityID

pkg/vex/openvex.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
openvex "github.com/openvex/go-vex/pkg/vex"
55
"github.com/samber/lo"
66

7+
"github.com/aquasecurity/trivy/pkg/sbom/core"
78
"github.com/aquasecurity/trivy/pkg/types"
89
)
910

@@ -17,13 +18,13 @@ func newOpenVEX(vex openvex.VEX) VEX {
1718
}
1819
}
1920

20-
func (v *OpenVEX) Filter(result *types.Result) {
21+
func (v *OpenVEX) Filter(result *types.Result, bom *core.BOM) {
2122
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
2223
if vuln.PkgIdentifier.PURL == nil {
2324
return true
2425
}
2526

26-
stmts := v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil)
27+
stmts := v.Matches(vuln, bom)
2728
if len(stmts) == 0 {
2829
return true
2930
}
@@ -41,6 +42,17 @@ func (v *OpenVEX) Filter(result *types.Result) {
4142
})
4243
}
4344

45+
func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, bom *core.BOM) []openvex.Statement {
46+
root := bom.Root()
47+
if root != nil && root.PkgID.PURL != nil {
48+
stmts := v.vex.Matches(vuln.VulnerabilityID, root.PkgID.PURL.String(), []string{vuln.PkgIdentifier.PURL.String()})
49+
if len(stmts) != 0 {
50+
return stmts
51+
}
52+
}
53+
return v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil)
54+
}
55+
4456
func findingStatus(status openvex.Status) types.FindingStatus {
4557
switch status {
4658
case openvex.StatusNotAffected:

pkg/vex/testdata/openvex-oci.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"@context": "https://openvex.dev/ns/v0.2.0",
3+
"author": "Aqua Security",
4+
"role": "Project Release Bot",
5+
"timestamp": "2023-01-16T19:07:16.853479631-06:00",
6+
"version": 1,
7+
"statements": [
8+
{
9+
"vulnerability": {
10+
"name": "CVE-2022-3715"
11+
},
12+
"products": [
13+
{
14+
"@id": "pkg:oci/debian",
15+
"subcomponents": [
16+
{
17+
"@id": "pkg:deb/debian/bash"
18+
}
19+
]
20+
}
21+
],
22+
"status": "not_affected",
23+
"justification": "vulnerable_code_not_in_execute_path"
24+
}
25+
]
26+
}

pkg/vex/vex.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
1515
"github.com/aquasecurity/trivy/pkg/sbom"
16+
"github.com/aquasecurity/trivy/pkg/sbom/core"
1617
"github.com/aquasecurity/trivy/pkg/sbom/cyclonedx"
1718
"github.com/aquasecurity/trivy/pkg/types"
1819
)
@@ -21,7 +22,7 @@ import (
2122
// Note: This is in the experimental stage and does not yet support many specifications.
2223
// The implementation may change significantly.
2324
type VEX interface {
24-
Filter(*types.Result)
25+
Filter(*types.Result, *core.BOM)
2526
}
2627

2728
func New(filePath string, report types.Report) (VEX, error) {

pkg/vex/vex_test.go

+127-54
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,48 @@ import (
1616
"github.com/aquasecurity/trivy/pkg/vex"
1717
)
1818

19+
var (
20+
vuln1 = types.DetectedVulnerability{
21+
VulnerabilityID: "CVE-2021-44228",
22+
PkgName: "spring-boot",
23+
InstalledVersion: "2.6.0",
24+
PkgIdentifier: ftypes.PkgIdentifier{
25+
PURL: &packageurl.PackageURL{
26+
Type: packageurl.TypeMaven,
27+
Namespace: "org.springframework.boot",
28+
Name: "spring-boot",
29+
Version: "2.6.0",
30+
},
31+
},
32+
}
33+
vuln2 = types.DetectedVulnerability{
34+
VulnerabilityID: "CVE-2021-0001",
35+
PkgName: "spring-boot",
36+
InstalledVersion: "2.6.0",
37+
PkgIdentifier: ftypes.PkgIdentifier{
38+
PURL: &packageurl.PackageURL{
39+
Type: packageurl.TypeMaven,
40+
Namespace: "org.springframework.boot",
41+
Name: "spring-boot",
42+
Version: "2.6.0",
43+
},
44+
},
45+
}
46+
vuln3 = types.DetectedVulnerability{
47+
VulnerabilityID: "CVE-2022-3715",
48+
PkgName: "bash",
49+
InstalledVersion: "5.2.15",
50+
PkgIdentifier: ftypes.PkgIdentifier{
51+
PURL: &packageurl.PackageURL{
52+
Type: packageurl.TypeDebian,
53+
Namespace: "debian",
54+
Name: "bash",
55+
Version: "5.2.15",
56+
},
57+
},
58+
}
59+
)
60+
1961
func TestMain(m *testing.M) {
2062
log.InitLogger(false, true)
2163
os.Exit(m.Run())
@@ -28,6 +70,7 @@ func TestVEX_Filter(t *testing.T) {
2870
}
2971
type args struct {
3072
vulns []types.DetectedVulnerability
73+
bom *core.BOM
3174
}
3275
tests := []struct {
3376
name string
@@ -42,21 +85,8 @@ func TestVEX_Filter(t *testing.T) {
4285
filePath: "testdata/openvex.json",
4386
},
4487
args: args{
45-
vulns: []types.DetectedVulnerability{
46-
{
47-
VulnerabilityID: "CVE-2021-44228",
48-
PkgName: "spring-boot",
49-
InstalledVersion: "2.6.0",
50-
PkgIdentifier: ftypes.PkgIdentifier{
51-
PURL: &packageurl.PackageURL{
52-
Type: packageurl.TypeMaven,
53-
Namespace: "org.springframework.boot",
54-
Name: "spring-boot",
55-
Version: "2.6.0",
56-
},
57-
},
58-
},
59-
},
88+
vulns: []types.DetectedVulnerability{vuln1},
89+
bom: newTestBOM(),
6090
},
6191
want: []types.DetectedVulnerability{},
6292
},
@@ -67,49 +97,38 @@ func TestVEX_Filter(t *testing.T) {
6797
},
6898
args: args{
6999
vulns: []types.DetectedVulnerability{
70-
{
71-
VulnerabilityID: "CVE-2021-44228",
72-
PkgName: "spring-boot",
73-
InstalledVersion: "2.6.0",
74-
PkgIdentifier: ftypes.PkgIdentifier{
75-
PURL: &packageurl.PackageURL{
76-
Type: packageurl.TypeMaven,
77-
Namespace: "org.springframework.boot",
78-
Name: "spring-boot",
79-
Version: "2.6.0",
80-
},
81-
},
82-
},
83-
{
84-
VulnerabilityID: "CVE-2021-0001",
85-
PkgName: "spring-boot",
86-
InstalledVersion: "2.6.0",
87-
PkgIdentifier: ftypes.PkgIdentifier{
88-
PURL: &packageurl.PackageURL{
89-
Type: packageurl.TypeMaven,
90-
Namespace: "org.springframework.boot",
91-
Name: "spring-boot",
92-
Version: "2.6.0",
93-
},
94-
},
95-
},
100+
vuln1, // filtered by VEX
101+
vuln2,
96102
},
103+
bom: newTestBOM(),
97104
},
98105
want: []types.DetectedVulnerability{
99-
{
100-
VulnerabilityID: "CVE-2021-0001",
101-
PkgName: "spring-boot",
102-
InstalledVersion: "2.6.0",
103-
PkgIdentifier: ftypes.PkgIdentifier{
104-
PURL: &packageurl.PackageURL{
105-
Type: packageurl.TypeMaven,
106-
Namespace: "org.springframework.boot",
107-
Name: "spring-boot",
108-
Version: "2.6.0",
109-
},
110-
},
106+
vuln2,
107+
},
108+
},
109+
{
110+
name: "OpenVEX, subcomponents, oci image",
111+
fields: fields{
112+
filePath: "testdata/openvex-oci.json",
113+
},
114+
args: args{
115+
vulns: []types.DetectedVulnerability{
116+
vuln3,
111117
},
118+
bom: newTestBOM(),
119+
},
120+
want: []types.DetectedVulnerability{},
121+
},
122+
{
123+
name: "OpenVEX, subcomponents, wrong oci image",
124+
fields: fields{
125+
filePath: "testdata/openvex-oci.json",
126+
},
127+
args: args{
128+
vulns: []types.DetectedVulnerability{vuln3},
129+
bom: newTestBOM2(),
112130
},
131+
want: []types.DetectedVulnerability{vuln3},
113132
},
114133
{
115134
name: "CycloneDX SBOM with CycloneDX VEX",
@@ -347,8 +366,62 @@ func TestVEX_Filter(t *testing.T) {
347366
got := &types.Result{
348367
Vulnerabilities: tt.args.vulns,
349368
}
350-
v.Filter(got)
369+
v.Filter(got, tt.args.bom)
351370
assert.Equal(t, tt.want, got.Vulnerabilities)
352371
})
353372
}
354373
}
374+
375+
func newTestBOM() *core.BOM {
376+
bom := core.NewBOM(core.Options{})
377+
bom.AddComponent(&core.Component{
378+
Root: true,
379+
Type: core.TypeContainerImage,
380+
Name: "debian:12",
381+
PkgID: core.PkgID{
382+
PURL: &packageurl.PackageURL{
383+
Type: packageurl.TypeOCI,
384+
Name: "debian",
385+
Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90",
386+
Qualifiers: packageurl.Qualifiers{
387+
{
388+
Key: "tag",
389+
Value: "12",
390+
},
391+
{
392+
Key: "repository_url",
393+
Value: "docker.io/library/debian",
394+
},
395+
},
396+
},
397+
},
398+
})
399+
return bom
400+
}
401+
402+
func newTestBOM2() *core.BOM {
403+
bom := core.NewBOM(core.Options{})
404+
bom.AddComponent(&core.Component{
405+
Root: true,
406+
Type: core.TypeContainerImage,
407+
Name: "ubuntu:24.04",
408+
PkgID: core.PkgID{
409+
PURL: &packageurl.PackageURL{
410+
Type: packageurl.TypeOCI,
411+
Name: "ubuntu",
412+
Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90",
413+
Qualifiers: packageurl.Qualifiers{
414+
{
415+
Key: "tag",
416+
Value: "24.04",
417+
},
418+
{
419+
Key: "repository_url",
420+
Value: "docker.io/library/ubuntu",
421+
},
422+
},
423+
},
424+
},
425+
})
426+
return bom
427+
}

0 commit comments

Comments
 (0)