Skip to content

Commit

Permalink
Merge pull request #105 from paketo-buildpacks/sbom-deps
Browse files Browse the repository at this point in the history
Adds support for DependencyLayerContributor and HelperLayerContributor to generate SBOMs
  • Loading branch information
Daniel Mikusa authored Nov 19, 2021
2 parents 6006d2f + f3f1b0b commit 5cea674
Show file tree
Hide file tree
Showing 11 changed files with 484 additions and 41 deletions.
48 changes: 46 additions & 2 deletions buildpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/heroku/color"

"github.com/paketo-buildpacks/libpak/bard"
"github.com/paketo-buildpacks/libpak/sbom"
)

// BuildpackConfiguration represents a build or launch configuration parameter.
Expand Down Expand Up @@ -80,13 +81,19 @@ type BuildpackDependency struct {
// Stacks are the stacks the dependency is compatible with.
Stacks []string `toml:"stacks"`

// Licenses are the stacks the dependency is distributed under.
// Licenses are the licenses the dependency is distributed under.
Licenses []BuildpackDependencyLicense `toml:"licenses"`

// CPEs are the Common Platform Enumeration identifiers for the dependency
CPEs []string `toml:"cpes"`

// PURL is the package URL that identifies the dependency
PURL string `toml:"purl"`
}

// AsBOMEntry renders a bill of materials entry describing the dependency.
//
// Deprecated: as of Buildpacks RFC 95, use `sherpa.SBOMScanner` instead
// Deprecated: as of Buildpacks RFC 95, use `BuildpackDependency.AsSyftArtifact` instead
func (b BuildpackDependency) AsBOMEntry() libcnb.BOMEntry {
return libcnb.BOMEntry{
Name: b.ID,
Expand All @@ -101,6 +108,33 @@ func (b BuildpackDependency) AsBOMEntry() libcnb.BOMEntry {
}
}

// AsSyftArtifact renders a bill of materials entry describing the dependency as Syft.
func (b BuildpackDependency) AsSyftArtifact() (sbom.SyftArtifact, error) {
licenses := []string{}
for _, license := range b.Licenses {
licenses = append(licenses, license.Type)
}

sbomArtifact := sbom.SyftArtifact{
Name: b.Name,
Version: b.Version,
Type: "UnknownPackage",
FoundBy: "libpak",
Licenses: licenses,
Locations: []sbom.SyftLocation{{Path: "buildpack.toml"}},
CPEs: b.CPEs,
PURL: b.PURL,
}

var err error
sbomArtifact.ID, err = sbomArtifact.Hash()
if err != nil {
return sbom.SyftArtifact{}, fmt.Errorf("unable to generate hash\n%w", err)
}

return sbomArtifact, nil
}

// BuildpackMetadata is an extension to libcnb.Buildpack's metadata with opinions.
type BuildpackMetadata struct {

Expand Down Expand Up @@ -196,6 +230,16 @@ func NewBuildpackMetadata(metadata map[string]interface{}) (BuildpackMetadata, e
}
}

if v, ok := v["cpes"].([]interface{}); ok {
for _, v := range v {
d.CPEs = append(d.CPEs, v.(string))
}
}

if v, ok := v["purl"].(string); ok {
d.PURL = v
}

m.Dependencies = append(m.Dependencies, d)
}
}
Expand Down
36 changes: 36 additions & 0 deletions buildpack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/sclevine/spec"

"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/sbom"
)

func testBuildpack(t *testing.T, context spec.G, it spec.S) {
Expand Down Expand Up @@ -62,6 +63,37 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
}))
})

it("renders dependency as a SyftArtifact", func() {
dependency := libpak.BuildpackDependency{
ID: "test-id",
Name: "test-name",
Version: "1.1.1",
URI: "test-uri",
SHA256: "test-sha256",
Stacks: []string{"test-stack"},
Licenses: []libpak.BuildpackDependencyLicense{
{
Type: "test-type",
URI: "test-uri",
},
},
CPEs: []string{"test-cpe1", "test-cpe2"},
PURL: "test-purl",
}

Expect(dependency.AsSyftArtifact()).To(Equal(sbom.SyftArtifact{
ID: "46713835f08d90b7",
Name: "test-name",
Version: "1.1.1",
Type: "UnknownPackage",
FoundBy: "libpak",
Licenses: []string{"test-type"},
Locations: []sbom.SyftLocation{{Path: "buildpack.toml"}},
CPEs: []string{"test-cpe1", "test-cpe2"},
PURL: "test-purl",
}))
})

context("NewBuildpackMetadata", func() {
it("deserializes metadata", func() {
actual := map[string]interface{}{
Expand All @@ -86,6 +118,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
"uri": "test-uri",
},
},
"cpes": []interface{}{"cpe:2.3:a:test-id:1.1.1"},
"purl": "pkg:generic:[email protected]",
},
},
"include-files": []interface{}{"test-include-file"},
Expand Down Expand Up @@ -114,6 +148,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
URI: "test-uri",
},
},
CPEs: []string{"cpe:2.3:a:test-id:1.1.1"},
PURL: "pkg:generic:[email protected]",
},
},
IncludeFiles: []string{"test-include-file"},
Expand Down
2 changes: 2 additions & 0 deletions dependency_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ func testDependencyCache(t *testing.T, context spec.G, it spec.S) {
URI: "test-uri",
},
},
CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
PURL: "pkg:generic/[email protected]?arch=amd64",
}

dependencyCache = libpak.DependencyCache{
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ go 1.15
require (
github.com/CycloneDX/cyclonedx-go v0.4.0
github.com/Masterminds/semver/v3 v3.1.1
github.com/buildpacks/libcnb v1.24.1-0.20211118031525-6aa81e50810d
github.com/buildpacks/libcnb v1.25.0
github.com/creack/pty v1.1.17
github.com/heroku/color v0.0.6
github.com/imdario/mergo v0.3.12
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/onsi/gomega v1.17.0
github.com/pelletier/go-toml v1.9.4
github.com/sclevine/spec v1.4.0
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030I
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs=
github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
github.com/buildpacks/libcnb v1.24.1-0.20211118031525-6aa81e50810d h1:rAJsgF0p6rtUPGSTOCnCt/ofXKQM34wN+XKMXH3bNBE=
github.com/buildpacks/libcnb v1.24.1-0.20211118031525-6aa81e50810d/go.mod h1:XX0+zHW8CNLNwiiwowgydAgWWfyDt8Lj1NcuWtkkBJQ=
github.com/buildpacks/libcnb v1.25.0 h1:f0UWYUbXQ/vTX6SztGn+sP/F6cVSAbBQO4B5/R1LEP8=
github.com/buildpacks/libcnb v1.25.0/go.mod h1:XX0+zHW8CNLNwiiwowgydAgWWfyDt8Lj1NcuWtkkBJQ=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -41,6 +41,8 @@ github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Expand Down
96 changes: 80 additions & 16 deletions layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/buildpacks/libcnb"

"github.com/paketo-buildpacks/libpak/sbom"
"github.com/paketo-buildpacks/libpak/sherpa"

"github.com/paketo-buildpacks/libpak/bard"
Expand Down Expand Up @@ -139,15 +140,18 @@ func NewDependencyLayer(dependency BuildpackDependency, cache DependencyCache, t
ExpectedTypes: types,
}

entry := dependency.AsBOMEntry()
entry.Metadata["layer"] = c.LayerName()
var entry libcnb.BOMEntry
if dependency.PURL == "" && len(dependency.CPEs) == 0 {
entry = dependency.AsBOMEntry()
entry.Metadata["layer"] = c.LayerName()

if types.Launch {
entry.Launch = true
}
if !(types.Launch && !types.Cache && !types.Build) {
// launch-only layers are the only layers NOT guaranteed to be present in the build environment
entry.Build = true
if types.Launch {
entry.Launch = true
}
if !(types.Launch && !types.Cache && !types.Build) {
// launch-only layers are the only layers NOT guaranteed to be present in the build environment
entry.Build = true
}
}

return c, entry
Expand All @@ -168,6 +172,18 @@ func (d *DependencyLayerContributor) Contribute(layer libcnb.Layer, f Dependency
}
defer artifact.Close()

sbomArtifact, err := d.Dependency.AsSyftArtifact()
if err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to get SBOM artifact %s\n%w", d.Dependency.ID, err)
}

sbomPath := layer.SBOMPath(libcnb.SyftJSON)
dep := sbom.NewSyftDependency(layer.Path, []sbom.SyftArtifact{sbomArtifact})
d.Logger.Debugf("Writing Syft SBOM at %s: %+v", sbomPath, dep)
if err := dep.WriteTo(sbomPath); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to write SBOM\n%w", err)
}

return f(artifact)
})
}
Expand Down Expand Up @@ -210,14 +226,17 @@ func NewHelperLayer(buildpack libcnb.Buildpack, names ...string) (HelperLayerCon
BuildpackInfo: buildpack.Info,
}

entry := libcnb.BOMEntry{
Name: "helper",
Metadata: map[string]interface{}{
"layer": c.Name(),
"names": names,
"version": buildpack.Info.Version,
},
Launch: true,
var entry libcnb.BOMEntry
if buildpack.API == "0.6" || buildpack.API == "0.5" || buildpack.API == "0.4" || buildpack.API == "0.3" || buildpack.API == "0.2" || buildpack.API == "0.1" {
entry = libcnb.BOMEntry{
Name: "helper",
Metadata: map[string]interface{}{
"layer": c.Name(),
"names": names,
"version": buildpack.Info.Version,
},
Launch: true,
}
}

return c, entry
Expand Down Expand Up @@ -261,6 +280,51 @@ func (h HelperLayerContributor) Contribute(layer libcnb.Layer) (libcnb.Layer, er
}
}

sbomArtifact, err := h.AsSyftArtifact()
if err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to get SBOM artifact for helper\n%w", err)
}

sbomPath := layer.SBOMPath(libcnb.SyftJSON)
dep := sbom.NewSyftDependency(layer.Path, []sbom.SyftArtifact{sbomArtifact})
h.Logger.Debugf("Writing Syft SBOM at %s: %+v", sbomPath, dep)
if err := dep.WriteTo(sbomPath); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to write SBOM\n%w", err)
}

return layer, nil
})
}

func (h HelperLayerContributor) AsSyftArtifact() (sbom.SyftArtifact, error) {
licenses := []string{}
for _, license := range h.BuildpackInfo.Licenses {
licenses = append(licenses, license.Type)
}

locations := []sbom.SyftLocation{}
cpes := []string{}
for _, name := range h.Names {
locations = append(locations, sbom.SyftLocation{Path: name})
cpes = append(cpes, fmt.Sprintf("cpe:2.3:a:%s:%s:%s:*:*:*:*:*:*:*",
h.BuildpackInfo.ID, name, h.BuildpackInfo.Version))
}

artifact := sbom.SyftArtifact{
Name: "helper",
Version: h.BuildpackInfo.Version,
Type: "UnknownPackage",
FoundBy: "libpak",
Licenses: licenses,
Locations: locations,
CPEs: cpes,
PURL: fmt.Sprintf("pkg:generic/%s@%s", h.BuildpackInfo.ID, h.BuildpackInfo.Version),
}
var err error
artifact.ID, err = artifact.Hash()
if err != nil {
return sbom.SyftArtifact{}, fmt.Errorf("unable to generate hash\n%w", err)
}

return artifact, nil
}
Loading

0 comments on commit 5cea674

Please sign in to comment.