Skip to content

Commit

Permalink
Add filesystem SBOM capability (#370)
Browse files Browse the repository at this point in the history
* add filesystem SBOM capability

* bump Go to 1.18 in go.mod

* update go.mod
  • Loading branch information
Sophie Wigmore authored Apr 18, 2022
1 parent 9e05a7e commit 8684022
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 21 deletions.
34 changes: 29 additions & 5 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/paketo-buildpacks/packit/v2"
"github.com/paketo-buildpacks/packit/v2/chronos"
"github.com/paketo-buildpacks/packit/v2/postal"
"github.com/paketo-buildpacks/packit/v2/sbom"
"github.com/paketo-buildpacks/packit/v2/scribe"
)

Expand Down Expand Up @@ -49,6 +50,11 @@ type EnvironmentConfiguration interface {
Configure(layer packit.Layer, extensionsDir, defaultIni string, scanDirs []string) error
}

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

// Build will return a packit.BuildFunc that will be invoked during the build
// phase of the buildpack lifecycle.
//
Expand All @@ -60,6 +66,7 @@ func Build(entryResolver EntryResolver,
dependencies DependencyManager,
files FileManager,
environment EnvironmentConfiguration,
sbomGenerator SBOMGenerator,
logger scribe.Emitter,
clock chronos.Clock) packit.BuildFunc {
return func(context packit.BuildContext) (packit.BuildResult, error) {
Expand Down Expand Up @@ -96,21 +103,19 @@ func Build(entryResolver EntryResolver,
logger.Debug.Subprocess(phpLayer.Path)
logger.Debug.Break()

logger.Debug.Process("Generating the SBOM")
logger.Debug.Break()
bom := dependencies.GenerateBillOfMaterials(dependency)
legacyBOM := dependencies.GenerateBillOfMaterials(dependency)
launch, build := entryResolver.MergeLayerTypes(PHPDependency, context.Plan.Entries)

phpLayer.Launch, phpLayer.Build, phpLayer.Cache = launch, build, build

var buildMetadata packit.BuildMetadata
if build {
buildMetadata.BOM = bom
buildMetadata.BOM = legacyBOM
}

var launchMetadata packit.LaunchMetadata
if launch {
launchMetadata.BOM = bom
launchMetadata.BOM = legacyBOM
}

cachedSHA, ok := phpLayer.Metadata[DepKey].(string)
Expand Down Expand Up @@ -162,6 +167,25 @@ func Build(entryResolver EntryResolver,
logger.Action("Completed in %s", duration.Round(time.Millisecond))
logger.Break()

logger.GeneratingSBOM(phpLayer.Path)
var sbomContent sbom.SBOM
duration, err = clock.Measure(func() error {
sbomContent, err = sbomGenerator.GenerateFromDependency(dependency, phpLayer.Path)
return err
})
if err != nil {
return packit.BuildResult{}, err
}

logger.Action("Completed in %s", duration.Round(time.Millisecond))
logger.Break()

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

logger.Debug.Subprocess("Finding PHP extensions directory")
extensionsDir, err := files.FindExtensions(phpLayer.Path)
if err != nil {
Expand Down
93 changes: 87 additions & 6 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

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

//nolint Ignore SA1019, informed usage of deprecated package
"github.com/paketo-buildpacks/packit/v2/paketosbom"
Expand All @@ -32,6 +33,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
cnbDir string
entryResolver *fakes.EntryResolver
dependencyManager *fakes.DependencyManager
sbomGenerator *fakes.SBOMGenerator
files *fakes.FileManager
clock chronos.Clock
environment *fakes.EnvironmentConfiguration
Expand Down Expand Up @@ -75,6 +77,9 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
},
},
}
// Syft SBOM
sbomGenerator = &fakes.SBOMGenerator{}
sbomGenerator.GenerateFromDependencyCall.Returns.SBOM = sbom.SBOM{}

files = &fakes.FileManager{}
files.FindExtensionsCall.Returns.String = "no-debug-non-zts-12345"
Expand All @@ -91,7 +96,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
buffer = bytes.NewBuffer(nil)
logEmitter := scribe.NewEmitter(buffer)

build = phpdist.Build(entryResolver, dependencyManager, files, environment, logEmitter, clock)
build = phpdist.Build(entryResolver, dependencyManager, files, environment, sbomGenerator, logEmitter, clock)
})

it.After(func() {
Expand All @@ -106,8 +111,9 @@ 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},
},
Plan: packit.BuildpackPlan{
Entries: []packit.BuildpackPlanEntry{
Expand All @@ -131,6 +137,16 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(result.Layers[0].Metadata[phpdist.DepKey]).To(Equal(""))

Expect(filepath.Join(layersDir, "php")).To(BeADirectory())
Expect(result.Layers[0].SBOM.Formats()).To(Equal([]packit.SBOMFormat{
{
Extension: sbom.Format(sbom.CycloneDXFormat).Extension(),
Content: sbom.NewFormattedReader(sbom.SBOM{}, sbom.CycloneDXFormat),
},
{
Extension: sbom.Format(sbom.SPDXFormat).Extension(),
Content: sbom.NewFormattedReader(sbom.SBOM{}, sbom.SPDXFormat),
},
}))

Expect(entryResolver.ResolveCall.Receives.Name).To(Equal(phpdist.PHPDependency))
Expect(entryResolver.ResolveCall.Receives.Entries).To(Equal([]packit.BuildpackPlanEntry{
Expand Down Expand Up @@ -172,6 +188,10 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
ExtensionDir: "no-debug-non-zts-12345",
}))

Expect(dependencyManager.GenerateBillOfMaterialsCall.Receives.Dependencies).To(Equal([]postal.Dependency{{Name: "PHP"}}))
Expect(sbomGenerator.GenerateFromDependencyCall.Receives.Dependency).To(Equal(postal.Dependency{Name: "PHP"}))
Expect(sbomGenerator.GenerateFromDependencyCall.Receives.Dir).To(Equal(filepath.Join(layersDir, "php")))

Expect(environment.ConfigureCall.CallCount).To(Equal(1))
Expect(environment.ConfigureCall.Receives.Layer.Path).To(Equal(filepath.Join(layersDir, "php")))
Expect(environment.ConfigureCall.Receives.ExtensionsDir).To(Equal("no-debug-non-zts-12345"))
Expand Down Expand Up @@ -383,6 +403,13 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
},
Layers: packit.Layers{Path: layersDir},
})
Expect(dependencyManager.GenerateBillOfMaterialsCall.CallCount).To(Equal(1))
Expect(dependencyManager.GenerateBillOfMaterialsCall.Receives.Dependencies).To(Equal([]postal.Dependency{
{
Name: "PHP",
SHA256: "some-sha",
},
}))

Expect(err).NotTo(HaveOccurred())
Expect(result.Launch.BOM).To(Equal(
Expand Down Expand Up @@ -418,6 +445,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
))

Expect(dependencyManager.DeliverCall.CallCount).To(Equal(0))
Expect(sbomGenerator.GenerateFromDependencyCall.CallCount).To(Equal(0))

Expect(buffer.String()).To(ContainSubstring("Some Buildpack some-version"))
Expect(buffer.String()).To(ContainSubstring("Resolving PHP version"))
Expand Down Expand Up @@ -506,7 +534,60 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(err).To(MatchError(ContainSubstring("permission denied")))
})
})
context("NEW when finding PHP extensions fails", func() {

context("when generating the SBOM returns an error", func() {
it("returns an error", func() {
_, err := build(packit.BuildContext{
CNBPath: cnbDir,
BuildpackInfo: packit.BuildpackInfo{
SBOMFormats: []string{"random-format"},
},
Plan: packit.BuildpackPlan{
Entries: []packit.BuildpackPlanEntry{
{
Name: "php",
Metadata: map[string]interface{}{
"version": "7.2.*",
"version-source": "some-source",
},
},
},
},
})
Expect(err).To(MatchError("unsupported SBOM format: 'random-format'"))
})
})

context("when formatting the SBOM returns an error", func() {
it.Before(func() {
sbomGenerator.GenerateFromDependencyCall.Returns.Error = errors.New("failed to generate SBOM")
})

it("returns an error", func() {
_, err := build(packit.BuildContext{
CNBPath: cnbDir,
BuildpackInfo: packit.BuildpackInfo{
Name: "Some Buildpack",
Version: "1.2.3",
SBOMFormats: []string{sbom.CycloneDXFormat, sbom.SPDXFormat},
},
Plan: packit.BuildpackPlan{
Entries: []packit.BuildpackPlanEntry{
{
Name: "php",
Metadata: map[string]interface{}{
"version": "7.2.*",
"version-source": "some-source",
},
},
},
},
})
Expect(err).To(MatchError(ContainSubstring("failed to generate SBOM")))
})
})

context("when finding PHP extensions fails", func() {
it.Before(func() {
files.FindExtensionsCall.Returns.Error = errors.New("cannot find extensions")
})
Expand All @@ -529,8 +610,8 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(err).To(MatchError(ContainSubstring("cannot find extensions")))
})
})
// TODO: Remove NEW from tests
context("NEW when writing default php.ini fails", func() {

context("when writing default php.ini fails", func() {
it.Before(func() {
files.WriteConfigCall.Returns.Err = errors.New("some config writing error")
})
Expand Down
1 change: 1 addition & 0 deletions buildpack.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ api = "0.7"
id = "paketo-buildpacks/php-dist"
keywords = ["php"]
name = "Paketo PHP Distribution Buildpack"
sbom-formats = ["application/vnd.cyclonedx+json", "application/spdx+json", "application/vnd.syft+json"]

[[buildpack.licenses]]
type = "Apache-2.0"
Expand Down
36 changes: 36 additions & 0 deletions fakes/sbom_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package fakes

import (
"sync"

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

type SBOMGenerator struct {
GenerateFromDependencyCall struct {
mutex sync.Mutex
CallCount int
Receives struct {
Dependency postal.Dependency
Dir string
}
Returns struct {
SBOM sbom.SBOM
Error error
}
Stub func(postal.Dependency, string) (sbom.SBOM, error)
}
}

func (f *SBOMGenerator) GenerateFromDependency(param1 postal.Dependency, param2 string) (sbom.SBOM, error) {
f.GenerateFromDependencyCall.mutex.Lock()
defer f.GenerateFromDependencyCall.mutex.Unlock()
f.GenerateFromDependencyCall.CallCount++
f.GenerateFromDependencyCall.Receives.Dependency = param1
f.GenerateFromDependencyCall.Receives.Dir = param2
if f.GenerateFromDependencyCall.Stub != nil {
return f.GenerateFromDependencyCall.Stub(param1, param2)
}
return f.GenerateFromDependencyCall.Returns.SBOM, f.GenerateFromDependencyCall.Returns.Error
}
76 changes: 75 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/paketo-buildpacks/php-dist

go 1.16
go 1.18

require (
github.com/BurntSushi/toml v1.1.0
Expand All @@ -11,3 +11,77 @@ require (
github.com/sclevine/spec v1.4.0
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/CycloneDX/cyclonedx-go v0.5.0 // indirect
github.com/ForestEckhardt/freezer v0.0.11 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/acobaugh/osrelease v0.1.0 // indirect
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
github.com/anchore/go-rpmdb v0.0.0-20210914181456-a9c52348da63 // indirect
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b // indirect
github.com/anchore/packageurl-go v0.1.1-0.20220314153042-1bcd40e5206b // indirect
github.com/anchore/stereoscope v0.0.0-20220406160859-c03a18a6b270 // indirect
github.com/anchore/syft v0.43.2 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.0.2 // indirect
github.com/containerd/containerd v1.5.10 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect
github.com/docker/cli v20.10.12+incompatible // indirect
github.com/docker/distribution v2.8.0+incompatible // indirect
github.com/docker/docker v20.10.12+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/facebookincubator/nvdtools v0.1.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jinzhu/copier v0.3.2 // indirect
github.com/klauspost/compress v1.14.2 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mholt/archiver/v3 v3.5.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spdx/tools-golang v0.2.0 // indirect
github.com/spf13/afero v1.8.0 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vifraa/gopom v0.1.0 // indirect
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 // indirect
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c // indirect
google.golang.org/grpc v1.44.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)
Loading

0 comments on commit 8684022

Please sign in to comment.