mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-23 07:29:00 -08:00
feat(vex): consider root component for relationships (#6313)
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
This commit is contained in:
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
26
pkg/vex/testdata/openvex-oci.json
vendored
Normal file
26
pkg/vex/testdata/openvex-oci.json
vendored
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())
|
||||
@@ -28,6 +70,7 @@ func TestVEX_Filter(t *testing.T) {
|
||||
}
|
||||
type args struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
bom *core.BOM
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -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{},
|
||||
},
|
||||
@@ -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{
|
||||
vuln2,
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
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",
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user