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"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
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/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/vex"
|
"github.com/aquasecurity/trivy/pkg/vex"
|
||||||
)
|
)
|
||||||
@@ -87,11 +89,16 @@ func filterByVEX(report types.Report, opt FilterOption) error {
|
|||||||
return nil
|
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 {
|
for i, result := range report.Results {
|
||||||
if len(result.Vulnerabilities) == 0 {
|
if len(result.Vulnerabilities) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vexDoc.Filter(&report.Results[i])
|
vexDoc.Filter(&report.Results[i], bom)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/purl"
|
"github.com/aquasecurity/trivy/pkg/purl"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/sbom/core"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"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 {
|
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
|
||||||
found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool {
|
found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool {
|
||||||
return string(*item.CVE) == vuln.VulnerabilityID
|
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 {
|
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
|
||||||
stmt, ok := lo.Find(v.statements, func(item Statement) bool {
|
stmt, ok := lo.Find(v.statements, func(item Statement) bool {
|
||||||
return item.VulnerabilityID == vuln.VulnerabilityID
|
return item.VulnerabilityID == vuln.VulnerabilityID
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
openvex "github.com/openvex/go-vex/pkg/vex"
|
openvex "github.com/openvex/go-vex/pkg/vex"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/sbom/core"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"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 {
|
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
|
||||||
if vuln.PkgIdentifier.PURL == nil {
|
if vuln.PkgIdentifier.PURL == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
stmts := v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil)
|
stmts := v.Matches(vuln, bom)
|
||||||
if len(stmts) == 0 {
|
if len(stmts) == 0 {
|
||||||
return true
|
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 {
|
func findingStatus(status openvex.Status) types.FindingStatus {
|
||||||
switch status {
|
switch status {
|
||||||
case openvex.StatusNotAffected:
|
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"
|
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/sbom"
|
"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/sbom/cyclonedx"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"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.
|
// Note: This is in the experimental stage and does not yet support many specifications.
|
||||||
// The implementation may change significantly.
|
// The implementation may change significantly.
|
||||||
type VEX interface {
|
type VEX interface {
|
||||||
Filter(*types.Result)
|
Filter(*types.Result, *core.BOM)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(filePath string, report types.Report) (VEX, error) {
|
func New(filePath string, report types.Report) (VEX, error) {
|
||||||
|
|||||||
@@ -16,6 +16,48 @@ import (
|
|||||||
"github.com/aquasecurity/trivy/pkg/vex"
|
"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) {
|
func TestMain(m *testing.M) {
|
||||||
log.InitLogger(false, true)
|
log.InitLogger(false, true)
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
@@ -28,6 +70,7 @@ func TestVEX_Filter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
vulns []types.DetectedVulnerability
|
vulns []types.DetectedVulnerability
|
||||||
|
bom *core.BOM
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -42,21 +85,8 @@ func TestVEX_Filter(t *testing.T) {
|
|||||||
filePath: "testdata/openvex.json",
|
filePath: "testdata/openvex.json",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
vulns: []types.DetectedVulnerability{
|
vulns: []types.DetectedVulnerability{vuln1},
|
||||||
{
|
bom: newTestBOM(),
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{},
|
want: []types.DetectedVulnerability{},
|
||||||
},
|
},
|
||||||
@@ -67,50 +97,39 @@ func TestVEX_Filter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
vulns: []types.DetectedVulnerability{
|
vulns: []types.DetectedVulnerability{
|
||||||
{
|
vuln1, // filtered by VEX
|
||||||
VulnerabilityID: "CVE-2021-44228",
|
vuln2,
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
bom: newTestBOM(),
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
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",
|
name: "CycloneDX SBOM with CycloneDX VEX",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
@@ -347,8 +366,62 @@ func TestVEX_Filter(t *testing.T) {
|
|||||||
got := &types.Result{
|
got := &types.Result{
|
||||||
Vulnerabilities: tt.args.vulns,
|
Vulnerabilities: tt.args.vulns,
|
||||||
}
|
}
|
||||||
v.Filter(got)
|
v.Filter(got, tt.args.bom)
|
||||||
assert.Equal(t, tt.want, got.Vulnerabilities)
|
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