diff --git a/cmd/syft/cli/attest.go b/cmd/syft/cli/attest.go index b1a602d1108..a826977d4b5 100644 --- a/cmd/syft/cli/attest.go +++ b/cmd/syft/cli/attest.go @@ -20,8 +20,7 @@ const ( attestHelp = attestExample + attestSchemeHelp ) -//nolint:dupl -func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command { +func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions, ao *options.AttestOptions) *cobra.Command { cmd := &cobra.Command{ Use: "attest --output [FORMAT] <IMAGE>", Short: "Generate an SBOM as an attestation for the given [SOURCE] container image", @@ -50,11 +49,17 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po }, } - // syft attest is an enhancment of the packages command, so it should have the same flags + // syft attest is an enhancement of the packages command, so it should have the same flags err := po.AddFlags(cmd, v) if err != nil { log.Fatal(err) } + // syft attest has its own options not included as part of the packages command + err = ao.AddFlags(cmd, v) + if err != nil { + log.Fatal(err) + } + return cmd } diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index b2b31db8eb0..d0a1baac099 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -97,6 +97,7 @@ func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, err return sBytes, nil } +//nolint:funlen func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error { errs := make(chan error) go func() { @@ -131,9 +132,18 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <- } args := []string{"attest", si.UserInput, "--type", "custom", "--predicate", f.Name()} + if app.Attest.Key != "" { + args = append(args, "--key", app.Attest.Key) + } + execCmd := exec.Command(cmd, args...) execCmd.Env = os.Environ() - execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1") + 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") + } // bus adapter for ui to hook into stdout via an os pipe r, w, err := os.Pipe() diff --git a/cmd/syft/cli/commands.go b/cmd/syft/cli/commands.go index acc18386c40..cc80b97aa34 100644 --- a/cmd/syft/cli/commands.go +++ b/cmd/syft/cli/commands.go @@ -45,12 +45,13 @@ func New() (*cobra.Command, error) { // we also need the command to have information about the `root` options because of this alias ro := &options.RootOptions{} po := &options.PackagesOptions{} + ao := &options.AttestOptions{} packagesCmd := Packages(v, app, ro, po) // root options are also passed to the attestCmd so that a user provided config location can be discovered poweruserCmd := PowerUser(v, app, ro) convertCmd := Convert(v, app, ro, po) - attestCmd := Attest(v, app, ro, po) + attestCmd := Attest(v, app, ro, po, ao) // rootCmd is currently an alias for the packages command rootCmd := &cobra.Command{ diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go new file mode 100644 index 00000000000..d436bea9506 --- /dev/null +++ b/cmd/syft/cli/options/attest.go @@ -0,0 +1,25 @@ +package options + +import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +type AttestOptions struct { + Key string +} + +var _ Interface = (*AttestOptions)(nil) + +func (o AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { + cmd.Flags().StringVarP(&o.Key, "key", "k", "", "the key to use for the attestation") + return bindAttestConfigOptions(cmd.Flags(), v) +} + +func bindAttestConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { + if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil { + return err + } + return nil +} diff --git a/internal/config/application.go b/internal/config/application.go index 3cf7254bcac..b5a74b6380f 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -47,6 +47,7 @@ type Application struct { Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"` Package pkg `yaml:"package" json:"package" mapstructure:"package"` + Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"` FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"` FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"` FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"` diff --git a/internal/config/attest.go b/internal/config/attest.go new file mode 100644 index 00000000000..f0493d7bfce --- /dev/null +++ b/internal/config/attest.go @@ -0,0 +1,13 @@ +package config + +import "github.com/spf13/viper" + +type attest struct { + Key string `yaml:"key" json:"key" mapstructure:"key"` + Password string `yaml:"password" json:"password" mapstructure:"password"` +} + +func (cfg attest) loadDefaultValues(v *viper.Viper) { + v.SetDefault("attest.key", "") + v.SetDefault("attest.password", "") +} diff --git a/ui/event_handlers.go b/ui/event_handlers.go index 49e5cccd35c..9a015c2163c 100644 --- a/ui/event_handlers.go +++ b/ui/event_handlers.go @@ -618,6 +618,9 @@ func AttestationStartedHandler(ctx context.Context, fr *frame.Frame, event party text := s.Text() if strings.Contains(text, "tlog entry created with index") { tlogEntry = text + } else { + // no tlog entry create so user used personal PKI + tlogEntry = "signed attestation using provided key" } _, err = line.Write([]byte(fmt.Sprintf(" %s %s", auxInfoFormat.Sprintf("░░"), text))) if err != nil {