Skip to content

Commit

Permalink
GenerateFromDependency without CPE or PURL (#373)
Browse files Browse the repository at this point in the history
* if dependency has no CPE, omit or use all-wildcards CPE value

* use all-N/A CPE instead of all-ANY cpe when CPE is unknown

* rename UnknownDependencyCPE --> UnknownCPE

* add godoc for UnknownCPE

Co-authored-by: Forest Eckhardt <feckhardt@pivotal.io>
  • Loading branch information
Frankie G-J and ForestEckhardt authored Aug 5, 2022
1 parent 32d5998 commit 444cccb
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 0 deletions.
9 changes: 9 additions & 0 deletions sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import (
"github.com/paketo-buildpacks/packit/v2/postal"
)

// UnknownCPE is a Common Platform Enumeration (CPE) that uses the NA (Not
// applicable) logical operator for all components of its name. It is designed
// not to match with other CPEs, to avoid false positive CPE matches.
const UnknownCPE = "cpe:2.3:-:-:-:-:-:-:-:-:-:-:-"

// SBOM holds the internal representation of the generated software
// bill-of-materials. This type can be combined with a FormattedReader to
// output the SBoM in a number of file formats.
Expand Down Expand Up @@ -70,6 +75,10 @@ func Generate(path string) (SBOM, error) {
// and the directory path where the dependency will be located within the
// application image.
func GenerateFromDependency(dependency postal.Dependency, path string) (SBOM, error) {
if dependency.CPE == "" {
dependency.CPE = UnknownCPE
}

cpe, err := pkg.NewCPE(dependency.CPE)
if err != nil {
return SBOM{}, err
Expand Down
96 changes: 96 additions & 0 deletions sbom/sbom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,102 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
Expect(cdx14Output.Metadata.Component.Name).To(Equal("some-path"), cdx.String())
})

context("when the input dependency does not have a CPE or a PURL", func() {
it("succeeds in generating an SBOM without CPEs", func() {
bom, err := sbom.GenerateFromDependency(postal.Dependency{
ID: "go",
Licenses: []string{"BSD-3-Clause"},
Name: "Go",
SHA256: "ca9ef23a5db944b116102b87c1ae9344b27e011dae7157d2f1e501abd39e9829",
Source: "https://dl.google.com/go/go1.16.9.src.tar.gz",
SourceSHA256: "0a1cc7fd7bd20448f71ebed64d846138850d5099b18cf5cc10a4fc45160d8c3d",
Stacks: []string{"io.buildpacks.stacks.bionic", "io.paketo.stacks.tiny"},
URI: "https://deps.paketo.io/go/go_go1.16.9_linux_x64_bionic_ca9ef23a.tgz",
Version: "1.16.9",
}, "some-path")
Expect(err).NotTo(HaveOccurred())

formatter, err := bom.InFormats(sbom.SyftFormat, sbom.CycloneDXFormat, sbom.SPDXFormat)
Expect(err).NotTo(HaveOccurred())

formats := formatter.Formats()

syft := bytes.NewBuffer(nil)
for _, format := range formats {
if format.Extension == "syft.json" {
_, err = io.Copy(syft, format.Content)
Expect(err).NotTo(HaveOccurred())
}
}

var syftDefaultOutput syftOutput
err = json.NewDecoder(syft).Decode(&syftDefaultOutput)
Expect(err).NotTo(HaveOccurred(), syft.String())

Expect(syftDefaultOutput.Schema.Version).To(Equal(`3.0.1`), syft.String())

goArtifact := syftDefaultOutput.Artifacts[0]
Expect(goArtifact.Name).To(Equal("Go"), syft.String())
Expect(goArtifact.Version).To(Equal("1.16.9"), syft.String())
Expect(goArtifact.Licenses).To(Equal([]string{"BSD-3-Clause"}), syft.String())
Expect(syftDefaultOutput.Source.Type).To(Equal("directory"), syft.String())
Expect(syftDefaultOutput.Source.Target).To(Equal("some-path"), syft.String())
Expect(goArtifact.PURL).To(BeEmpty())
Expect(goArtifact.CPEs).To(Equal([]string{"cpe:2.3:-:-:-:-:-:-:-:-:-:-:-"}))

cdx := bytes.NewBuffer(nil)
for _, format := range formats {
if format.Extension == "cdx.json" {
_, err = io.Copy(cdx, format.Content)
Expect(err).NotTo(HaveOccurred())
}
}

var cdxDefaultOutput cdxOutput
err = json.Unmarshal(cdx.Bytes(), &cdxDefaultOutput)
Expect(err).NotTo(HaveOccurred(), cdx.String())

Expect(cdxDefaultOutput.BOMFormat).To(Equal("CycloneDX"))
Expect(cdxDefaultOutput.SpecVersion).To(Equal("1.3"))

goComponent := cdxDefaultOutput.Components[0]
Expect(goComponent.Name).To(Equal("Go"), cdx.String())
Expect(goComponent.Version).To(Equal("1.16.9"), cdx.String())
Expect(goComponent.Licenses).To(HaveLen(1), cdx.String())
Expect(goComponent.Licenses[0].License.ID).To(Equal("BSD-3-Clause"), cdx.String())
Expect(goComponent.PURL).To(BeEmpty())

Expect(cdxDefaultOutput.Metadata.Component.Type).To(Equal("file"), cdx.String())
Expect(cdxDefaultOutput.Metadata.Component.Name).To(Equal("some-path"), cdx.String())

spdx := bytes.NewBuffer(nil)
for _, format := range formats {
if format.Extension == "spdx.json" {
_, err = io.Copy(spdx, format.Content)
Expect(err).NotTo(HaveOccurred())
}
}

var spdxDefaultOutput spdxOutput
err = json.Unmarshal(spdx.Bytes(), &spdxDefaultOutput)
Expect(err).NotTo(HaveOccurred())
Expect(err).NotTo(HaveOccurred(), spdx.String())

Expect(spdxDefaultOutput.SPDXVersion).To(Equal("SPDX-2.2"), spdx.String())

goPackage := spdxDefaultOutput.Packages[0]
Expect(goPackage.Name).To(Equal("Go"), spdx.String())
Expect(goPackage.Version).To(Equal("1.16.9"), spdx.String())
Expect(goPackage.LicenseConcluded).To(Equal("BSD-3-Clause"), spdx.String())
Expect(goPackage.LicenseDeclared).To(Equal("BSD-3-Clause"), spdx.String())
Expect(goPackage.ExternalRefs).To(Equal([]externalRef{{
Category: "SECURITY",
Locator: "cpe:2.3:-:-:-:-:-:-:-:-:-:-:-",
Type: "cpe23Type",
}}), spdx.String())
})
})

context("failure cases", func() {
context("when the CPE is invalid", func() {
it("returns an error", func() {
Expand Down

0 comments on commit 444cccb

Please sign in to comment.