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

Generate SBOM from built binaries #263

Merged
merged 3 commits into from
Feb 18, 2022
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
32 changes: 29 additions & 3 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"path/filepath"
"time"

"github.com/paketo-buildpacks/packit"
"github.com/paketo-buildpacks/packit/chronos"
"github.com/paketo-buildpacks/packit/scribe"
"github.com/paketo-buildpacks/packit/v2"
"github.com/paketo-buildpacks/packit/v2/chronos"
"github.com/paketo-buildpacks/packit/v2/sbom"
"github.com/paketo-buildpacks/packit/v2/scribe"
)

//go:generate faux --interface BuildProcess --output fakes/build_process.go
Expand All @@ -31,6 +32,11 @@ type SourceRemover interface {
Clear(path string) error
}

//go:generate faux --interface SBOMGenerator --output fakes/sbom_generator.go
type SBOMGenerator interface {
Generate(dir string) (sbom.SBOM, error)
}

func Build(
parser ConfigurationParser,
buildProcess BuildProcess,
Expand All @@ -39,6 +45,7 @@ func Build(
clock chronos.Clock,
logs scribe.Emitter,
sourceRemover SourceRemover,
sbomGenerator SBOMGenerator,
) packit.BuildFunc {
return func(context packit.BuildContext) (packit.BuildResult, error) {
logs.Title("%s %s", context.BuildpackInfo.Name, context.BuildpackInfo.Version)
Expand Down Expand Up @@ -113,6 +120,25 @@ func Build(
return packit.BuildResult{}, err
}

logs.GeneratingSBOM(filepath.Join(targetsLayer.Path, "bin"))

var sbomContent sbom.SBOM
duration, err := clock.Measure(func() error {
sbomContent, err = sbomGenerator.Generate(filepath.Join(targetsLayer.Path, "bin"))
return err
})
if err != nil {
return packit.BuildResult{}, err
}
logs.Action("Completed in %s", duration.Round(time.Millisecond))
logs.Break()

logs.FormattingSBOM(context.BuildpackInfo.SBOMFormats...)
targetsLayer.SBOM, err = sbomContent.InFormats(context.BuildpackInfo.SBOMFormats...)
if err != nil {
return packit.BuildResult{}, err
}

var processes []packit.Process
for index, binary := range binaries {
processes = append(processes, packit.Process{
Expand Down
244 changes: 109 additions & 135 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import (

gobuild "github.com/paketo-buildpacks/go-build"
"github.com/paketo-buildpacks/go-build/fakes"
"github.com/paketo-buildpacks/packit"
"github.com/paketo-buildpacks/packit/chronos"
"github.com/paketo-buildpacks/packit/scribe"
"github.com/paketo-buildpacks/packit/v2"
"github.com/paketo-buildpacks/packit/v2/chronos"
"github.com/paketo-buildpacks/packit/v2/sbom"
"github.com/paketo-buildpacks/packit/v2/scribe"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
Expand All @@ -35,6 +36,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
sourceRemover *fakes.SourceRemover
parser *fakes.ConfigurationParser
checksumCalculator *fakes.ChecksumCalculator
sbomGenerator *fakes.SBOMGenerator

build packit.BuildFunc
)
Expand Down Expand Up @@ -76,6 +78,9 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
ImportPath: "some-import-path",
}

sbomGenerator = &fakes.SBOMGenerator{}
sbomGenerator.GenerateCall.Returns.SBOM = sbom.SBOM{}

build = gobuild.Build(
parser,
buildProcess,
Expand All @@ -84,6 +89,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
clock,
scribe.NewEmitter(logs),
sourceRemover,
sbomGenerator,
)
})

Expand All @@ -99,56 +105,56 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
CNBPath: cnbDir,
Stack: "some-stack",
BuildpackInfo: packit.BuildpackInfo{
Name: "Some Buildpack",
Version: "some-version",
Name: "Some Buildpack",
Version: "some-version",
SBOMFormats: []string{sbom.CycloneDXFormat, sbom.SPDXFormat},
},
Layers: packit.Layers{Path: layersDir},
})
Expect(err).NotTo(HaveOccurred())

Expect(result).To(Equal(packit.BuildResult{
Layers: []packit.Layer{
{
Name: "targets",
Path: filepath.Join(layersDir, "targets"),
SharedEnv: packit.Environment{},
BuildEnv: packit.Environment{},
LaunchEnv: packit.Environment{},
ProcessLaunchEnv: map[string]packit.Environment{},
Build: false,
Launch: true,
Cache: false,
Metadata: map[string]interface{}{
"cache_sha": "some-checksum",
"built_at": timestamp.Format(time.RFC3339Nano),
},
},
{
Name: "gocache",
Path: filepath.Join(layersDir, "gocache"),
SharedEnv: packit.Environment{},
BuildEnv: packit.Environment{},
LaunchEnv: packit.Environment{},
ProcessLaunchEnv: map[string]packit.Environment{},
Build: false,
Launch: false,
Cache: true,
},
Expect(result.Layers).To(HaveLen(2))

targets := result.Layers[0]
Expect(targets.Name).To(Equal("targets"))
Expect(targets.Path).To(Equal(filepath.Join(layersDir, "targets")))
Expect(targets.Metadata).To(Equal(map[string]interface{}{
"cache_sha": "some-checksum",
"built_at": timestamp.Format(time.RFC3339Nano),
}))
Expect(targets.Build).To(BeFalse())
Expect(targets.Cache).To(BeFalse())
Expect(targets.Launch).To(BeTrue())

Expect(targets.SBOM.Formats()).To(Equal([]packit.SBOMFormat{
{
Extension: sbom.Format(sbom.CycloneDXFormat).Extension(),
Content: sbom.NewFormattedReader(sbom.SBOM{}, sbom.CycloneDXFormat),
},
Launch: packit.LaunchMetadata{
Processes: []packit.Process{
{
Type: "some-start-command",
Command: "path/some-start-command",
Direct: true,
Default: true,
},
{
Type: "another-start-command",
Command: "path/another-start-command",
Direct: true,
},
},
{
Extension: sbom.Format(sbom.SPDXFormat).Extension(),
Content: sbom.NewFormattedReader(sbom.SBOM{}, sbom.SPDXFormat),
},
}))

gocache := result.Layers[1]
Expect(gocache.Name).To(Equal("gocache"))
Expect(gocache.Path).To(Equal(filepath.Join(layersDir, "gocache")))
Expect(gocache.Build).To(BeFalse())
Expect(gocache.Cache).To(BeTrue())
Expect(gocache.Launch).To(BeFalse())

Expect(result.Launch.Processes).To(Equal([]packit.Process{
{
Type: "some-start-command",
Command: "path/some-start-command",
Direct: true,
Default: true,
},
{
Type: "another-start-command",
Command: "path/another-start-command",
Direct: true,
},
}))

Expand All @@ -170,6 +176,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(pathManager.TeardownCall.Receives.GoPath).To(Equal("some-go-path"))

Expect(sourceRemover.ClearCall.Receives.Path).To(Equal(workingDir))
Expect(sbomGenerator.GenerateCall.Receives.Dir).To(Equal(filepath.Join(targets.Path, "bin")))

Expect(logs.String()).To(ContainSubstring("Some Buildpack some-version"))
Expect(logs.String()).To(ContainSubstring("Assigning launch processes"))
Expand Down Expand Up @@ -252,53 +259,20 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
})
Expect(err).NotTo(HaveOccurred())

Expect(result).To(Equal(packit.BuildResult{
Layers: []packit.Layer{
{
Name: "targets",
Path: filepath.Join(layersDir, "targets"),
SharedEnv: packit.Environment{},
BuildEnv: packit.Environment{},
LaunchEnv: packit.Environment{},
ProcessLaunchEnv: map[string]packit.Environment{},
Build: false,
Launch: true,
Cache: false,
Metadata: map[string]interface{}{
"cache_sha": "some-checksum",
"built_at": timestamp.Format(time.RFC3339Nano),
},
},
{
Name: "gocache",
Path: filepath.Join(layersDir, "gocache"),
SharedEnv: packit.Environment{},
BuildEnv: packit.Environment{},
LaunchEnv: packit.Environment{},
ProcessLaunchEnv: map[string]packit.Environment{},
Build: false,
Launch: false,
Cache: true,
},
Expect(result.Launch.Processes).To(Equal([]packit.Process{
{
Type: "some-start-command",
Command: "path/some-start-command",
Direct: true,
Default: true,
},
Launch: packit.LaunchMetadata{
Processes: []packit.Process{
{
Type: "some-start-command",
Command: "path/some-start-command",
Direct: true,
Default: true,
},
{
Type: "another-start-command",
Command: "path/another-start-command",
Direct: true,
},
},
{
Type: "another-start-command",
Command: "path/another-start-command",
Direct: true,
},
}))
})

})

context("when the targets were previously built", func() {
Expand All @@ -312,7 +286,7 @@ launch = true
Expect(err).NotTo(HaveOccurred())
})

it("returns a result that builds correctly", func() {
it("uses the cached layer", func() {
result, err := build(packit.BuildContext{
WorkingDir: workingDir,
CNBPath: cnbDir,
Expand All @@ -325,51 +299,17 @@ launch = true
})
Expect(err).NotTo(HaveOccurred())

Expect(result).To(Equal(packit.BuildResult{
Layers: []packit.Layer{
{
Name: "targets",
Path: filepath.Join(layersDir, "targets"),
SharedEnv: packit.Environment{},
BuildEnv: packit.Environment{},
LaunchEnv: packit.Environment{},
ProcessLaunchEnv: map[string]packit.Environment{},
Build: false,
Launch: true,
Cache: false,
Metadata: map[string]interface{}{
"cache_sha": "some-checksum",
"built_at": timestamp.Add(-10 * time.Second).Format(time.RFC3339Nano),
},
},
{
Name: "gocache",
Path: filepath.Join(layersDir, "gocache"),
SharedEnv: packit.Environment{},
BuildEnv: packit.Environment{},
LaunchEnv: packit.Environment{},
ProcessLaunchEnv: map[string]packit.Environment{},
Build: false,
Launch: false,
Cache: true,
},
},
Launch: packit.LaunchMetadata{
Processes: []packit.Process{
{
Type: "some-start-command",
Command: "path/some-start-command",
Direct: true,
Default: true,
},
{
Type: "another-start-command",
Command: "path/another-start-command",
Direct: true,
},
},
},
Expect(result.Layers).To(HaveLen(2))
targets := result.Layers[0]
Expect(targets.Name).To(Equal("targets"))
Expect(targets.Path).To(Equal(filepath.Join(layersDir, "targets")))
Expect(targets.Metadata).To(Equal(map[string]interface{}{
"cache_sha": "some-checksum",
"built_at": timestamp.Add(-10 * time.Second).Format(time.RFC3339Nano),
}))
Expect(targets.Build).To(BeFalse())
Expect(targets.Cache).To(BeFalse())
Expect(targets.Launch).To(BeTrue())
})
})

Expand Down Expand Up @@ -559,5 +499,39 @@ launch = true
Expect(err).To(MatchError(ContainSubstring("cannot enable live reload on stack 'io.paketo.stacks.tiny': stack does not support watchexec")))
})
})
context("when an SBOM cannot be generated", func() {
it.Before(func() {
sbomGenerator.GenerateCall.Returns.Error = errors.New("sbom generation error")
})
it("fails the build and returns the error", func() {
_, err := build(packit.BuildContext{
WorkingDir: workingDir,
CNBPath: cnbDir,
Stack: "io.paketo.stacks.tiny",
BuildpackInfo: packit.BuildpackInfo{
Name: "Some Buildpack",
Version: "some-version",
},
Layers: packit.Layers{Path: layersDir},
})
Expect(err).To(MatchError("sbom generation error"))
})
})
context("when a requested SBOM format is invalid", func() {
it("fails the build and returns the error", func() {
_, err := build(packit.BuildContext{
WorkingDir: workingDir,
CNBPath: cnbDir,
Stack: "io.paketo.stacks.tiny",
BuildpackInfo: packit.BuildpackInfo{
Name: "Some Buildpack",
Version: "some-version",
SBOMFormats: []string{"invalid-format"},
},
Layers: packit.Layers{Path: layersDir},
})
Expect(err).To(MatchError(`"invalid-format" is not a supported SBOM format`))
})
})
})
}
3 changes: 2 additions & 1 deletion buildpack.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
api = "0.6"
api = "0.7"

[buildpack]
homepage = "https://github.com/paketo-buildpacks/go-build"
id = "paketo-buildpacks/go-build"
name = "Paketo Go Build Buildpack"
sbom-formats = ["application/vnd.cyclonedx+json","application/spdx+json","application/vnd.syft+json"]

[[buildpack.licenses]]
type = "Apache-2.0"
Expand Down
Loading