Skip to content

Commit

Permalink
generate SBOM from go binaries
Browse files Browse the repository at this point in the history
and attach to go targets layer
  • Loading branch information
Frankie Gallina-Jones authored and ryanmoran committed Feb 18, 2022
1 parent dd2cac2 commit 75412fd
Show file tree
Hide file tree
Showing 31 changed files with 1,148 additions and 260 deletions.
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.Process("Generating SBOM")

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))

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

logs.Break()

var processes []packit.Process
for index, binary := range binaries {
processes = append(processes, packit.Process{
Expand Down
245 changes: 110 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,18 @@ 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())
// TODO: assertions about gocache layer
})
})

Expand Down Expand Up @@ -559,5 +500,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

0 comments on commit 75412fd

Please sign in to comment.