Skip to content

Commit

Permalink
feat(vex): consider root component for relationships (#6313)
Browse files Browse the repository at this point in the history
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
  • Loading branch information
knqyf263 and DmitriyLewen authored Mar 19, 2024
1 parent 3177924 commit c4022d6
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 60 deletions.
9 changes: 8 additions & 1 deletion pkg/result/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"golang.org/x/xerrors"

dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/sbom/core"
sbomio "github.com/aquasecurity/trivy/pkg/sbom/io"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/vex"
)
Expand Down Expand Up @@ -87,11 +89,16 @@ func filterByVEX(report types.Report, opt FilterOption) error {
return nil
}

bom, err := sbomio.NewEncoder(core.Options{}).Encode(report)
if err != nil {
return xerrors.Errorf("unable to encode the SBOM: %w", err)
}

for i, result := range report.Results {
if len(result.Vulnerabilities) == 0 {
continue
}
vexDoc.Filter(&report.Results[i])
vexDoc.Filter(&report.Results[i], bom)
}
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/vex/csaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/purl"
"github.com/aquasecurity/trivy/pkg/sbom/core"
"github.com/aquasecurity/trivy/pkg/types"
)

Expand All @@ -23,7 +24,7 @@ func newCSAF(advisory csaf.Advisory) VEX {
}
}

func (v *CSAF) Filter(result *types.Result) {
func (v *CSAF) Filter(result *types.Result, _ *core.BOM) {
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool {
return string(*item.CVE) == vuln.VulnerabilityID
Expand Down
2 changes: 1 addition & 1 deletion pkg/vex/cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func newCycloneDX(sbom *core.BOM, vex *cdx.BOM) *CycloneDX {
}
}

func (v *CycloneDX) Filter(result *types.Result) {
func (v *CycloneDX) Filter(result *types.Result, _ *core.BOM) {
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
stmt, ok := lo.Find(v.statements, func(item Statement) bool {
return item.VulnerabilityID == vuln.VulnerabilityID
Expand Down
16 changes: 14 additions & 2 deletions pkg/vex/openvex.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
openvex "github.com/openvex/go-vex/pkg/vex"
"github.com/samber/lo"

"github.com/aquasecurity/trivy/pkg/sbom/core"
"github.com/aquasecurity/trivy/pkg/types"
)

Expand All @@ -17,13 +18,13 @@ func newOpenVEX(vex openvex.VEX) VEX {
}
}

func (v *OpenVEX) Filter(result *types.Result) {
func (v *OpenVEX) Filter(result *types.Result, bom *core.BOM) {
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
if vuln.PkgIdentifier.PURL == nil {
return true
}

stmts := v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil)
stmts := v.Matches(vuln, bom)
if len(stmts) == 0 {
return true
}
Expand All @@ -41,6 +42,17 @@ func (v *OpenVEX) Filter(result *types.Result) {
})
}

func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, bom *core.BOM) []openvex.Statement {
root := bom.Root()
if root != nil && root.PkgID.PURL != nil {
stmts := v.vex.Matches(vuln.VulnerabilityID, root.PkgID.PURL.String(), []string{vuln.PkgIdentifier.PURL.String()})
if len(stmts) != 0 {
return stmts
}
}
return v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil)
}

func findingStatus(status openvex.Status) types.FindingStatus {
switch status {
case openvex.StatusNotAffected:
Expand Down
26 changes: 26 additions & 0 deletions pkg/vex/testdata/openvex-oci.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"@context": "https://openvex.dev/ns/v0.2.0",
"author": "Aqua Security",
"role": "Project Release Bot",
"timestamp": "2023-01-16T19:07:16.853479631-06:00",
"version": 1,
"statements": [
{
"vulnerability": {
"name": "CVE-2022-3715"
},
"products": [
{
"@id": "pkg:oci/debian",
"subcomponents": [
{
"@id": "pkg:deb/debian/bash"
}
]
}
],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path"
}
]
}
3 changes: 2 additions & 1 deletion pkg/vex/vex.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/sbom"
"github.com/aquasecurity/trivy/pkg/sbom/core"
"github.com/aquasecurity/trivy/pkg/sbom/cyclonedx"
"github.com/aquasecurity/trivy/pkg/types"
)
Expand All @@ -21,7 +22,7 @@ import (
// Note: This is in the experimental stage and does not yet support many specifications.
// The implementation may change significantly.
type VEX interface {
Filter(*types.Result)
Filter(*types.Result, *core.BOM)
}

func New(filePath string, report types.Report) (VEX, error) {
Expand Down
181 changes: 127 additions & 54 deletions pkg/vex/vex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,48 @@ import (
"github.com/aquasecurity/trivy/pkg/vex"
)

var (
vuln1 = types.DetectedVulnerability{
VulnerabilityID: "CVE-2021-44228",
PkgName: "spring-boot",
InstalledVersion: "2.6.0",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework.boot",
Name: "spring-boot",
Version: "2.6.0",
},
},
}
vuln2 = types.DetectedVulnerability{
VulnerabilityID: "CVE-2021-0001",
PkgName: "spring-boot",
InstalledVersion: "2.6.0",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework.boot",
Name: "spring-boot",
Version: "2.6.0",
},
},
}
vuln3 = types.DetectedVulnerability{
VulnerabilityID: "CVE-2022-3715",
PkgName: "bash",
InstalledVersion: "5.2.15",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeDebian,
Namespace: "debian",
Name: "bash",
Version: "5.2.15",
},
},
}
)

func TestMain(m *testing.M) {
log.InitLogger(false, true)
os.Exit(m.Run())
Expand All @@ -28,6 +70,7 @@ func TestVEX_Filter(t *testing.T) {
}
type args struct {
vulns []types.DetectedVulnerability
bom *core.BOM
}
tests := []struct {
name string
Expand All @@ -42,21 +85,8 @@ func TestVEX_Filter(t *testing.T) {
filePath: "testdata/openvex.json",
},
args: args{
vulns: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2021-44228",
PkgName: "spring-boot",
InstalledVersion: "2.6.0",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework.boot",
Name: "spring-boot",
Version: "2.6.0",
},
},
},
},
vulns: []types.DetectedVulnerability{vuln1},
bom: newTestBOM(),
},
want: []types.DetectedVulnerability{},
},
Expand All @@ -67,49 +97,38 @@ func TestVEX_Filter(t *testing.T) {
},
args: args{
vulns: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2021-44228",
PkgName: "spring-boot",
InstalledVersion: "2.6.0",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework.boot",
Name: "spring-boot",
Version: "2.6.0",
},
},
},
{
VulnerabilityID: "CVE-2021-0001",
PkgName: "spring-boot",
InstalledVersion: "2.6.0",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework.boot",
Name: "spring-boot",
Version: "2.6.0",
},
},
},
vuln1, // filtered by VEX
vuln2,
},
bom: newTestBOM(),
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2021-0001",
PkgName: "spring-boot",
InstalledVersion: "2.6.0",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeMaven,
Namespace: "org.springframework.boot",
Name: "spring-boot",
Version: "2.6.0",
},
},
vuln2,
},
},
{
name: "OpenVEX, subcomponents, oci image",
fields: fields{
filePath: "testdata/openvex-oci.json",
},
args: args{
vulns: []types.DetectedVulnerability{
vuln3,
},
bom: newTestBOM(),
},
want: []types.DetectedVulnerability{},
},
{
name: "OpenVEX, subcomponents, wrong oci image",
fields: fields{
filePath: "testdata/openvex-oci.json",
},
args: args{
vulns: []types.DetectedVulnerability{vuln3},
bom: newTestBOM2(),
},
want: []types.DetectedVulnerability{vuln3},
},
{
name: "CycloneDX SBOM with CycloneDX VEX",
Expand Down Expand Up @@ -347,8 +366,62 @@ func TestVEX_Filter(t *testing.T) {
got := &types.Result{
Vulnerabilities: tt.args.vulns,
}
v.Filter(got)
v.Filter(got, tt.args.bom)
assert.Equal(t, tt.want, got.Vulnerabilities)
})
}
}

func newTestBOM() *core.BOM {
bom := core.NewBOM(core.Options{})
bom.AddComponent(&core.Component{
Root: true,
Type: core.TypeContainerImage,
Name: "debian:12",
PkgID: core.PkgID{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeOCI,
Name: "debian",
Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90",
Qualifiers: packageurl.Qualifiers{
{
Key: "tag",
Value: "12",
},
{
Key: "repository_url",
Value: "docker.io/library/debian",
},
},
},
},
})
return bom
}

func newTestBOM2() *core.BOM {
bom := core.NewBOM(core.Options{})
bom.AddComponent(&core.Component{
Root: true,
Type: core.TypeContainerImage,
Name: "ubuntu:24.04",
PkgID: core.PkgID{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeOCI,
Name: "ubuntu",
Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90",
Qualifiers: packageurl.Qualifiers{
{
Key: "tag",
Value: "24.04",
},
{
Key: "repository_url",
Value: "docker.io/library/ubuntu",
},
},
},
},
})
return bom
}

0 comments on commit c4022d6

Please sign in to comment.