diff --git a/pkg/sbom/cyclonedx/core/cyclonedx.go b/pkg/sbom/cyclonedx/core/cyclonedx.go index 3f3b78c5fd8e..dc7e08128a8b 100644 --- a/pkg/sbom/cyclonedx/core/cyclonedx.go +++ b/pkg/sbom/cyclonedx/core/cyclonedx.go @@ -53,6 +53,7 @@ type CycloneDX struct { type Component struct { Type cdx.ComponentType Name string + Group string Version string PackageURL *purl.PackageURL Licenses []string @@ -120,6 +121,7 @@ func (c *CycloneDX) MarshalComponent(component *Component, components map[string BOMRef: bomRef, Type: component.Type, Name: component.Name, + Group: component.Group, Version: component.Version, PackageURL: c.PackageURL(component.PackageURL), Supplier: c.Supplier(component.Supplier), diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 2532177e1ba2..258607b77fd3 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -290,6 +290,14 @@ func pkgComponent(pkg Package) (*core.Component, error) { return nil, xerrors.Errorf("failed to new package purl: %w", err) } + name := pkg.Name + var group string + // use `group` field for GroupID and `name` for ArtifactID for jar files + if pkg.Type == ftypes.Jar { + name = pu.Name + group = pu.Namespace + } + properties := map[string]string{ PropertyPkgID: pkg.ID, PropertyPkgType: pkg.Type, @@ -305,7 +313,8 @@ func pkgComponent(pkg Package) (*core.Component, error) { return &core.Component{ Type: cdx.ComponentTypeLibrary, - Name: pkg.Name, + Name: name, + Group: group, Version: pu.Version, PackageURL: &pu, Supplier: pkg.Maintainer, diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index 32d986ccefd4..69668d4776cb 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -1045,6 +1045,18 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, }, + { + Target: "Java", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "org.springframework:spring-web", + Version: "5.3.22", + FilePath: "spring-web-5.3.22.jar", + }, + }, + }, }, }, want: &cdx.BOM{ @@ -1103,6 +1115,24 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, }, + { + BOMRef: "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + Type: "library", + Name: "spring-web", + Group: "org.springframework", + Version: "5.3.22", + PackageURL: "pkg:maven/org.springframework/spring-web@5.3.22", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:FilePath", + Value: "spring-web-5.3.22.jar", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "jar", + }, + }, + }, }, Vulnerabilities: &[]cdx.Vulnerability{}, Dependencies: &[]cdx.Dependency{ @@ -1110,6 +1140,7 @@ func TestMarshaler_Marshal(t *testing.T) { Ref: "3ff14136-e09f-4df9-80ea-000000000002", Dependencies: &[]string{ "3ff14136-e09f-4df9-80ea-000000000003", + "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", }, }, { @@ -1122,6 +1153,10 @@ func TestMarshaler_Marshal(t *testing.T) { Ref: "pkg:gem/actioncable@6.1.4.1", Dependencies: lo.ToPtr([]string{}), }, + { + Ref: "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + Dependencies: lo.ToPtr([]string{}), + }, }, }, }, diff --git a/pkg/sbom/cyclonedx/testdata/happy/bom.json b/pkg/sbom/cyclonedx/testdata/happy/bom.json index e712540d699b..0fd73a8eb652 100644 --- a/pkg/sbom/cyclonedx/testdata/happy/bom.json +++ b/pkg/sbom/cyclonedx/testdata/happy/bom.json @@ -86,7 +86,8 @@ { "bom-ref": "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", "type": "library", - "name": "org.codehaus.mojo:child-project", + "name": "child-project", + "group": "org.codehaus.mojo", "version": "1.0", "purl": "pkg:maven/org.codehaus.mojo/child-project@1.0", "properties": [ diff --git a/pkg/sbom/cyclonedx/testdata/happy/group-in-name.json b/pkg/sbom/cyclonedx/testdata/happy/group-in-name.json new file mode 100644 index 000000000000..6302acd63129 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/happy/group-in-name.json @@ -0,0 +1,59 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "serialNumber": "urn:uuid:8366a7c8-229c-4518-b86c-8a1bcf69af01", + "version": 1, + "metadata": { + "timestamp": "2023-06-20T04:32:10+00:00", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "0.42.1" + } + ], + "component": { + "bom-ref": "b0ae8323-eb7b-4be5-bc5c-4849fd795ec0", + "type": "application", + "name": "spring-web-5.3.22.jar", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + "type": "library", + "name": "org.springframework:spring-web", + "version": "5.3.22", + "purl": "pkg:maven/org.springframework/spring-web@5.3.22", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "spring-web-5.3.22.jar" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "jar" + } + ] + } + ], + "dependencies": [ + { + "ref": "b0ae8323-eb7b-4be5-bc5c-4849fd795ec0", + "dependsOn": [ + "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar" + ] + }, + { + "ref": "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index 057bbf53e51e..37df2b439fde 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -3,6 +3,7 @@ package cyclonedx import ( "bytes" "errors" + "fmt" "io" "sort" "strconv" @@ -10,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/package-url/packageurl-go" "github.com/samber/lo" "golang.org/x/exp/maps" "golang.org/x/xerrors" @@ -344,7 +346,7 @@ func toPackage(component cdx.Component) (bool, string, *ftypes.Package, error) { pkg := p.Package() // Trivy's marshall loses case-sensitivity in PURL used in SBOM for packages (Go, Npm, PyPI), // so we have to use an original package name - pkg.Name = component.Name + pkg.Name = getPackageName(p.Type, component) pkg.Ref = component.BOMRef for _, license := range lo.FromPtr(component.Licenses) { @@ -405,3 +407,11 @@ func toTrivyCdxComponent(component cdx.Component) ftypes.Component { PackageURL: component.PackageURL, } } + +func getPackageName(typ string, component cdx.Component) string { + // Jar uses `Group` field for `GroupID` + if typ == packageurl.TypeMaven && component.Group != "" { + return fmt.Sprintf("%s:%s", component.Group, component.Name) + } + return component.Name +} diff --git a/pkg/sbom/cyclonedx/unmarshal_test.go b/pkg/sbom/cyclonedx/unmarshal_test.go index fb07fc53327c..585b522c5e5c 100644 --- a/pkg/sbom/cyclonedx/unmarshal_test.go +++ b/pkg/sbom/cyclonedx/unmarshal_test.go @@ -282,6 +282,25 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, }, + { + name: "happy path for jar where name is GroupID and ArtifactID", + inputFile: "testdata/happy/group-in-name.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: "jar", + Libraries: []ftypes.Package{ + { + Name: "org.springframework:spring-web", + Version: "5.3.22", + Ref: "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + FilePath: "spring-web-5.3.22.jar", + }, + }, + }, + }, + }, + }, { name: "happy path only os component", inputFile: "testdata/happy/os-only-bom.json",