Skip to content

Commit

Permalink
Remove unnecessary coversion from XML > JSON for CycloneDx + syft
Browse files Browse the repository at this point in the history
Signed-off-by: Sambhav Kothari <[email protected]>
  • Loading branch information
sambhav committed Dec 14, 2021
1 parent bef1d87 commit dc7078f
Show file tree
Hide file tree
Showing 4 changed files with 8 additions and 184 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/paketo-buildpacks/libpak
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.25.2
github.com/creack/pty v1.1.17
Expand All @@ -14,6 +13,7 @@ require (
github.com/pelletier/go-toml v1.9.4
github.com/sclevine/spec v1.4.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.7.0
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
)
5 changes: 0 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/CycloneDX/cyclonedx-go v0.4.0 h1:Wz4QZ9B4RXGWIWTypVLEOVJgOdFfy5mcS5PGNzUkZxU=
github.com/CycloneDX/cyclonedx-go v0.4.0/go.mod h1:rmRcf//gT7PIzovatusbWi377xqCg1FS4jyST0GH20E=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
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.25.2 h1:gjP4e1ns7GYXAuxb58hSDhlK9ksbzG0ErpBJJmp2gDA=
github.com/buildpacks/libcnb v1.25.2/go.mod h1:XX0+zHW8CNLNwiiwowgydAgWWfyDt8Lj1NcuWtkkBJQ=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
Expand Down Expand Up @@ -66,7 +62,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
Expand Down
83 changes: 2 additions & 81 deletions sbom/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package sbom
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/CycloneDX/cyclonedx-go"
"github.com/buildpacks/libcnb"
"github.com/mitchellh/hashstructure/v2"
"github.com/paketo-buildpacks/libpak/bard"
Expand Down Expand Up @@ -38,7 +36,7 @@ func NewSyftDependency(dependencyPath string, artifacts []SyftArtifact) SyftDepe
},
Descriptor: SyftDescriptor{
Name: "syft",
Version: "0.30.1",
Version: "0.32.0",
},
Schema: SyftSchema{
Version: "1.1.0",
Expand Down Expand Up @@ -150,88 +148,11 @@ func (b SyftCLISBOMScanner) scan(sbomPathCreator func(libcnb.SBOMFormat) string,
if err := b.runSyft(sbomLocation, scanDir, format); err != nil {
return fmt.Errorf("unable to run syft\n%w", err)
}

if format == libcnb.CycloneDXJSON {
// syft doesn't presently support cyclonedx JSON output and we need to convert
// until https://github.com/anchore/syft/issues/631 is addressed
if err := b.ConvertCycloneDXXMLtoJSON(sbomLocation, false); err != nil {
return fmt.Errorf("unable convert XML to JSON\n%w", err)
}
}
}

return nil
}

// ConvertCycloneDXXMLtoJSON reads input CycloneDX XML, converts to JSON and overwrites the XML optionally keeping a backup copy of the xml
func (b SyftCLISBOMScanner) ConvertCycloneDXXMLtoJSON(inputPath string, backup bool) error {
if backup {
if err := b.backupXMLFile(inputPath); err != nil {
return fmt.Errorf("unable to backup file\n%w", err)
}
}

bom, err := b.readXMLSBOM(inputPath)
if err != nil {
return fmt.Errorf("unable to read XML file for conversion\n%w", err)
}

if err := b.writeJSONSBOM(inputPath, bom); err != nil {
return fmt.Errorf("unable to write converted JSON BOM file\n%w", err)
}

return nil
}

func (b SyftCLISBOMScanner) writeJSONSBOM(outputPath string, bom cyclonedx.BOM) error {
outputFile, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("unable to create BOM file %s\n%w", outputPath, err)
}
defer outputFile.Close()

decoder := cyclonedx.NewBOMEncoder(outputFile, cyclonedx.BOMFileFormatJSON)
if err = decoder.Encode(&bom); err != nil {
return fmt.Errorf("unable to decode BOM\n%w", err)
}

return nil
}

func (b SyftCLISBOMScanner) readXMLSBOM(inputPath string) (cyclonedx.BOM, error) {
inputFile, err := os.Open(inputPath)
if err != nil {
return cyclonedx.BOM{}, fmt.Errorf("unable to read file to convert %s\n%w", inputPath, err)
}
defer inputFile.Close()

var bom cyclonedx.BOM
decoder := cyclonedx.NewBOMDecoder(inputFile, cyclonedx.BOMFileFormatXML)
if err = decoder.Decode(&bom); err != nil {
return cyclonedx.BOM{}, fmt.Errorf("unable to decode BOM\n%w", err)
}

return bom, nil
}

func (b SyftCLISBOMScanner) backupXMLFile(inputPath string) error {
backupPath := fmt.Sprintf("%s.bak", inputPath)
outputFile, err := os.Create(backupPath)
if err != nil {
return fmt.Errorf("unable to create backup file %s\n%w", backupPath, err)
}
defer outputFile.Close()

inputFile, err := os.Open(inputPath)
if err != nil {
return fmt.Errorf("unable to read file for backup %s\n%w", inputPath, err)
}
defer inputFile.Close()

_, err = io.Copy(outputFile, inputFile)
return err
}

func (b SyftCLISBOMScanner) runSyft(sbomOutputPath string, scanDir string, format libcnb.SBOMFormat) error {
writer, err := os.Create(sbomOutputPath)
if err != nil {
Expand All @@ -258,7 +179,7 @@ func SBOMFormatToSyftOutputFormat(format libcnb.SBOMFormat) string {

switch format {
case libcnb.CycloneDXJSON:
formatRaw = "cyclonedx"
formatRaw = "cyclonedx-json"
case libcnb.SPDXJSON:
formatRaw = "spdx-json"
case libcnb.SyftJSON:
Expand Down
102 changes: 5 additions & 97 deletions sbom/sbom_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package sbom_test

import (
"fmt"
"io"
"io/ioutil"
"os"
Expand Down Expand Up @@ -105,10 +104,11 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
Expect(string(result)).To(Equal("succeed2"))
})

it("runs syft twice, once per format", func() {
it("runs syft thrice, once per format", func() {
outputPaths := map[libcnb.SBOMFormat]string{
libcnb.SPDXJSON: layers.LaunchSBOMPath(libcnb.SPDXJSON),
libcnb.SyftJSON: layers.LaunchSBOMPath(libcnb.SyftJSON),
libcnb.SPDXJSON: layers.LaunchSBOMPath(libcnb.SPDXJSON),
libcnb.SyftJSON: layers.LaunchSBOMPath(libcnb.SyftJSON),
libcnb.CycloneDXJSON: layers.LaunchSBOMPath(libcnb.CycloneDXJSON),
}

for format, outputPath := range outputPaths {
Expand All @@ -135,98 +135,6 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
}
})

it("converts between cyclonedx XML and JSON", func() {
outputPath := layers.BuildSBOMPath(libcnb.CycloneDXJSON)
Expect(ioutil.WriteFile(outputPath, []byte(`<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:48051e17-8720-4503-a2ef-47efab3fc03f">
<metadata>
<timestamp>2021-11-15T16:15:46-05:00</timestamp>
<tools>
<tool>
<vendor>anchore</vendor>
<name>syft</name>
<version>0.29.0</version>
</tool>
</tools>
<component type="file">
<name>.</name>
<version></version>
</component>
</metadata>
<components>
<component type="library">
<name>github.com/BurntSushi/toml</name>
<version>v0.4.1</version>
<purl>pkg:golang/github.com/BurntSushi/[email protected]</purl>
</component>
</components>
</bom>`), 0644))

scanner := sbom.SyftCLISBOMScanner{
Executor: &executor,
Layers: layers,
Logger: bard.NewLogger(io.Discard),
}

Expect(scanner.ConvertCycloneDXXMLtoJSON(outputPath, false)).To(Succeed())

Expect(outputPath).To(BeARegularFile())
Expect(fmt.Sprintf("%s.bak", outputPath)).ToNot(BeARegularFile())

input, err := ioutil.ReadFile(outputPath)
Expect(err).ToNot(HaveOccurred())
Expect(string(input)).To(ContainSubstring(`{"type":"library","name":"github.com/BurntSushi/toml","version":"v0.4.1","purl":"pkg:golang/github.com/BurntSushi/[email protected]"}`))
})

it("converts between cyclonedx XML and JSON with backup", func() {
outputPath := layers.LaunchSBOMPath(libcnb.CycloneDXJSON)
Expect(ioutil.WriteFile(outputPath, []byte(`<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:48051e17-8720-4503-a2ef-47efab3fc03f">
<metadata>
<timestamp>2021-11-15T16:15:46-05:00</timestamp>
<tools>
<tool>
<vendor>anchore</vendor>
<name>syft</name>
<version>0.29.0</version>
</tool>
</tools>
<component type="file">
<name>.</name>
<version></version>
</component>
</metadata>
<components>
<component type="library">
<name>github.com/BurntSushi/toml</name>
<version>v0.4.1</version>
<purl>pkg:golang/github.com/BurntSushi/[email protected]</purl>
</component>
</components>
</bom>`), 0644))

scanner := sbom.SyftCLISBOMScanner{
Executor: &executor,
Layers: layers,
Logger: bard.NewLogger(io.Discard),
}

Expect(scanner.ConvertCycloneDXXMLtoJSON(outputPath, true)).To(Succeed())

Expect(outputPath).To(BeARegularFile())

input, err := ioutil.ReadFile(outputPath)
Expect(err).ToNot(HaveOccurred())
Expect(string(input)).To(ContainSubstring(`{"type":"library","name":"github.com/BurntSushi/toml","version":"v0.4.1","purl":"pkg:golang/github.com/BurntSushi/[email protected]"}`))

outputPath = fmt.Sprintf("%s.bak", outputPath)
Expect(outputPath).To(BeARegularFile())

input, err = ioutil.ReadFile(outputPath)
Expect(err).ToNot(HaveOccurred())
Expect(string(input)).To(ContainSubstring(`<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:48051e17-8720-4503-a2ef-47efab3fc03f">`))
})

it("writes out a manual BOM entry", func() {
dep := sbom.SyftDependency{
Artifacts: []sbom.SyftArtifact{
Expand All @@ -253,7 +161,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
},
Descriptor: sbom.SyftDescriptor{
Name: "syft",
Version: "0.30.1",
Version: "0.32.0",
},
Schema: sbom.SyftSchema{
Version: "1.1.0",
Expand Down

0 comments on commit dc7078f

Please sign in to comment.