Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for DependencyLayerContributor and HelperLayerContributor to generate SBOMs #105

Merged
merged 1 commit into from
Nov 19, 2021
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
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:test-id@1.1.1",
},
},
"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:test-id@1.1.1",
},
},
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/some-java11@11.0.2?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