From 0e46de3fae677d3e6f025bab8789b700e6c8273e Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Tue, 30 Apr 2024 21:47:22 +0400 Subject: [PATCH 1/7] feat: add package hashes [skip ci] Signed-off-by: knqyf263 --- pkg/fanal/applier/applier_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index 8ba2b3f1eb95..ac8915f2dad6 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -1,10 +1,10 @@ package applier_test import ( - "github.com/package-url/packageurl-go" "sort" "testing" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" From 11cf96ab18b2f9051dfa2feedd63861dfdc5cfd3 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Wed, 1 May 2024 16:14:46 +0400 Subject: [PATCH 2/7] feat(openvex): improve relationship support [skip ci] Signed-off-by: knqyf263 --- pkg/result/filter.go | 2 +- pkg/result/filter_test.go | 48 ++++--- pkg/sbom/core/bom.go | 16 ++- pkg/sbom/cyclonedx/marshal.go | 2 +- pkg/sbom/io/encode.go | 1 + pkg/vex/openvex.go | 56 ++++---- pkg/vex/vex.go | 64 +++++++++ pkg/vex/vex_test.go | 240 ++++++++++++++++++++++++++++------ 8 files changed, 336 insertions(+), 93 deletions(-) diff --git a/pkg/result/filter.go b/pkg/result/filter.go index 6e92dbd3de98..17b2b6feb422 100644 --- a/pkg/result/filter.go +++ b/pkg/result/filter.go @@ -89,7 +89,7 @@ func filterByVEX(report types.Report, opt FilterOption) error { return nil } - bom, err := sbomio.NewEncoder(core.Options{}).Encode(report) + bom, err := sbomio.NewEncoder(core.Options{Parents: true}).Encode(report) if err != nil { return xerrors.Errorf("unable to encode the SBOM: %w", err) } diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go index 740caa6693d0..7dae1e45081b 100644 --- a/pkg/result/filter_test.go +++ b/pkg/result/filter_test.go @@ -18,10 +18,12 @@ import ( func TestFilter(t *testing.T) { var ( - vuln1 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2019-0001", - PkgName: "foo", - PkgIdentifier: ftypes.PkgIdentifier{ + pkg1 = ftypes.Package{ + ID: "foo@1.2.3", + Name: "foo", + Version: "1.2.3", + Identifier: ftypes.PkgIdentifier{ + Hash: "01", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/aquasecurity", @@ -29,25 +31,29 @@ func TestFilter(t *testing.T) { Version: "1.2.3", }, }, - InstalledVersion: "1.2.3", + } + vuln1 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0001", + PkgName: pkg1.Name, + InstalledVersion: pkg1.Version, FixedVersion: "1.2.4", + PkgIdentifier: ftypes.PkgIdentifier{ + Hash: pkg1.Identifier.Hash, + PURL: pkg1.Identifier.PURL, + }, Vulnerability: dbTypes.Vulnerability{ Severity: dbTypes.SeverityLow.String(), }, } vuln2 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2019-0002", - PkgName: "foo", + VulnerabilityID: "CVE-2019-0002", + PkgName: pkg1.Name, + InstalledVersion: pkg1.Version, + FixedVersion: "1.2.4", PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGolang, - Namespace: "github.com/aquasecurity", - Name: "foo", - Version: "4.5.6", - }, + Hash: pkg1.Identifier.Hash, + PURL: pkg1.Identifier.PURL, }, - InstalledVersion: "1.2.3", - FixedVersion: "1.2.4", Vulnerability: dbTypes.Vulnerability{ Severity: dbTypes.SeverityCritical.String(), }, @@ -243,8 +249,14 @@ func TestFilter(t *testing.T) { name: "filter by VEX", args: args{ report: types.Report{ + ArtifactName: ".", + ArtifactType: ftypes.ArtifactFilesystem, Results: types.Results{ types.Result{ + Target: "gobinary", + Class: types.ClassLangPkg, + Type: ftypes.GoBinary, + Packages: []ftypes.Package{pkg1}, Vulnerabilities: []types.DetectedVulnerability{ vuln1, vuln2, @@ -262,8 +274,14 @@ func TestFilter(t *testing.T) { vexPath: "testdata/openvex.json", }, want: types.Report{ + ArtifactName: ".", + ArtifactType: ftypes.ArtifactFilesystem, Results: types.Results{ types.Result{ + Target: "gobinary", + Class: types.ClassLangPkg, + Type: ftypes.GoBinary, + Packages: []ftypes.Package{pkg1}, Vulnerabilities: []types.DetectedVulnerability{ vuln2, }, diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index 68ba8dab76d5..1fb3078d0c6b 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -70,6 +70,10 @@ type BOM struct { // This is used to ensure that each package URL is only represented once in the BOM. purls map[string][]uuid.UUID + // parents is a map of parent components to their children + // This field is populated when Options.Parents is set to true. + parents map[uuid.UUID][]uuid.UUID + // opts is a set of options for the BOM. opts Options } @@ -199,7 +203,8 @@ type Vulnerability struct { } type Options struct { - GenerateBOMRef bool + GenerateBOMRef bool // Generate BOMRef for CycloneDX + Parents bool // Hold parent maps } func NewBOM(opts Options) *BOM { @@ -208,6 +213,7 @@ func NewBOM(opts Options) *BOM { relationships: make(map[uuid.UUID][]Relationship), vulnerabilities: make(map[uuid.UUID][]Vulnerability), purls: make(map[string][]uuid.UUID), + parents: make(map[uuid.UUID][]uuid.UUID), opts: opts, } } @@ -257,6 +263,10 @@ func (b *BOM) AddRelationship(parent, child *Component, relationshipType Relatio Type: relationshipType, Dependency: child.id, }) + + if b.opts.Parents { + b.parents[child.id] = append(b.parents[child.id], parent.id) + } } func (b *BOM) AddVulnerabilities(c *Component, vulns []Vulnerability) { @@ -298,8 +308,8 @@ func (b *BOM) Vulnerabilities() map[uuid.UUID][]Vulnerability { return b.vulnerabilities } -func (b *BOM) NumComponents() int { - return len(b.components) + 1 // +1 for the root component +func (b *BOM) Parents() map[uuid.UUID][]uuid.UUID { + return b.parents } // bomRef returns BOMRef for CycloneDX diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 5b7241254d25..7f4bb0c3b397 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -60,7 +60,7 @@ func (m *Marshaler) MarshalReport(ctx context.Context, report types.Report) (*cd // Marshal converts the Trivy component to the CycloneDX format func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*cdx.BOM, error) { m.bom = bom - m.componentIDs = make(map[uuid.UUID]string, m.bom.NumComponents()) + m.componentIDs = make(map[uuid.UUID]string, len(m.bom.Components())) cdxBOM := cdx.NewBOM() cdxBOM.SerialNumber = uuid.New().URN() diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 4b715b023a08..db9052f033dd 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -348,6 +348,7 @@ func (*Encoder) component(result types.Result, pkg ftypes.Package) *core.Compone SrcVersion: utils.FormatSrcVersion(pkg), SrcFile: srcFile, PkgIdentifier: ftypes.PkgIdentifier{ + UID: pkg.Identifier.UID, PURL: pkg.Identifier.PURL, }, Supplier: pkg.Maintainer, diff --git a/pkg/vex/openvex.go b/pkg/vex/openvex.go index ce049777e8cc..59086a7ace4f 100644 --- a/pkg/vex/openvex.go +++ b/pkg/vex/openvex.go @@ -1,11 +1,9 @@ package vex 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" + openvex "github.com/openvex/go-vex/pkg/vex" ) type OpenVEX struct { @@ -19,38 +17,36 @@ func newOpenVEX(vex openvex.VEX) VEX { } 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 - } + filterVulnerabilities(result, bom, v.NotAffected) +} - stmts := v.Matches(vuln, bom) - if len(stmts) == 0 { - return true - } +func (v *OpenVEX) NotAffected(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) { + stmts := v.Matches(vuln, product, subComponent) + if len(stmts) == 0 { + return types.ModifiedFinding{}, false + } - // Take the latest statement for a given vulnerability and product - // as a sequence of statements can be overridden by the newer one. - // cf. https://github.com/openvex/spec/blob/fa5ba0c0afedb008dc5ebad418548cacf16a3ca7/OPENVEX-SPEC.md#the-vex-statement - stmt := stmts[len(stmts)-1] - if stmt.Status == openvex.StatusNotAffected || stmt.Status == openvex.StatusFixed { - result.ModifiedFindings = append(result.ModifiedFindings, - types.NewModifiedFinding(vuln, findingStatus(stmt.Status), string(stmt.Justification), "OpenVEX")) - return false - } - return true - }) + // Take the latest statement for a given vulnerability and product + // as a sequence of statements can be overridden by the newer one. + // cf. https://github.com/openvex/spec/blob/fa5ba0c0afedb008dc5ebad418548cacf16a3ca7/OPENVEX-SPEC.md#the-vex-statement + stmt := stmts[len(stmts)-1] + if stmt.Status == openvex.StatusNotAffected || stmt.Status == openvex.StatusFixed { + modifiedFindings := types.NewModifiedFinding(vuln, findingStatus(stmt.Status), string(stmt.Justification), "OpenVEX") + return modifiedFindings, true + } + return types.ModifiedFinding{}, false } -func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, bom *core.BOM) []openvex.Statement { - root := bom.Root() - if root != nil && root.PkgIdentifier.PURL != nil { - stmts := v.vex.Matches(vuln.VulnerabilityID, root.PkgIdentifier.PURL.String(), []string{vuln.PkgIdentifier.PURL.String()}) - if len(stmts) != 0 { - return stmts - } +func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, product, subComponent *core.Component) []openvex.Statement { + if product == nil || product.PkgIdentifier.PURL == nil { + return nil + } + + var subComponentPURL string + if subComponent != nil && subComponent.PkgIdentifier.PURL != nil { + subComponentPURL = subComponent.PkgIdentifier.PURL.String() } - return v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil) + return v.vex.Matches(vuln.VulnerabilityID, product.PkgIdentifier.PURL.String(), []string{subComponentPURL}) } func findingStatus(status openvex.Status) types.FindingStatus { diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 64aa651d640c..5f8d2bccdd7e 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -6,8 +6,10 @@ import ( "os" csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" + "github.com/google/uuid" "github.com/hashicorp/go-multierror" openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/samber/lo" "github.com/sirupsen/logrus" "golang.org/x/xerrors" @@ -104,3 +106,65 @@ func decodeCSAF(r io.ReadSeeker) (VEX, error) { } return newCSAF(adv), nil } + +type NotAffected func(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) + +func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) { + components := lo.MapEntries(bom.Components(), func(id uuid.UUID, component *core.Component) (string, *core.Component) { + return component.PkgID.Hash, component + }) + + result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { + if vuln.PkgIdentifier.PURL == nil { + return true + } + + c, ok := components[vuln.PkgIdentifier.Hash] + if !ok { + return true // Should never reach here + } + + notAffectedFn := func(c, leaf *core.Component) bool { + modified, notAffected := fn(vuln, c, leaf) + if notAffected { + result.ModifiedFindings = append(result.ModifiedFindings, modified) + return true + } + return false + } + + return reachRoot(c, bom.Components(), bom.Parents(), notAffectedFn) + }) +} + +func reachRoot(leaf *core.Component, components map[uuid.UUID]*core.Component, parents map[uuid.UUID][]uuid.UUID, + notAffected func(c, leaf *core.Component) bool) bool { + + if notAffected(leaf, nil) { + return false + } + + visited := make(map[uuid.UUID]bool) + + var dfs func(c *core.Component) bool + dfs = func(c *core.Component) bool { + if notAffected(c, leaf) { + return false + } else if c.Root { + return true + } + + visited[c.ID()] = true + for _, parent := range parents[c.ID()] { + if visited[parent] { + continue + } + if dfs(components[parent]) { + return true + } + } + return false + } + + return dfs(leaf) +} diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index ad385b63596e..e7699aff2fb9 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -18,24 +18,40 @@ import ( ) var ( - vuln1 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2021-44228", - PkgName: "spring-boot", - InstalledVersion: "2.6.0", + ociComponent = core.Component{ + Root: true, + Type: core.TypeContainerImage, + Name: "debian:12", PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.springframework.boot", - Name: "spring-boot", - Version: "2.6.0", + Type: packageurl.TypeOCI, + Name: "debian", + Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + Qualifiers: packageurl.Qualifiers{ + { + Key: "tag", + Value: "12", + }, + { + Key: "repository_url", + Value: "docker.io/library/debian", + }, + }, }, }, } - vuln2 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2021-0001", - PkgName: "spring-boot", - InstalledVersion: "2.6.0", + fsComponent = core.Component{ + Root: true, + Type: core.TypeFilesystem, + Name: ".", + } + springComponent = core.Component{ + Type: core.TypeLibrary, + Group: "org.springframework.boot", + Name: "spring-boot", + Version: "2.6.0", PkgIdentifier: ftypes.PkgIdentifier{ + UID: "01", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.springframework.boot", @@ -44,11 +60,12 @@ var ( }, }, } - vuln3 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2022-3715", - PkgName: "bash", - InstalledVersion: "5.2.15", + bashComponent = core.Component{ + Type: core.TypeLibrary, + Name: "bash", + Version: "5.3", PkgIdentifier: ftypes.PkgIdentifier{ + UID: "02", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Namespace: "debian", @@ -57,6 +74,98 @@ var ( }, }, } + goModuleComponent = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-module", + Version: "1.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "03", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-module", + Version: "1.0.0", + }, + }, + } + goDirectComponent1 = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-direct1", + Version: "2.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "04", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-direct1", + Version: "2.0.0", + }, + }, + } + goDirectComponent2 = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-direct2", + Version: "3.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "05", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-direct2", + Version: "3.0.0", + }, + }, + } + goTransitiveComponent = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-transitive", + Version: "4.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "06", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-transitive", + Version: "4.0.0", + }, + }, + } + vuln1 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2021-44228", + PkgName: springComponent.Name, + InstalledVersion: springComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: springComponent.PkgIdentifier.UID, + PURL: springComponent.PkgIdentifier.PURL, + }, + } + vuln2 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2021-0001", + PkgName: springComponent.Name, + InstalledVersion: springComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: springComponent.PkgIdentifier.UID, + PURL: springComponent.PkgIdentifier.PURL, + }, + } + vuln3 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2022-3715", + PkgName: bashComponent.Name, + InstalledVersion: bashComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: bashComponent.PkgIdentifier.UID, + PURL: bashComponent.PkgIdentifier.PURL, + }, + } + vuln4 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2024-0001", + PkgName: goTransitiveComponent.Name, + InstalledVersion: goTransitiveComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: goTransitiveComponent.PkgIdentifier.UID, + PURL: goTransitiveComponent.PkgIdentifier.PURL, + }, + } ) func TestMain(m *testing.M) { @@ -87,7 +196,7 @@ func TestVEX_Filter(t *testing.T) { }, args: args{ vulns: []types.DetectedVulnerability{vuln1}, - bom: newTestBOM(), + bom: newTestBOM1(), }, want: []types.DetectedVulnerability{}, }, @@ -101,7 +210,7 @@ func TestVEX_Filter(t *testing.T) { vuln1, // filtered by VEX vuln2, }, - bom: newTestBOM(), + bom: newTestBOM1(), }, want: []types.DetectedVulnerability{ vuln2, @@ -116,7 +225,7 @@ func TestVEX_Filter(t *testing.T) { vulns: []types.DetectedVulnerability{ vuln3, }, - bom: newTestBOM(), + bom: newTestBOM1(), }, want: []types.DetectedVulnerability{}, }, @@ -131,6 +240,28 @@ func TestVEX_Filter(t *testing.T) { }, want: []types.DetectedVulnerability{vuln3}, }, + { + name: "OpenVEX, single path between product and subcomponent", + fields: fields{ + filePath: "testdata/openvex-nested.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{vuln4}, + bom: newTestBOM3(), + }, + want: []types.DetectedVulnerability{}, + }, + { + name: "OpenVEX, multi paths between product and subcomponent", + fields: fields{ + filePath: "testdata/openvex-nested.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{vuln4}, + bom: newTestBOM4(), + }, + want: []types.DetectedVulnerability{vuln4}, // Will not be filtered because of multi paths + }, { name: "CycloneDX SBOM with CycloneDX VEX", fields: fields{ @@ -373,30 +504,16 @@ func TestVEX_Filter(t *testing.T) { } } -func newTestBOM() *core.BOM { - bom := core.NewBOM(core.Options{}) - bom.AddComponent(&core.Component{ - Root: true, - Type: core.TypeContainerImage, - Name: "debian:12", - PkgIdentifier: ftypes.PkgIdentifier{ - 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", - }, - }, - }, - }, - }) +func newTestBOM1() *core.BOM { + // - oci:debian?tag=12 + // - pkg:maven/org.springframework.boot/spring-boot@2.6.0 + // - pkg:deb/debian/bash@5.3 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&ociComponent) + bom.AddComponent(&springComponent) + bom.AddComponent(&bashComponent) + bom.AddRelationship(&ociComponent, &springComponent, core.RelationshipContains) + bom.AddRelationship(&ociComponent, &bashComponent, core.RelationshipContains) return bom } @@ -426,3 +543,40 @@ func newTestBOM2() *core.BOM { }) return bom } + +func newTestBOM3() *core.BOM { + // - filesystem + // - pkg:golang/github.com/aquasecurity/go-module@1.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct1@2.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@4.0.0 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&fsComponent) + bom.AddComponent(&goModuleComponent) + bom.AddComponent(&goDirectComponent1) + bom.AddComponent(&goTransitiveComponent) + bom.AddRelationship(&fsComponent, &goModuleComponent, core.RelationshipContains) + bom.AddRelationship(&goModuleComponent, &goDirectComponent1, core.RelationshipDependsOn) + bom.AddRelationship(&goDirectComponent1, &goTransitiveComponent, core.RelationshipDependsOn) + return bom +} + +func newTestBOM4() *core.BOM { + // - filesystem + // - pkg:golang/github.com/aquasecurity/go-module@2.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct1@3.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct2@4.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&fsComponent) + bom.AddComponent(&goModuleComponent) + bom.AddComponent(&goDirectComponent1) + bom.AddComponent(&goDirectComponent2) + bom.AddComponent(&goTransitiveComponent) + bom.AddRelationship(&fsComponent, &goModuleComponent, core.RelationshipContains) + bom.AddRelationship(&goModuleComponent, &goDirectComponent1, core.RelationshipDependsOn) + bom.AddRelationship(&goModuleComponent, &goDirectComponent2, core.RelationshipDependsOn) + bom.AddRelationship(&goDirectComponent1, &goTransitiveComponent, core.RelationshipDependsOn) + bom.AddRelationship(&goDirectComponent2, &goTransitiveComponent, core.RelationshipDependsOn) + return bom +} From 550bc681d3e435c87c2a082053ed1faf907910a7 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Mon, 20 May 2024 12:12:38 +0400 Subject: [PATCH 3/7] fix: rebase Signed-off-by: knqyf263 --- pkg/result/filter_test.go | 18 +++++++++++------- pkg/vex/csaf.go | 2 +- pkg/vex/openvex.go | 3 ++- pkg/vex/testdata/openvex-nested.json | 26 ++++++++++++++++++++++++++ pkg/vex/vex.go | 4 ++-- 5 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 pkg/vex/testdata/openvex-nested.json diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go index 7dae1e45081b..71ac8644cd36 100644 --- a/pkg/result/filter_test.go +++ b/pkg/result/filter_test.go @@ -2,6 +2,7 @@ package result_test import ( "context" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/package-url/packageurl-go" "testing" "time" @@ -23,7 +24,7 @@ func TestFilter(t *testing.T) { Name: "foo", Version: "1.2.3", Identifier: ftypes.PkgIdentifier{ - Hash: "01", + UID: "01", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/aquasecurity", @@ -38,7 +39,7 @@ func TestFilter(t *testing.T) { InstalledVersion: pkg1.Version, FixedVersion: "1.2.4", PkgIdentifier: ftypes.PkgIdentifier{ - Hash: pkg1.Identifier.Hash, + UID: pkg1.Identifier.UID, PURL: pkg1.Identifier.PURL, }, Vulnerability: dbTypes.Vulnerability{ @@ -51,7 +52,7 @@ func TestFilter(t *testing.T) { InstalledVersion: pkg1.Version, FixedVersion: "1.2.4", PkgIdentifier: ftypes.PkgIdentifier{ - Hash: pkg1.Identifier.Hash, + UID: pkg1.Identifier.UID, PURL: pkg1.Identifier.PURL, }, Vulnerability: dbTypes.Vulnerability{ @@ -250,7 +251,7 @@ func TestFilter(t *testing.T) { args: args{ report: types.Report{ ArtifactName: ".", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ types.Result{ Target: "gobinary", @@ -275,7 +276,7 @@ func TestFilter(t *testing.T) { }, want: types.Report{ ArtifactName: ".", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ types.Result{ Target: "gobinary", @@ -676,7 +677,10 @@ func TestFilter(t *testing.T) { }, }, }, - severities: []dbTypes.Severity{dbTypes.SeverityLow, dbTypes.SeverityHigh}, + severities: []dbTypes.Severity{ + dbTypes.SeverityLow, + dbTypes.SeverityHigh, + }, policyFile: "./testdata/test-ignore-policy-licenses-and-secrets.rego", }, want: types.Report{ @@ -689,7 +693,7 @@ func TestFilter(t *testing.T) { secret1, }, ModifiedFindings: []types.ModifiedFinding{ - { + { Type: types.FindingTypeSecret, Status: types.FindingStatusIgnored, Statement: "Filtered by Rego", diff --git a/pkg/vex/csaf.go b/pkg/vex/csaf.go index 3e43503b042b..8f6ecc9a84cb 100644 --- a/pkg/vex/csaf.go +++ b/pkg/vex/csaf.go @@ -1,7 +1,7 @@ package vex import ( - csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" + "github.com/csaf-poc/csaf_distribution/v3/csaf" "github.com/package-url/packageurl-go" "github.com/samber/lo" diff --git a/pkg/vex/openvex.go b/pkg/vex/openvex.go index 59086a7ace4f..36ab7808d559 100644 --- a/pkg/vex/openvex.go +++ b/pkg/vex/openvex.go @@ -1,9 +1,10 @@ package vex import ( + openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/types" - openvex "github.com/openvex/go-vex/pkg/vex" ) type OpenVEX struct { diff --git a/pkg/vex/testdata/openvex-nested.json b/pkg/vex/testdata/openvex-nested.json new file mode 100644 index 000000000000..da7dd68615a5 --- /dev/null +++ b/pkg/vex/testdata/openvex-nested.json @@ -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-2024-0001" + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/go-direct1@2.0.0", + "subcomponents": [ + { + "@id": "pkg:golang/github.com/aquasecurity/go-transitive@4.0.0" + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 5f8d2bccdd7e..cc073bbe3002 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -111,7 +111,7 @@ type NotAffected func(vuln types.DetectedVulnerability, product, subComponent *c func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) { components := lo.MapEntries(bom.Components(), func(id uuid.UUID, component *core.Component) (string, *core.Component) { - return component.PkgID.Hash, component + return component.PkgIdentifier.UID, component }) result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { @@ -119,7 +119,7 @@ func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) return true } - c, ok := components[vuln.PkgIdentifier.Hash] + c, ok := components[vuln.PkgIdentifier.UID] if !ok { return true // Should never reach here } From fc8aed5021d440f0138973a05669dfc46469352d Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Mon, 20 May 2024 14:03:29 +0400 Subject: [PATCH 4/7] test: update golden files Signed-off-by: knqyf263 --- integration/testdata/conda-spdx.json.golden | 12 +++++------ integration/testdata/julia-spdx.json.golden | 24 ++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/integration/testdata/conda-spdx.json.golden b/integration/testdata/conda-spdx.json.golden index f8a8778538c0..c14d1bf4e018 100644 --- a/integration/testdata/conda-spdx.json.golden +++ b/integration/testdata/conda-spdx.json.golden @@ -14,7 +14,7 @@ "packages": [ { "name": "openssl", - "SPDXID": "SPDXRef-Package-32b6b37a6fa2e57f", + "SPDXID": "SPDXRef-Package-22a178da112ac20a", "versionInfo": "1.1.1q", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -38,7 +38,7 @@ }, { "name": "pip", - "SPDXID": "SPDXRef-Package-e260029d0b6fd07b", + "SPDXID": "SPDXRef-Package-c22b9ee9a601ba6", "versionInfo": "22.2.2", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -103,21 +103,21 @@ }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-32b6b37a6fa2e57f", + "relatedSpdxElement": "SPDXRef-Package-22a178da112ac20a", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-e260029d0b6fd07b", + "relatedSpdxElement": "SPDXRef-Package-c22b9ee9a601ba6", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-32b6b37a6fa2e57f", + "spdxElementId": "SPDXRef-Package-22a178da112ac20a", "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-e260029d0b6fd07b", + "spdxElementId": "SPDXRef-Package-c22b9ee9a601ba6", "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", "relationshipType": "CONTAINS" } diff --git a/integration/testdata/julia-spdx.json.golden b/integration/testdata/julia-spdx.json.golden index 483991784365..8ae4ead23a7d 100644 --- a/integration/testdata/julia-spdx.json.golden +++ b/integration/testdata/julia-spdx.json.golden @@ -25,7 +25,7 @@ }, { "name": "A", - "SPDXID": "SPDXRef-Package-2a46714189f3b9de", + "SPDXID": "SPDXRef-Package-7784b00da0cb0cb0", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -48,7 +48,7 @@ }, { "name": "B", - "SPDXID": "SPDXRef-Package-4a8e351c4c9b7318", + "SPDXID": "SPDXRef-Package-960543ac5c5f7e10", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -60,18 +60,18 @@ { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "pkg:julia/B@1.9.0?uuid=edca9bc6-334e-11e9-3554-9595dbb4349c" + "referenceLocator": "pkg:julia/B@1.9.0?uuid=f41f7b98-334e-11e9-1257-49272045fb24" } ], "attributionTexts": [ - "PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c", + "PkgID: f41f7b98-334e-11e9-1257-49272045fb24", "PkgType: julia" ], "primaryPackagePurpose": "LIBRARY" }, { "name": "B", - "SPDXID": "SPDXRef-Package-d10d5e4a30a43fff", + "SPDXID": "SPDXRef-Package-a4705eb108e4f15c", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -83,11 +83,11 @@ { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "pkg:julia/B@1.9.0?uuid=f41f7b98-334e-11e9-1257-49272045fb24" + "referenceLocator": "pkg:julia/B@1.9.0?uuid=edca9bc6-334e-11e9-3554-9595dbb4349c" } ], "attributionTexts": [ - "PkgID: f41f7b98-334e-11e9-1257-49272045fb24", + "PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c", "PkgType: julia" ], "primaryPackagePurpose": "LIBRARY" @@ -106,17 +106,17 @@ "relationships": [ { "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-2a46714189f3b9de", + "relatedSpdxElement": "SPDXRef-Package-7784b00da0cb0cb0", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-4a8e351c4c9b7318", + "relatedSpdxElement": "SPDXRef-Package-960543ac5c5f7e10", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff", + "relatedSpdxElement": "SPDXRef-Package-a4705eb108e4f15c", "relationshipType": "CONTAINS" }, { @@ -130,8 +130,8 @@ "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-2a46714189f3b9de", - "relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff", + "spdxElementId": "SPDXRef-Package-7784b00da0cb0cb0", + "relatedSpdxElement": "SPDXRef-Package-960543ac5c5f7e10", "relationshipType": "DEPENDS_ON" } ] From f60045bdc5b950d4f27c3385dca2cbc8d0e9247e Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Mon, 20 May 2024 14:11:51 +0400 Subject: [PATCH 5/7] chore: add comments Signed-off-by: knqyf263 --- pkg/vex/vex.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index cc073bbe3002..d9182009a989 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -137,6 +137,7 @@ func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) }) } +// reachRoot traverses the component tree from the leaf to the root and returns true if the leaf reaches the root. func reachRoot(leaf *core.Component, components map[uuid.UUID]*core.Component, parents map[uuid.UUID][]uuid.UUID, notAffected func(c, leaf *core.Component) bool) bool { @@ -146,8 +147,10 @@ func reachRoot(leaf *core.Component, components map[uuid.UUID]*core.Component, p visited := make(map[uuid.UUID]bool) + // Use Depth First Search (DFS) var dfs func(c *core.Component) bool dfs = func(c *core.Component) bool { + // Call the function with the current component and the leaf component if notAffected(c, leaf) { return false } else if c.Root { From b45c21eaf5a11d86c5bdfec1d297800d7f55fbe4 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 21 May 2024 12:55:40 +0400 Subject: [PATCH 6/7] Update pkg/vex/vex.go Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> --- pkg/vex/vex.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index d9182009a989..1799dcc71608 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -6,7 +6,7 @@ import ( "os" csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" - "github.com/google/uuid" + "github.com/aquasecurity/trivy/pkg/uuid" "github.com/hashicorp/go-multierror" openvex "github.com/openvex/go-vex/pkg/vex" "github.com/samber/lo" From 34483c233d1bc8df08802b2749220f997209aefe Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Tue, 21 May 2024 13:00:50 +0400 Subject: [PATCH 7/7] chore: add an error message Signed-off-by: knqyf263 --- pkg/vex/vex.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 1799dcc71608..c2b5ad10ea43 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -5,8 +5,7 @@ import ( "io" "os" - csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" - "github.com/aquasecurity/trivy/pkg/uuid" + "github.com/csaf-poc/csaf_distribution/v3/csaf" "github.com/hashicorp/go-multierror" openvex "github.com/openvex/go-vex/pkg/vex" "github.com/samber/lo" @@ -14,10 +13,12 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/log" "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" + "github.com/aquasecurity/trivy/pkg/uuid" ) // VEX represents Vulnerability Exploitability eXchange. It abstracts multiple VEX formats. @@ -121,6 +122,7 @@ func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) c, ok := components[vuln.PkgIdentifier.UID] if !ok { + log.Error("Component not found", log.String("uid", vuln.PkgIdentifier.UID)) return true // Should never reach here }