Skip to content

Commit

Permalink
syftCLIScanner: allow generic path to be scanned
Browse files Browse the repository at this point in the history
Do not use the explicit scheme of "dir:" so that even
a file can be scanned.

Example of usage: go-mod-vendor buildpack scans the "go.mod"
file of the app, not the app directory.
  • Loading branch information
arjun024 committed Oct 15, 2024
1 parent 292f752 commit 837f1ba
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 23 deletions.
13 changes: 6 additions & 7 deletions sbomgen/syft_cli_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Executable interface {
Execute(pexec.Execution) (err error)
}

// SyftCLIScanner implements scanning a dir using the `syft` CLI
// SyftCLIScanner implements scanning a path using the `syft` CLI
// to generate SBOM, process it, and write it to a location that complies with
// the buildpacks spec. Supports CycloneDX, SPDX and Syft mediatypes, with an
// optional version param for CycloneDX and Syft.
Expand All @@ -43,13 +43,13 @@ func NewSyftCLIScanner(syftCLI Executable, logger scribe.Emitter) SyftCLIScanner
}
}

// Generate takes a path to a directory to scan and a list of SBOM mediatypes
// (with an optional version for CycloneDX and SPDX), and invokes the syft CLI
// scan command. The CLI is instructed to write the SBOM to
// Generate takes a path to scan and a list of SBOM mediatypes (with an
// optional version for CycloneDX and SPDX), and invokes the syft CLI scan
// command. The CLI is instructed to write the SBOM to
// <layers>/<layer>.sbom.<ext> as defined by the buildpack spec. Additionally,
// CycloneDX & SPDX outputs are modified to make the output reproducible
// (Paketo RFCs 38 & 49).
func (s SyftCLIScanner) GenerateSBOM(scanDir, layersPath, layerName string, mediaTypes ...string) error {
func (s SyftCLIScanner) GenerateSBOM(scanPath, layersPath, layerName string, mediaTypes ...string) error {
sbomWritePaths := make(map[string]string)
args := []string{"scan", "--quiet"}

Expand All @@ -71,12 +71,11 @@ func (s SyftCLIScanner) GenerateSBOM(scanDir, layersPath, layerName string, medi
args = append(args, "--output", fmt.Sprintf("%s=%s", syftOutputFormat, sbomWritePaths[mediatype]))
}

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

s.logger.Debug.Subprocess("Executing syft CLI with args %v", args)
if err := s.syftCLI.Execute(pexec.Execution{
Args: args,
Dir: scanDir,
Stdout: s.logger.ActionWriter,
Stderr: s.logger.ActionWriter,
}); err != nil {
Expand Down
32 changes: 16 additions & 16 deletions sbomgen/syft_cli_scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,22 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
context("syft CLI execution", func() {
context("single mediatype without a version", func() {
it("runs the cli commands to scan and generate SBOM", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name", sbomgen.CycloneDXFormat)
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name", sbomgen.CycloneDXFormat)
Expect(err).NotTo(HaveOccurred())

Expect(executions).To(HaveLen(1))
Expect(executions[0].Args).To(Equal([]string{
"scan",
"--quiet",
"--output", fmt.Sprintf("cyclonedx-json=%s/some-layer-name.sbom.cdx.json", layersDir),
"dir:some-dir",
"some-path",
}))
})
})

context("multiple mediatypes without a version", func() {
it("runs the cli commands to scan and generate SBOM", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name",
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name",
sbomgen.CycloneDXFormat, sbomgen.SPDXFormat, sbomgen.SyftFormat)
Expect(err).NotTo(HaveOccurred())

Expand All @@ -141,14 +141,14 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
"--output", fmt.Sprintf("cyclonedx-json=%s/some-layer-name.sbom.cdx.json", layersDir),
"--output", fmt.Sprintf("spdx-json=%s/some-layer-name.sbom.spdx.json", layersDir),
"--output", fmt.Sprintf("syft-json=%s/some-layer-name.sbom.syft.json", layersDir),
"dir:some-dir",
"some-path",
}))
})
})

context("multiple mediatypes with and without version", func() {
it("runs the cli commands to scan and generate SBOM", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name",
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name",
sbomgen.CycloneDXFormat+";version=1.2.3", sbomgen.SPDXFormat, sbomgen.SyftFormat)
Expect(err).NotTo(HaveOccurred())

Expand All @@ -159,15 +159,15 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
"--output", fmt.Sprintf("cyclonedx-json@1.2.3=%s/some-layer-name.sbom.cdx.json", layersDir),
"--output", fmt.Sprintf("spdx-json=%s/some-layer-name.sbom.spdx.json", layersDir),
"--output", fmt.Sprintf("syft-json=%s/some-layer-name.sbom.syft.json", layersDir),
"dir:some-dir",
"some-path",
}))
})
})
})

context("making CLI CycloneDX output reproducible", func() {
it("removes non-reproducible fields from CycloneDX SBOM", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name", sbomgen.CycloneDXFormat)
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name", sbomgen.CycloneDXFormat)
Expect(err).NotTo(HaveOccurred())

generatedSBOM, err := os.ReadFile(filepath.Join(layersDir, "some-layer-name.sbom.cdx.json"))
Expand Down Expand Up @@ -205,7 +205,7 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
context("making CLI SPDX output reproducible", func() {
context("without setting $SOURCE_DATE_EPOCH", func() {
it("modifies non-reproducible fields from SPDX SBOM", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name", sbomgen.SPDXFormat)
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name", sbomgen.SPDXFormat)
Expect(err).NotTo(HaveOccurred())

generatedSBOM, err := os.ReadFile(filepath.Join(layersDir, "some-layer-name.sbom.spdx.json"))
Expand Down Expand Up @@ -240,7 +240,7 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
})

it("modifies non-reproducible fields from SPDX SBOM", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name", sbomgen.SPDXFormat)
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name", sbomgen.SPDXFormat)
Expect(err).NotTo(HaveOccurred())

generatedSBOM, err := os.ReadFile(filepath.Join(layersDir, "some-layer-name.sbom.spdx.json"))
Expand All @@ -260,21 +260,21 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
context("failure cases", func() {
context("invalid mediatype name", func() {
it("shows an invalid type error", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name", "whatever-mediatype")
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name", "whatever-mediatype")
Expect(err).To(MatchError(ContainSubstring("mediatype whatever-mediatype matched none of the known mediatypes. Valid values are [application/vnd.cyclonedx+json application/spdx+json application/vnd.syft+json], with an optional version param for CycloneDX and SPDX")))
})
})

context("invalid mediatype version format", func() {
it("shows an invalid mediatype version format error", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name", "application/vnd.cyclonedx+json;;foo")
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name", "application/vnd.cyclonedx+json;;foo")
Expect(err).To(MatchError(ContainSubstring("Expected <mediatype>[;version=<ver>], Got application/vnd.cyclonedx+json;;foo")))
})
})

context("syft mediatype contains a version specifier", func() {
it("shows an error", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name",
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name",
sbomgen.CycloneDXFormat, sbomgen.SPDXFormat, sbomgen.SyftFormat+";version=1.2.3")
Expect(err).To(MatchError(ContainSubstring("The syft mediatype does not allow providing a ;version=<ver> param")))
})
Expand All @@ -289,9 +289,9 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
}
})
it("returns an error & writes to logs", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", layersDir, "some-layer-name", sbomgen.CycloneDXFormat+";version=1.2.3", sbomgen.SPDXFormat, sbomgen.SyftFormat)
err := syftCLIScanner.GenerateSBOM("some-path", layersDir, "some-layer-name", sbomgen.CycloneDXFormat+";version=1.2.3", sbomgen.SPDXFormat, sbomgen.SyftFormat)
Expect(err).To(MatchError(ContainSubstring(
fmt.Sprintf("failed to execute syft cli with args '[scan --quiet --output cyclonedx-json@1.2.3=%s/some-layer-name.sbom.cdx.json --output spdx-json=%s/some-layer-name.sbom.spdx.json --output syft-json=%s/some-layer-name.sbom.syft.json dir:some-dir]'", layersDir, layersDir, layersDir))))
fmt.Sprintf("failed to execute syft cli with args '[scan --quiet --output cyclonedx-json@1.2.3=%s/some-layer-name.sbom.cdx.json --output spdx-json=%s/some-layer-name.sbom.spdx.json --output syft-json=%s/some-layer-name.sbom.syft.json some-path]'", layersDir, layersDir, layersDir))))
Expect(err).To(MatchError(ContainSubstring("cli command failed")))
Expect(err).To(MatchError(ContainSubstring("You might be missing a buildpack that provides the syft CLI")))

Expand All @@ -315,7 +315,7 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
})

it("returns helpful error message", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", tmpLayersDir, "some-layer-name", sbomgen.CycloneDXFormat)
err := syftCLIScanner.GenerateSBOM("some-path", tmpLayersDir, "some-layer-name", sbomgen.CycloneDXFormat)
Expect(err).To(MatchError(ContainSubstring("failed to make CycloneDX SBOM reproducible: unable to decode CycloneDX JSON")))
})
})
Expand All @@ -335,7 +335,7 @@ func testSyftCLIScanner(t *testing.T, context spec.G, it spec.S) {
})

it("returns helpful error message", func() {
err := syftCLIScanner.GenerateSBOM("some-dir", tmpLayersDir, "some-layer-name", sbomgen.SPDXFormat)
err := syftCLIScanner.GenerateSBOM("some-path", tmpLayersDir, "some-layer-name", sbomgen.SPDXFormat)
Expect(err).To(MatchError(ContainSubstring("failed to make SPDX SBOM reproducible: unable to decode SPDX JSON")))
})
})
Expand Down

0 comments on commit 837f1ba

Please sign in to comment.