Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vex): consider root component for relationships #6313

Merged
merged 3 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}