Skip to content

Commit

Permalink
Merge pull request #143 from paketo-buildpacks/sbom_reprod
Browse files Browse the repository at this point in the history
Removes `metadata.timestamp` and `serialNumber` fields from generates CycloneDX JSON
  • Loading branch information
Daniel Mikusa authored May 6, 2022
2 parents d82231c + 9395235 commit a529d07
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 8 deletions.
60 changes: 58 additions & 2 deletions sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/buildpacks/libcnb"
"github.com/mitchellh/hashstructure/v2"
Expand Down Expand Up @@ -146,12 +147,67 @@ func (b SyftCLISBOMScanner) scan(sbomPathCreator func(libcnb.SBOMFormat) string,

args = append(args, fmt.Sprintf("dir:%s", scanDir))

return b.Executor.Execute(effect.Execution{
if err := b.Executor.Execute(effect.Execution{
Command: "syft",
Args: args,
Stdout: b.Logger.TerminalErrorWriter(),
Stderr: b.Logger.TerminalErrorWriter(),
})
}); err != nil {
return fmt.Errorf("unable to run `syft %s`\n%w", args, err)
}

// cleans cyclonedx file which has a timestamp and unique id which always change
for _, format := range formats {
if format == libcnb.CycloneDXJSON {
if err := b.makeCycloneDXReproducible(sbomPathCreator(format)); err != nil {
return fmt.Errorf("unable to make cyclone dx file reproducible\n%w", err)
}
}
}

return nil
}

func (b SyftCLISBOMScanner) makeCycloneDXReproducible(path string) error {
input, err := loadCycloneDXFile(path)
if err != nil {
return err
}

delete(input, "serialNumber")

if md, exists := input["metadata"]; exists {
if metadata, ok := md.(map[string]interface{}); ok {
delete(metadata, "timestamp")
}
}

out, err := os.Create(path)
if err != nil {
return fmt.Errorf("unable to open CycloneDX JSON for writing %s\n%w", path, err)
}
defer out.Close()

if err := json.NewEncoder(out).Encode(input); err != nil {
return fmt.Errorf("unable to encode CycloneDX\n%w", err)
}

return nil
}

func loadCycloneDXFile(path string) (map[string]interface{}, error) {
in, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("unable to read CycloneDX JSON file %s\n%w", path, err)
}
defer in.Close()

raw := map[string]interface{}{}
if err := json.NewDecoder(in).Decode(&raw); err != nil {
return nil, fmt.Errorf("unable to decode CycloneDX JSON %s\n%w", path, err)
}

return raw, nil
}

// SBOMFormatToSyftOutputFormat converts a libcnb.SBOMFormat to the syft matching syft output format string
Expand Down
58 changes: 52 additions & 6 deletions sbom/sbom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,52 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
Expect(string(result)).To(Equal("succeed1"))
})

it("runs syft to generate reproducible cycloneDX JSON", func() {
format := libcnb.CycloneDXJSON
outputPath := layers.BuildSBOMPath(format)

executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
return e.Command == "syft" &&
len(e.Args) == 5 &&
strings.HasPrefix(e.Args[3], "cyclonedx-json=") &&
e.Args[4] == "dir:something"
})).Run(func(args mock.Arguments) {
Expect(ioutil.WriteFile(outputPath, []byte(`{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:fcfa5e19-bf49-47b4-8c85-ab61e2728f8e",
"version": 1,
"metadata": {
"timestamp": "2022-05-05T11:33:13-04:00",
"tools": [
{
"vendor": "anchore",
"name": "syft",
"version": "0.45.1"
}
],
"component": {
"bom-ref": "555d623e4777b7ae",
"type": "file",
"name": "target/demo-0.0.1-SNAPSHOT.jar"
}
}
}`), 0644)).To(Succeed())
}).Return(nil)

// uses interface here intentionally, to force that inteface and implementation match
scanner = sbom.NewSyftCLISBOMScanner(layers, &executor, bard.NewLogger(io.Discard))

Expect(scanner.ScanBuild("something", format)).To(Succeed())

result, err := ioutil.ReadFile(outputPath)
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).ToNot(ContainSubstring("serialNumber"))
Expect(string(result)).ToNot(ContainSubstring("urn:uuid:fcfa5e19-bf49-47b4-8c85-ab61e2728f8e"))
Expect(string(result)).ToNot(ContainSubstring("timestamp"))
Expect(string(result)).ToNot(ContainSubstring("2022-05-05T11:33:13-04:00"))
})

it("runs syft once to generate layer-specific JSON", func() {
format := libcnb.SyftJSON
outputPath := layer.SBOMPath(format)
Expand Down Expand Up @@ -114,9 +160,9 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
strings.HasPrefix(e.Args[7], sbom.SBOMFormatToSyftOutputFormat(libcnb.SPDXJSON)) &&
e.Args[8] == "dir:something"
})).Run(func(args mock.Arguments) {
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.CycloneDXJSON), []byte("succeed1"), 0644)).To(Succeed())
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.SyftJSON), []byte("succeed2"), 0644)).To(Succeed())
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.SPDXJSON), []byte("succeed3"), 0644)).To(Succeed())
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.CycloneDXJSON), []byte(`{"succeed":1}`), 0644)).To(Succeed())
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.SyftJSON), []byte(`{"succeed":2}`), 0644)).To(Succeed())
Expect(ioutil.WriteFile(layers.LaunchSBOMPath(libcnb.SPDXJSON), []byte(`{"succeed":3}`), 0644)).To(Succeed())
}).Return(nil)

scanner := sbom.SyftCLISBOMScanner{
Expand All @@ -129,15 +175,15 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {

result, err := ioutil.ReadFile(layers.LaunchSBOMPath(libcnb.CycloneDXJSON))
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).To(Equal("succeed1"))
Expect(string(result)).To(HavePrefix(`{"succeed":1}`))

result, err = ioutil.ReadFile(layers.LaunchSBOMPath(libcnb.SyftJSON))
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).To(Equal("succeed2"))
Expect(string(result)).To(HavePrefix(`{"succeed":2}`))

result, err = ioutil.ReadFile(layers.LaunchSBOMPath(libcnb.SPDXJSON))
Expect(err).ToNot(HaveOccurred())
Expect(string(result)).To(Equal("succeed3"))
Expect(string(result)).To(HavePrefix(`{"succeed":3}`))
})

it("writes out a manual BOM entry", func() {
Expand Down

0 comments on commit a529d07

Please sign in to comment.