Skip to content

Commit

Permalink
remove sbom.writer bytes call and add sbom publisher
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
  • Loading branch information
wagoodman committed Jun 23, 2023
1 parent a15a9db commit b743651
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 534 deletions.
211 changes: 99 additions & 112 deletions cmd/syft/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,6 @@ func Run(_ context.Context, app *config.Application, args []string) error {
return err
}

writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
if err != nil {
return fmt.Errorf("unable to write to report destination: %w", err)
}

defer func() {
if err := writer.Close(); err != nil {
fmt.Printf("unable to close report destination: %+v", err)
}
}()

// could be an image or a directory, with or without a scheme
// TODO: validate that source is image
userInput := args[0]
Expand All @@ -63,15 +52,15 @@ func Run(_ context.Context, app *config.Application, args []string) error {
subscription := eventBus.Subscribe()

return eventloop.EventLoop(
execWorker(app, *si, writer),
execWorker(app, *si),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}

func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, errs chan error) ([]byte, error) {
func buildSBOM(app *config.Application, si source.Input, errs chan error) (*sbom.SBOM, error) {
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
if cleanup != nil {
defer cleanup()
Expand All @@ -89,122 +78,120 @@ func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, err
return nil, fmt.Errorf("no SBOM produced for %q", si.UserInput)
}

// note: only works for single format no multi writer support
sBytes, err := writer.Bytes(*s)
if err != nil {
return nil, fmt.Errorf("unable to build SBOM bytes: %w", err)
}

return sBytes, nil
return s, nil
}

//nolint:funlen
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
func execWorker(app *config.Application, si source.Input) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
defer bus.Exit()
defer close(errs)

sBytes, err := buildSBOM(app, si, writer, errs)
s, err := buildSBOM(app, si, errs)
if err != nil {
errs <- fmt.Errorf("unable to build SBOM: %w", err)
return
}

// TODO: add multi writer support
for _, o := range app.Outputs {
f, err := os.CreateTemp("", o)
if err != nil {
errs <- fmt.Errorf("unable to create temp file: %w", err)
return
}

defer f.Close()
defer os.Remove(f.Name())

if _, err := f.Write(sBytes); err != nil {
errs <- fmt.Errorf("unable to write SBOM to temp file: %w", err)
return
}

// TODO: what other validation here besides binary name?
cmd := "cosign"
if !commandExists(cmd) {
errs <- fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
return
}

// Select Cosign predicate type based on defined output type
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
var predicateType string
switch strings.ToLower(o) {
case "cyclonedx-json":
predicateType = "cyclonedx"
case "spdx-tag-value":
predicateType = "spdx"
case "spdx-json":
predicateType = "spdxjson"
default:
predicateType = "custom"
}

args := []string{"attest", si.UserInput, "--predicate", f.Name(), "--type", predicateType}
if app.Attest.Key != "" {
args = append(args, "--key", app.Attest.Key)
}

execCmd := exec.Command(cmd, args...)
execCmd.Env = os.Environ()
if app.Attest.Key != "" {
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
} else {
// no key provided, use cosign's keyless mode
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
}

log.WithFields("cmd", strings.Join(execCmd.Args, " ")).Trace("creating attestation")

// bus adapter for ui to hook into stdout via an os pipe
r, w, err := os.Pipe()
if err != nil {
errs <- fmt.Errorf("unable to create os pipe: %w", err)
return
}
defer w.Close()

mon := progress.NewManual(-1)

bus.Publish(
partybus.Event{
Type: event.AttestationStarted,
Source: monitor.GenericTask{
Title: monitor.Title{
Default: "Create attestation",
WhileRunning: "Creating attestation",
OnSuccess: "Created attestation",
},
Context: "cosign",
},
Value: &monitor.ShellProgress{
Reader: r,
Progressable: mon,
// note: ValidateOutputOptions ensures that there is no more than one output type
o := app.Outputs[0]

f, err := os.CreateTemp("", o)
if err != nil {
errs <- fmt.Errorf("unable to create temp file: %w", err)
return
}
defer os.Remove(f.Name())

writer, err := options.MakeWriter(app.Outputs, f.Name(), app.OutputTemplatePath)
if err != nil {
errs <- fmt.Errorf("unable to create SBOM writer: %w", err)
return
}

if err := writer.Write(*s); err != nil {
errs <- fmt.Errorf("unable to write SBOM to temp file: %w", err)
return
}

// TODO: what other validation here besides binary name?
cmd := "cosign"
if !commandExists(cmd) {
errs <- fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
return
}

// Select Cosign predicate type based on defined output type
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
var predicateType string
switch strings.ToLower(o) {
case "cyclonedx-json":
predicateType = "cyclonedx"
case "spdx-tag-value", "spdx-tv":
predicateType = "spdx"
case "spdx-json", "json":
predicateType = "spdxjson"
default:
predicateType = "custom"
}

args := []string{"attest", si.UserInput, "--predicate", f.Name(), "--type", predicateType}
if app.Attest.Key != "" {
args = append(args, "--key", app.Attest.Key)
}

execCmd := exec.Command(cmd, args...)
execCmd.Env = os.Environ()
if app.Attest.Key != "" {
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
} else {
// no key provided, use cosign's keyless mode
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
}

log.WithFields("cmd", strings.Join(execCmd.Args, " ")).Trace("creating attestation")

// bus adapter for ui to hook into stdout via an os pipe
r, w, err := os.Pipe()
if err != nil {
errs <- fmt.Errorf("unable to create os pipe: %w", err)
return
}
defer w.Close()

mon := progress.NewManual(-1)

bus.Publish(
partybus.Event{
Type: event.AttestationStarted,
Source: monitor.GenericTask{
Title: monitor.Title{
Default: "Create attestation",
WhileRunning: "Creating attestation",
OnSuccess: "Created attestation",
},
Context: "cosign",
},
)

execCmd.Stdout = w
execCmd.Stderr = w
Value: &monitor.ShellProgress{
Reader: r,
Progressable: mon,
},
},
)

// attest the SBOM
err = execCmd.Run()
if err != nil {
mon.SetError(err)
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
return
}
execCmd.Stdout = w
execCmd.Stderr = w

mon.SetCompleted()
// attest the SBOM
err = execCmd.Run()
if err != nil {
mon.SetError(err)
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
return
}

mon.SetCompleted()
}()
return errs
}
Expand Down
6 changes: 0 additions & 6 deletions cmd/syft/cli/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ func Run(_ context.Context, app *config.Application, args []string) error {
return err
}

defer func() {
if err := writer.Close(); err != nil {
log.Warnf("unable to write to report destination: %w", err)
}
}()

// this can only be a SBOM file
userInput := args[0]

Expand Down
Loading

0 comments on commit b743651

Please sign in to comment.