Skip to content
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
73 changes: 51 additions & 22 deletions sbom/cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package sbom
import (
"errors"
"io"
"strings"
"time"

cyclonedx "github.com/CycloneDX/cyclonedx-go"
Expand Down Expand Up @@ -168,30 +169,59 @@ func (ccx *CycloneDX) convertCycloneDxToSbom(bom *cyclonedx.BOM) (*Sbom, error)
return nil, errors.New("not a valid cyclone dx BOM")
}

rootComponent := bom.Metadata.Component
sbom := &Sbom{
Asset: &Asset{
Name: bom.Metadata.Component.Name + ":" + bom.Metadata.Component.Version,
Name: rootComponent.Name,
Platform: &Platform{
Version: rootComponent.Version,
Title: rootComponent.Description,
},
},
Packages: make([]*Package, 0),
}

if bom.Metadata.Tools != nil && bom.Metadata.Tools.Components != nil {
// last one wins :-) - we only support one tool
for _, component := range *bom.Metadata.Tools.Components {
sbom.Generator = &Generator{
Name: component.Name,
Version: component.Version,
Vendor: component.Author,
switch rootComponent.Type {
case cyclonedx.ComponentTypeOS:
hostnameId := "//platformid.api.mondoo.app/hostname/" + rootComponent.Name
sbom.Asset.PlatformIds = append(sbom.Asset.PlatformIds, hostnameId)
case cyclonedx.ComponentTypeContainer:
// we need to figure out where to get the container ID from properly. For now, we use the BOMRef
bomRefId := "//platformid.api.mondoo.app/runtime/docker/images/" + rootComponent.BOMRef
sbom.Asset.PlatformIds = append(sbom.Asset.PlatformIds, bomRefId)
}

if bom.Metadata.Tools != nil {
if bom.Metadata.Tools.Components != nil {
// last one wins :-) - we only support one tool
for _, component := range *bom.Metadata.Tools.Components {
sbom.Generator = &Generator{
Name: component.Name,
Version: component.Version,
Vendor: component.Author,
}
}
}

// if we have no generator info, fallback to trying tools. these are deprecated
// but might still be present
if sbom.Generator == nil && bom.Metadata.Tools.Tools != nil {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is purely fallback in case the tools are present and we couldnt get a generator

for _, tool := range *bom.Metadata.Tools.Tools {
sbom.Generator = &Generator{
Name: tool.Name,
Version: tool.Version,
Vendor: tool.Vendor,
}
}
}
}

for i := range *bom.Components {
component := (*bom.Components)[i]
for _, component := range *bom.Components {
pkg := &Package{
Name: component.Name,
Version: component.Version,
Purl: component.PackageURL,
Name: component.Name,
Version: component.Version,
Purl: component.PackageURL,
Description: component.Description,
}

// parse purl to gather package type
Expand All @@ -208,8 +238,7 @@ func (ccx *CycloneDX) convertCycloneDxToSbom(bom *cyclonedx.BOM) (*Sbom, error)

if component.Evidence != nil && component.Evidence.Occurrences != nil && ccx.opts.IncludeEvidence {
pkg.EvidenceList = make([]*Evidence, 0)
for i := range *component.Evidence.Occurrences {
e := (*component.Evidence.Occurrences)[i]
for _, e := range *component.Evidence.Occurrences {
pkg.EvidenceList = append(pkg.EvidenceList, &Evidence{
Type: EvidenceType_EVIDENCE_TYPE_FILE,
Value: e.Location,
Expand All @@ -219,18 +248,18 @@ func (ccx *CycloneDX) convertCycloneDxToSbom(bom *cyclonedx.BOM) (*Sbom, error)

switch component.Type {
case cyclonedx.ComponentTypeOS:
sbom.Asset.Platform = &Platform{
Name: component.Name,
Version: component.Version,
Title: component.Description,
}
sbom.Asset.Platform.Family = familyMap[component.Name]

sbom.Asset.Platform.Name = component.Name
sbom.Asset.Platform.Version = component.Version
sbom.Asset.Platform.Title = component.Description
sbom.Asset.Platform.Family = familyMap[strings.ToLower(component.Name)]
if len(component.CPE) > 0 {
sbom.Asset.Platform.Cpes = []string{component.CPE}
}
sbom.Packages = append(sbom.Packages, pkg)
case cyclonedx.ComponentTypeLibrary:
sbom.Packages = append(sbom.Packages, pkg)
case cyclonedx.ComponentTypeApplication:
sbom.Packages = append(sbom.Packages, pkg)
}
}

Expand Down
78 changes: 68 additions & 10 deletions sbom/cyclonedx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,74 @@ func TestCycloneDxOutput(t *testing.T) {
}

func TestCycloneDxJsonDecoding(t *testing.T) {
f, err := os.Open("./testdata/alpine-319.cyclone.json")
require.NoError(t, err)

formatHandler := &sbom.CycloneDX{
Format: cyclonedx.BOMFileFormatJSON,
}

bom, err := formatHandler.Parse(f)
require.NoError(t, err)
assert.NotNil(t, bom)
t.Run("alpine 3.19", func(t *testing.T) {
f, err := os.Open("./testdata/alpine-319.cyclone.json")
require.NoError(t, err)

formatHandler := &sbom.CycloneDX{
Format: cyclonedx.BOMFileFormatJSON,
}

bom, err := formatHandler.Parse(f)
require.NoError(t, err)
assert.NotNil(t, bom)
})

t.Run("ubuntu 20.04 container", func(t *testing.T) {
f, err := os.Open("./testdata/ubuntu-20.04-cyclonedx.json")
require.NoError(t, err)

formatHandler := &sbom.CycloneDX{
Format: cyclonedx.BOMFileFormatJSON,
}

bom, err := formatHandler.Parse(f)
require.NoError(t, err)
assert.NotNil(t, bom)

// verify we have the right asset and platform information.
assert.Equal(t, "ubuntu", bom.Asset.Platform.Name)
assert.Equal(t, "20.04", bom.Asset.Platform.Version)
assert.Equal(t, []string{"linux", "unix", "os"}, bom.Asset.Platform.Family)
assert.Equal(t, "Ubuntu 20.04.6 LTS", bom.Asset.Platform.Title)
// this is the bom-ref
assert.Equal(t, []string{"//platformid.api.mondoo.app/runtime/docker/images/e3cf4bf83104fade"}, bom.Asset.PlatformIds)
// 1 library components + 1 os component
assert.Len(t, bom.Packages, 2)

// verify the generator is correct
assert.Equal(t, "syft", bom.Generator.Name)
assert.Equal(t, "1.38.2", bom.Generator.Version)
assert.Equal(t, "anchore", bom.Generator.Vendor)
})

t.Run("ubuntu 22.04 container", func(t *testing.T) {
f, err := os.Open("./testdata/ubuntu-22.04-cyclonedx.json")
require.NoError(t, err)

formatHandler := &sbom.CycloneDX{
Format: cyclonedx.BOMFileFormatJSON,
}

bom, err := formatHandler.Parse(f)
require.NoError(t, err)
assert.NotNil(t, bom)

// verify we have the right asset and platform information.
assert.Equal(t, "ubuntu", bom.Asset.Platform.Name)
assert.Equal(t, "22.04", bom.Asset.Platform.Version)
assert.Equal(t, []string{"linux", "unix", "os"}, bom.Asset.Platform.Family)
assert.Equal(t, "Ubuntu 22.04.5 LTS", bom.Asset.Platform.Title)
// this is the bom-ref
assert.Equal(t, []string{"//platformid.api.mondoo.app/runtime/docker/images/2e194621f3c81dfe"}, bom.Asset.PlatformIds)
// 1 library components + 1 os component
assert.Len(t, bom.Packages, 2)

// verify the generator is correct
assert.Equal(t, "syft", bom.Generator.Name)
assert.Equal(t, "1.38.2", bom.Generator.Version)
assert.Equal(t, "anchore", bom.Generator.Vendor)
})
}

func TestCycloneDxXmlDecoding(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions sbom/formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func AllFormats() string {
return strings.Join(formats, ", ")
}

func New(fomat string) FormatSpecificationHandler {
switch fomat {
func New(format string) FormatSpecificationHandler {
switch format {
case FormatJson, "cnquery-json":
return &CnqueryBOM{}
case FormatCycloneDxJSON:
Expand Down
Loading
Loading