diff --git a/internal/formats/common/cyclonedxhelpers/cpe_test.go b/internal/formats/common/cyclonedxhelpers/cpe_test.go index 81d6f6f0784..879811ca2b7 100644 --- a/internal/formats/common/cyclonedxhelpers/cpe_test.go +++ b/internal/formats/common/cyclonedxhelpers/cpe_test.go @@ -7,9 +7,12 @@ import ( "github.com/stretchr/testify/assert" ) +var ( + testCPE = pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*") + testCPE2 = pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*") +) + func Test_CPE(t *testing.T) { - testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*") - testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*") tests := []struct { name string input pkg.Package diff --git a/internal/formats/common/cyclonedxhelpers/external_references.go b/internal/formats/common/cyclonedxhelpers/external_references.go index f109ad1993e..97b3e4f118e 100644 --- a/internal/formats/common/cyclonedxhelpers/external_references.go +++ b/internal/formats/common/cyclonedxhelpers/external_references.go @@ -5,10 +5,14 @@ import ( "github.com/CycloneDX/cyclonedx-go" "github.com/anchore/syft/syft/pkg" + "github.com/facebookincubator/nvdtools/wfn" ) func ExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference { refs := []cyclonedx.ExternalReference{} + if len(p.CPEs) > 0 { + refs = append(refs, generateCPERefs(p.CPEs)...) + } if hasMetadata(p) { switch metadata := p.Metadata.(type) { case pkg.ApkMetadata: @@ -63,3 +67,15 @@ func ExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference { } return nil } + +func generateCPERefs(cpes []wfn.Attributes) []cyclonedx.ExternalReference { + cpeRefs := []cyclonedx.ExternalReference{} + for _, cpe := range cpes { + cpeRefs = append(cpeRefs, cyclonedx.ExternalReference{ + URL: pkg.CPEURI(cpe), + Type: cyclonedx.ERTypeAdvisories, + Comment: "cpe", + }) + } + return cpeRefs +} diff --git a/internal/formats/common/cyclonedxhelpers/external_references_test.go b/internal/formats/common/cyclonedxhelpers/external_references_test.go index 9f22f75cdd5..e0ca7bf9b84 100644 --- a/internal/formats/common/cyclonedxhelpers/external_references_test.go +++ b/internal/formats/common/cyclonedxhelpers/external_references_test.go @@ -19,6 +19,16 @@ func Test_ExternalReferences(t *testing.T) { input: pkg.Package{}, expected: nil, }, + { + name: "cpes", + input: pkg.Package{ + CPEs: []pkg.CPE{ + testCPE, + testCPE2, + }, + }, + expected: &[]cyclonedx.ExternalReference{{URL: "cpe:/a:name:name:3.2", Comment: "cpe", Type: "advisories"}, {URL: "cpe:/a:name:name2:3.2", Comment: "cpe", Type: "advisories"}}, + }, { name: "from apk", input: pkg.Package{ diff --git a/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden index e76154bc9a0..4dcc4b65e8b 100644 --- a/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden +++ b/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden @@ -1,10 +1,10 @@ { "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:258d2616-5b1f-48cd-82a3-d6c95e262950", + "serialNumber": "urn:uuid:dc92128f-0c3c-44a5-ba74-7f79aa35065e", "version": 1, "metadata": { - "timestamp": "2022-01-14T22:47:00Z", + "timestamp": "2022-02-13T23:34:07Z", "tools": [ { "vendor": "anchore", @@ -13,6 +13,7 @@ } ], "component": { + "bom-ref": "163686ac6e30c752", "type": "file", "name": "/some/path", "version": "" @@ -32,6 +33,13 @@ ], "cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", "purl": "a-purl-2", + "externalReferences": [ + { + "url": "cpe:/:some:package:2", + "comment": "cpe", + "type": "advisories" + } + ], "properties": [ { "name": "foundBy", @@ -61,6 +69,13 @@ "version": "2.0.1", "cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", "purl": "a-purl-2", + "externalReferences": [ + { + "url": "cpe:/:some:package:2", + "comment": "cpe", + "type": "advisories" + } + ], "properties": [ { "name": "foundBy", diff --git a/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden index 9505eb86599..c796096da82 100644 --- a/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden +++ b/internal/formats/cyclonedx13json/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden @@ -1,10 +1,10 @@ { "bomFormat": "CycloneDX", "specVersion": "1.3", - "serialNumber": "urn:uuid:8a84b1cf-e918-4842-a6a8-c7fdafc55bc0", + "serialNumber": "urn:uuid:0ad4ecd6-903b-4a75-abbd-413de0d7452f", "version": 1, "metadata": { - "timestamp": "2022-01-14T22:47:00Z", + "timestamp": "2022-02-13T23:34:07Z", "tools": [ { "vendor": "anchore", @@ -13,6 +13,7 @@ } ], "component": { + "bom-ref": "711095b1cdf90cce", "type": "container", "name": "user-image-input", "version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368" @@ -32,6 +33,13 @@ ], "cpe": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*", "purl": "a-purl-1", + "externalReferences": [ + { + "url": "cpe:/:some:package:1", + "comment": "cpe", + "type": "advisories" + } + ], "properties": [ { "name": "foundBy", @@ -65,6 +73,13 @@ "version": "2.0.1", "cpe": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*", "purl": "a-purl-2", + "externalReferences": [ + { + "url": "cpe:/:some:package:2", + "comment": "cpe", + "type": "advisories" + } + ], "properties": [ { "name": "foundBy", diff --git a/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden b/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden index 4b4ad555233..10abfc9c3f7 100644 --- a/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden +++ b/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxDirectoryEncoder.golden @@ -1,7 +1,7 @@ - + - 2022-01-14T22:46:49Z + 2022-02-13T23:34:27Z anchore @@ -9,7 +9,7 @@ [not provided] - + /some/path @@ -25,6 +25,12 @@ cpe:2.3:*:some:package:2:*:*:*:*:*:*:* a-purl-2 + + + cpe:/:some:package:2 + cpe + + the-cataloger-1 python @@ -38,6 +44,12 @@ 2.0.1 cpe:2.3:*:some:package:2:*:*:*:*:*:*:* a-purl-2 + + + cpe:/:some:package:2 + cpe + + the-cataloger-2 deb diff --git a/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden b/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden index 58421601882..47b3886cb65 100644 --- a/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden +++ b/internal/formats/cyclonedx13xml/test-fixtures/snapshot/TestCycloneDxImageEncoder.golden @@ -1,7 +1,7 @@ - + - 2022-01-14T22:46:49Z + 2022-02-13T23:34:27Z anchore @@ -9,7 +9,7 @@ [not provided] - + user-image-input sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368 @@ -25,6 +25,12 @@ cpe:2.3:*:some:package:1:*:*:*:*:*:*:* a-purl-1 + + + cpe:/:some:package:1 + cpe + + the-cataloger-1 python @@ -39,6 +45,12 @@ 2.0.1 cpe:2.3:*:some:package:2:*:*:*:*:*:*:* a-purl-2 + + + cpe:/:some:package:2 + cpe + + the-cataloger-2 deb diff --git a/syft/pkg/cpe.go b/syft/pkg/cpe.go index db0194d9217..ddc6530a4ef 100644 --- a/syft/pkg/cpe.go +++ b/syft/pkg/cpe.go @@ -99,6 +99,22 @@ func CPEString(c CPE) string { return output.BindToFmtString() } +func CPEURI(c CPE) string { + output := CPE{} + output.Vendor = sanitize(c.Vendor) + output.Product = sanitize(c.Product) + output.Language = sanitize(c.Language) + output.Version = sanitize(c.Version) + output.TargetSW = sanitize(c.TargetSW) + output.Part = sanitize(c.Part) + output.Edition = sanitize(c.Edition) + output.Other = sanitize(c.Other) + output.SWEdition = sanitize(c.SWEdition) + output.TargetHW = sanitize(c.TargetHW) + output.Update = sanitize(c.Update) + return output.BindToURI() +} + // sanitize is a modified version of WFNize function from nvdtools // that quotes all the allowed punctation chars with a slash and replaces // spaces with underscores. It differs from the upstream implmentation as diff --git a/syft/pkg/cpe_test.go b/syft/pkg/cpe_test.go index a74ab05e4e7..f78403ec2ec 100644 --- a/syft/pkg/cpe_test.go +++ b/syft/pkg/cpe_test.go @@ -50,6 +50,10 @@ func TestNewCPE(t *testing.T) { t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", CPEString(test.expected), CPEString(actual)) } + if CPEURI(test.expected) != test.input { + t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", CPEURI(test.expected), test.input) + } + }) } } @@ -105,6 +109,9 @@ func Test_CPEParser(t *testing.T) { assert.Equal(t, c1, test.WFN) assert.Equal(t, c2, test.WFN) assert.Equal(t, CPEString(test.WFN), test.CPEString) + assert.Equal(t, CPEURI(c2), test.CPEUrl) + assert.Equal(t, CPEURI(c1), test.CPEUrl) + assert.Equal(t, must(NewCPE(CPEURI(c1))), test.WFN) }) } }