diff --git a/sca/bom/bomgenerator_test.go b/sca/bom/bomgenerator_test.go index 78d6fb95..76f908ac 100644 --- a/sca/bom/bomgenerator_test.go +++ b/sca/bom/bomgenerator_test.go @@ -84,7 +84,7 @@ func TestGetDiff(t *testing.T) { Sbom: &cyclonedx.BOM{ Components: &[]cyclonedx.Component{ {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "root", Version: "1.0"}, - {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", Version: "1.0"}, + {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", PackageURL: "pkg:component1", Version: "1.0"}, }, Dependencies: &[]cyclonedx.Dependency{ {Ref: "root", Dependencies: &[]string{"component1"}}, @@ -96,8 +96,8 @@ func TestGetDiff(t *testing.T) { sbom: &cyclonedx.BOM{ Components: &[]cyclonedx.Component{ {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "root", Version: "1.0"}, - {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", Version: "1.0"}, - {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", Version: "2.0"}, + {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component1", PackageURL: "pkg:component1", Version: "1.0"}, + {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", PackageURL: "pkg:component2", Version: "2.0"}, }, Dependencies: &[]cyclonedx.Dependency{ {Ref: "root", Dependencies: &[]string{"component1", "component2"}}, @@ -107,7 +107,7 @@ func TestGetDiff(t *testing.T) { expectedSbom: &cyclonedx.BOM{ Components: &[]cyclonedx.Component{ {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "root", Version: "1.0"}, - {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", Version: "2.0"}, + {Type: cyclonedx.ComponentTypeLibrary, BOMRef: "component2", PackageURL: "pkg:component2", Version: "2.0"}, }, Dependencies: &[]cyclonedx.Dependency{ {Ref: "root", Dependencies: &[]string{"component2"}}, diff --git a/utils/formats/cdxutils/cyclonedxutils.go b/utils/formats/cdxutils/cyclonedxutils.go index 9c5f90c5..3700410f 100644 --- a/utils/formats/cdxutils/cyclonedxutils.go +++ b/utils/formats/cdxutils/cyclonedxutils.go @@ -260,6 +260,18 @@ func SearchComponentByRef(components *[]cyclonedx.Component, ref string) (compon return } +func SearchComponentByCleanPurl(components *[]cyclonedx.Component, purl string) (component *cyclonedx.Component) { + if components == nil || len(*components) == 0 { + return + } + for i, comp := range *components { + if techutils.PurlToXrayComponentId(comp.PackageURL) == techutils.PurlToXrayComponentId(purl) { + return &(*components)[i] + } + } + return +} + func CreateFileOrDirComponent(filePathOrUri string) (component cyclonedx.Component) { component = cyclonedx.Component{ BOMRef: GetFileRef(filePathOrUri), @@ -309,12 +321,12 @@ func Exclude(bom cyclonedx.BOM, componentsToExclude ...cyclonedx.Component) (fil } filteredSbom = &bom for _, compToExclude := range componentsToExclude { - if matchedBomComp := SearchComponentByRef(bom.Components, compToExclude.BOMRef); matchedBomComp == nil || GetComponentRelation(&bom, matchedBomComp.BOMRef, false) == RootRelation { + if matchedBomComp := SearchComponentByCleanPurl(bom.Components, compToExclude.PackageURL); matchedBomComp == nil || GetComponentRelation(&bom, matchedBomComp.BOMRef, false) == RootRelation { // If not a match or Root component, skip it continue } // Exclude the component from the dependencies - filteredSbom.Dependencies = excludeFromDependencies(bom.Dependencies, compToExclude.BOMRef) + filteredSbom.Dependencies = excludeFromDependencies(bom.Dependencies, bom.Components, compToExclude) } toExclude := datastructures.MakeSet[string]() for _, comp := range *filteredSbom.Components { @@ -366,17 +378,17 @@ func excludeFromComponents(components *[]cyclonedx.Component, excludeComponents return &filteredComponents } -func excludeFromDependencies(dependencies *[]cyclonedx.Dependency, excludeComponents ...string) *[]cyclonedx.Dependency { +func excludeFromDependencies(dependencies *[]cyclonedx.Dependency, components *[]cyclonedx.Component, excludeComponents ...cyclonedx.Component) *[]cyclonedx.Dependency { if dependencies == nil || len(*dependencies) == 0 || len(excludeComponents) == 0 { return dependencies } - excludeRefs := datastructures.MakeSet[string]() - for _, compRef := range excludeComponents { - excludeRefs.Add(compRef) + excludePurls := datastructures.MakeSet[string]() + for _, component := range excludeComponents { + excludePurls.Add(techutils.PurlToXrayComponentId(component.PackageURL)) } filteredDependencies := []cyclonedx.Dependency{} for _, dep := range *dependencies { - if excludeRefs.Exists(dep.Ref) { + if excludePurls.Exists(GetTrimmedPurlByRef(dep.Ref, components)) { // This dependency is excluded, skip it continue } @@ -384,7 +396,7 @@ func excludeFromDependencies(dependencies *[]cyclonedx.Dependency, excludeCompon if dep.Dependencies != nil { // Also filter the components from the dependencies of this dependency for _, depRef := range *dep.Dependencies { - if !excludeRefs.Exists(depRef) { + if !excludePurls.Exists(GetTrimmedPurlByRef(depRef, components)) { if filteredDep.Dependencies == nil { filteredDep.Dependencies = &[]string{} } @@ -399,6 +411,15 @@ func excludeFromDependencies(dependencies *[]cyclonedx.Dependency, excludeCompon return &filteredDependencies } +func GetTrimmedPurlByRef(dep string, components *[]cyclonedx.Component) string { + component := SearchComponentByRef(components, dep) + if component == nil { + // couldn't find component + return "" + } + return techutils.PurlToXrayComponentId(component.PackageURL) +} + func AttachLicenseToComponent(component *cyclonedx.Component, license cyclonedx.LicenseChoice) { if component.Licenses == nil { component.Licenses = &cyclonedx.Licenses{} diff --git a/utils/formats/cdxutils/cyclonedxutils_test.go b/utils/formats/cdxutils/cyclonedxutils_test.go index 4f3f113d..44e8628f 100644 --- a/utils/formats/cdxutils/cyclonedxutils_test.go +++ b/utils/formats/cdxutils/cyclonedxutils_test.go @@ -1121,9 +1121,9 @@ func TestExclude(t *testing.T) { bom := cyclonedx.NewBOM() bom.Components = &[]cyclonedx.Component{ {BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp2", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp3", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp2", PackageURL: "pkg:comp2", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp3", PackageURL: "pkg:comp3", Type: cyclonedx.ComponentTypeLibrary}, } bom.Dependencies = &[]cyclonedx.Dependency{ {Ref: "root", Dependencies: &[]string{"comp1", "comp3"}}, @@ -1152,9 +1152,9 @@ func TestExclude(t *testing.T) { expected: &cyclonedx.BOM{ Components: &[]cyclonedx.Component{ {BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp2", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp3", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp2", PackageURL: "pkg:comp2", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp3", PackageURL: "pkg:comp3", Type: cyclonedx.ComponentTypeLibrary}, }, Dependencies: &[]cyclonedx.Dependency{ {Ref: "root", Dependencies: &[]string{"comp1", "comp3"}}, @@ -1164,25 +1164,25 @@ func TestExclude(t *testing.T) { }, { name: "Exclude single component with transitive dependencies", - exclude: []cyclonedx.Component{{BOMRef: "comp1"}}, + exclude: []cyclonedx.Component{{BOMRef: "comp1", PackageURL: "pkg:comp1"}}, bom: *bom, expected: &cyclonedx.BOM{ Components: &[]cyclonedx.Component{ {BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp3", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp3", PackageURL: "pkg:comp3", Type: cyclonedx.ComponentTypeLibrary}, }, Dependencies: &[]cyclonedx.Dependency{{Ref: "root", Dependencies: &[]string{"comp3"}}}, }, }, { name: "Exclude single component existing both directly and transitively", - exclude: []cyclonedx.Component{{BOMRef: "comp3"}}, + exclude: []cyclonedx.Component{{BOMRef: "comp3", PackageURL: "pkg:comp3"}}, bom: *bom, expected: &cyclonedx.BOM{ Components: &[]cyclonedx.Component{ {BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp2", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp2", PackageURL: "pkg:comp2", Type: cyclonedx.ComponentTypeLibrary}, }, Dependencies: &[]cyclonedx.Dependency{ {Ref: "root", Dependencies: &[]string{"comp1"}}, @@ -1192,18 +1192,44 @@ func TestExclude(t *testing.T) { }, { name: "Exclude multiple components", - exclude: []cyclonedx.Component{{BOMRef: "comp2"}, {BOMRef: "comp3"}, {BOMRef: "exclude-me"}}, + exclude: []cyclonedx.Component{{BOMRef: "comp2", PackageURL: "pkg:comp2"}, {BOMRef: "comp3", PackageURL: "pkg:comp3"}, {BOMRef: "exclude-me", PackageURL: "pkg:exclude-me"}}, bom: *bom, expected: &cyclonedx.BOM{ Components: &[]cyclonedx.Component{ {BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary}, - {BOMRef: "comp1", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp1", PackageURL: "pkg:comp1", Type: cyclonedx.ComponentTypeLibrary}, }, Dependencies: &[]cyclonedx.Dependency{ {Ref: "root", Dependencies: &[]string{"comp1"}}, }, }, }, + { + name: "Exclude by same name+version while ignoring hash", + bom: cyclonedx.BOM{ + Components: &[]cyclonedx.Component{ + {BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp1", PackageURL: "pkg:npm/comp1@1.0.0?hash=4321", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp2", PackageURL: "pkg:npm/comp2@1.0.0", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp3", PackageURL: "pkg:npm/comp3@1.0.0", Type: cyclonedx.ComponentTypeLibrary}, + }, + Dependencies: &[]cyclonedx.Dependency{ + {Ref: "root", Dependencies: &[]string{"comp1", "comp3"}}, + {Ref: "comp1", Dependencies: &[]string{"comp2", "comp3"}}, + }, + }, + // Exclude the same name+version, with a hash that should be ignored + exclude: []cyclonedx.Component{{BOMRef: "comp1", PackageURL: "pkg:npm/comp1@1.0.0?hash=1234"}}, + expected: &cyclonedx.BOM{ + Components: &[]cyclonedx.Component{ + {BOMRef: "root", Type: cyclonedx.ComponentTypeLibrary}, + {BOMRef: "comp3", PackageURL: "pkg:npm/comp3@1.0.0", Type: cyclonedx.ComponentTypeLibrary}, + }, + Dependencies: &[]cyclonedx.Dependency{ + {Ref: "root", Dependencies: &[]string{"comp3"}}, + }, + }, + }, } for _, tt := range tests {