From 71f2c5292357557ee51d8a30b44ea4eb743eb3e3 Mon Sep 17 00:00:00 2001 From: mrizzi Date: Mon, 30 Sep 2024 18:51:01 +0200 Subject: [PATCH] CycloneDX SBOM: support nested components Signed-off-by: mrizzi --- .../parser/cyclonedx/parser_cyclonedx.go | 13 +++- .../parser/cyclonedx/parser_cyclonedx_test.go | 63 ++++++++++++++----- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/pkg/ingestor/parser/cyclonedx/parser_cyclonedx.go b/pkg/ingestor/parser/cyclonedx/parser_cyclonedx.go index 1218437c1d..ff38d1eca6 100644 --- a/pkg/ingestor/parser/cyclonedx/parser_cyclonedx.go +++ b/pkg/ingestor/parser/cyclonedx/parser_cyclonedx.go @@ -210,8 +210,12 @@ func parseContainerType(name string, version string, topLevel bool) string { } func (c *cyclonedxParser) getPackages() error { - if c.cdxBom.Components != nil { - for _, comp := range *c.cdxBom.Components { + return traverseComponents(*c, c.cdxBom.Components) +} + +func traverseComponents(c cyclonedxParser, components *[]cdx.Component) error { + if components != nil { + for _, comp := range *components { // skipping over the "operating-system" type as it does not contain // the required purl for package node. Currently there is no use-case // to capture OS for GUAC. @@ -250,6 +254,11 @@ func (c *cyclonedxParser) getPackages() error { if err := c.getLicenseInformation(comp); err != nil { return fmt.Errorf("failed to get license information for component package with error: %w", err) } + + err = traverseComponents(c, comp.Components) + if err != nil { + return err + } } } return nil diff --git a/pkg/ingestor/parser/cyclonedx/parser_cyclonedx_test.go b/pkg/ingestor/parser/cyclonedx/parser_cyclonedx_test.go index ff15ceb908..17f964dc4d 100644 --- a/pkg/ingestor/parser/cyclonedx/parser_cyclonedx_test.go +++ b/pkg/ingestor/parser/cyclonedx/parser_cyclonedx_test.go @@ -369,7 +369,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { tests := []struct { name string cdxBom *cdx.BOM - wantPurl string + wantPurl []string }{{ name: "purl provided", cdxBom: &cdx.BOM{ @@ -380,7 +380,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { PackageURL: "pkg:oci/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?repository_url=gcr.io/distroless/static&tag=nonroot", }}, }, - wantPurl: "pkg:oci/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?repository_url=gcr.io/distroless/static&tag=nonroot", + wantPurl: []string{"pkg:oci/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?repository_url=gcr.io/distroless/static&tag=nonroot"}, }, { name: "gcr.io/distroless/static:nonroot - purl not provided", cdxBom: &cdx.BOM{ @@ -390,7 +390,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388", }}, }, - wantPurl: "pkg:guac/pkg/gcr.io/distroless/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?tag=nonroot", + wantPurl: []string{"pkg:guac/pkg/gcr.io/distroless/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?tag=nonroot"}, }, { name: "gcr.io/distroless/static - purl not provided, tag not specified", @@ -401,7 +401,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388", }}, }, - wantPurl: "pkg:guac/pkg/gcr.io/distroless/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?tag=", + wantPurl: []string{"pkg:guac/pkg/gcr.io/distroless/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?tag="}, }, { name: "gcr.io/distroless/static - purl not provided, tag not specified, version not specified", @@ -411,7 +411,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Type: cdx.ComponentTypeContainer, }}, }, - wantPurl: "pkg:guac/pkg/gcr.io/distroless/static@?tag=", + wantPurl: []string{"pkg:guac/pkg/gcr.io/distroless/static@?tag="}, }, { name: "library/debian:latest - purl not provided, assume docker.io", @@ -422,7 +422,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", }}, }, - wantPurl: "pkg:guac/pkg/library/debian@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag=latest", + wantPurl: []string{"pkg:guac/pkg/library/debian@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag=latest"}, }, { name: "library/debian - purl not provided, tag not specified", cdxBom: &cdx.BOM{ @@ -432,7 +432,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", }}, }, - wantPurl: "pkg:guac/pkg/library/debian@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag=", + wantPurl: []string{"pkg:guac/pkg/library/debian@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag="}, }, { name: "library - purl not provided, tag not specified", cdxBom: &cdx.BOM{ @@ -442,7 +442,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", }}, }, - wantPurl: "pkg:guac/pkg/library@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag=", + wantPurl: []string{"pkg:guac/pkg/library@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag="}, }, { name: "name split length too long, tag not specified", cdxBom: &cdx.BOM{ @@ -452,7 +452,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", }}, }, - wantPurl: "pkg:guac/pkg/ghcr.io/guacsec/guac/guacsec@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", + wantPurl: []string{"pkg:guac/pkg/ghcr.io/guacsec/guac/guacsec@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870"}, }, { name: "name contains local registry, tag specified", cdxBom: &cdx.BOM{ @@ -462,7 +462,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", }}, }, - wantPurl: "pkg:guac/pkg/foo.registry.com:4443/myapp/debian@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag=latest", + wantPurl: []string{"pkg:guac/pkg/foo.registry.com:4443/myapp/debian@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?tag=latest"}, }, { name: "ComponentTypeLibrary", @@ -473,7 +473,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", }}, }, - wantPurl: "pkg:guac/pkg/ghcr.io/guacsec/guac/guacsec@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", + wantPurl: []string{"pkg:guac/pkg/ghcr.io/guacsec/guac/guacsec@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870"}, }, { name: "file type - purl nor provided, version provided", cdxBom: &cdx.BOM{ @@ -483,7 +483,7 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", }}, }, - wantPurl: "pkg:guac/files/sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?filename=/home/work/test/build/webserver", + wantPurl: []string{"pkg:guac/files/sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870?filename=/home/work/test/build/webserver"}, }, { name: "file type - purl nor provided, version not provided", cdxBom: &cdx.BOM{ @@ -492,7 +492,31 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { Type: cdx.ComponentTypeFile, }}, }, - wantPurl: "pkg:guac/files/home/work/test/build/webserver", + wantPurl: []string{"pkg:guac/files/home/work/test/build/webserver"}, + }, { + name: "nested components", + cdxBom: &cdx.BOM{ + Components: &[]cdx.Component{{ + Name: "/home/work/test/build/webserver", + Type: cdx.ComponentTypeFile, + Components: &[]cdx.Component{{ + Name: "gcr.io/distroless/static:nonroot", + Type: cdx.ComponentTypeContainer, + Version: "sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388", + PackageURL: "pkg:oci/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?repository_url=gcr.io/distroless/static&tag=nonroot", + Components: &[]cdx.Component{{ + Name: "ghcr.io/guacsec/guac/guacsec", + Type: cdx.ComponentTypeLibrary, + Version: "sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", + }}, + }}, + }}, + }, + wantPurl: []string{ + "pkg:guac/files/home/work/test/build/webserver", + "pkg:oci/static@sha256:6ad5b696af3ca05a048bd29bf0f623040462638cb0b29c8d702cbb2805687388?repository_url=gcr.io/distroless/static&tag=nonroot", + "pkg:guac/pkg/ghcr.io/guacsec/guac/guacsec@sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870", + }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -510,12 +534,17 @@ func Test_cyclonedxParser_getComponentPackages(t *testing.T) { if err := c.getPackages(); err != nil { t.Errorf("Failed to getTopLevelPackage %s", err) } - wantPackage, err := asmhelpers.PurlToPkg(tt.wantPurl) - if err != nil { - t.Errorf("Failed to parse purl %v %v", tt.wantPurl, err) + wantPackage := make([]*model.PkgInputSpec, len(tt.wantPurl)) + for i, item := range tt.wantPurl { + pkg, err := asmhelpers.PurlToPkg(item) + if err != nil { + t.Errorf("Failed to parse purl %v %v", tt.wantPurl, err) + } + wantPackage[i] = pkg } + for _, comp := range *tt.cdxBom.Components { - if d := cmp.Diff(*wantPackage, *c.packagePackages[comp.BOMRef][0]); len(d) != 0 { + if d := cmp.Diff(wantPackage, c.packagePackages[comp.BOMRef]); len(d) != 0 { t.Errorf("addRootPackage failed to produce expected package for %v", tt.name) t.Errorf("spdx.GetPredicate mismatch values (+got, -expected): %s", d) }