diff --git a/.github/workflows/oss-project-board-add.yaml b/.github/workflows/oss-project-board-add.yaml new file mode 100644 index 0000000..b0d1fca --- /dev/null +++ b/.github/workflows/oss-project-board-add.yaml @@ -0,0 +1,16 @@ +name: Add to OSS board + +on: + issues: + types: + - opened + - reopened + - transferred + - labeled + +jobs: + + run: + uses: "anchore/workflows/.github/workflows/oss-project-board-add.yaml@main" + secrets: + token: ${{ secrets.OSS_PROJECT_GH_TOKEN }} diff --git a/cmd/quill/cli/cli.go b/cmd/quill/cli/cli.go index 0640d06..73c8277 100644 --- a/cmd/quill/cli/cli.go +++ b/cmd/quill/cli/cli.go @@ -6,33 +6,20 @@ import ( "github.com/spf13/cobra" "github.com/anchore/clio" - "github.com/anchore/go-logger" "github.com/anchore/quill/cmd/quill/cli/commands" "github.com/anchore/quill/cmd/quill/internal/ui" - "github.com/anchore/quill/internal" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/internal/redact" ) -func New(version clio.Version) *cobra.Command { - clioCfg := clio.NewConfig(internal.ApplicationName, version.Version). - WithLoggingConfig( - clio.LoggingConfig{ - Level: logger.WarnLevel, - }, - ). - WithLoggerConstructor( - func(config clio.Config) (logger.Logger, error) { - l, err := clio.DefaultLogger(config) - if err != nil { - return nil, err - } - // immediately set the logger to account for redactions from any configurations - log.Set(l) - return log.Get(), nil - }, - ). +func New(id clio.Identification) *cobra.Command { + clioCfg := clio.NewSetupConfig(id). + WithGlobalConfigFlag(). // add persistent -c for reading an application config from + WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config + WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text WithUIConstructor( + // select a UI based on the logging configuration and state of stdin (if stdin is a tty) func(cfg clio.Config) ([]clio.UI, error) { noUI := ui.None() if !cfg.Log.AllowUI(os.Stdin) { @@ -46,15 +33,21 @@ func New(version clio.Version) *cobra.Command { }, ). WithInitializers( - func(cfg clio.Config, state clio.State) error { + func(state *clio.State) error { + // clio is setting up and providing the bus, redact store, and logger to the application. Once loaded, + // we can hoist them into the internal packages for global use. + bus.Set(state.Bus) + redact.Set(state.RedactStore) + log.Set(state.Logger) + return nil }, ) app := clio.New(*clioCfg) - root := commands.Root(clioCfg, app) + root := commands.Root(app) submission := commands.Submission(app) submission.AddCommand(commands.SubmissionList(app)) @@ -68,7 +61,7 @@ func New(version clio.Version) *cobra.Command { p12.AddCommand(commands.P12AttachChain(app)) p12.AddCommand(commands.P12Describe(app)) - root.AddCommand(clio.VersionCommand(app, version)) + root.AddCommand(clio.VersionCommand(id)) root.AddCommand(commands.Sign(app)) root.AddCommand(commands.Notarize(app)) root.AddCommand(commands.SignAndNotarize(app)) @@ -78,9 +71,5 @@ func New(version clio.Version) *cobra.Command { root.AddCommand(extract) root.AddCommand(p12) - // root.Example is set _after all added commands_ because it collects all the - // options structs in order to output an accurate "config file" summary - root.Example = app.SummarizeConfig(root) - return root } diff --git a/cmd/quill/cli/commands/describe.go b/cmd/quill/cli/commands/describe.go index 8683436..dce8b27 100644 --- a/cmd/quill/cli/commands/describe.go +++ b/cmd/quill/cli/commands/describe.go @@ -42,26 +42,26 @@ func Describe(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - var err error - buf := &strings.Builder{} - switch strings.ToLower(opts.Output) { - case "text": - err = extract.ShowText(opts.Path, buf, !opts.Detail) - case "json": - err = extract.ShowJSON(opts.Path, buf) - default: - err = fmt.Errorf("unknown format: %s", opts.Output) - } + defer bus.Exit() - if err != nil { - return err - } + var err error + buf := &strings.Builder{} + switch strings.ToLower(opts.Output) { + case "text": + err = extract.ShowText(opts.Path, buf, !opts.Detail) + case "json": + err = extract.ShowJSON(opts.Path, buf) + default: + err = fmt.Errorf("unknown format: %s", opts.Output) + } - bus.Report(buf.String()) + if err != nil { + return err + } - return nil - })) + bus.Report(buf.String()) + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/embedded_certificates.go b/cmd/quill/cli/commands/embedded_certificates.go index 9557282..356777b 100644 --- a/cmd/quill/cli/commands/embedded_certificates.go +++ b/cmd/quill/cli/commands/embedded_certificates.go @@ -21,20 +21,20 @@ func EmbeddedCerts(app clio.Application) *cobra.Command { Short: "show the certificates embedded into quill (typically the Apple root and intermediate certs)", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - var err error - buf := &strings.Builder{} + defer bus.Exit() - err = showAppleCerts(buf) + var err error + buf := &strings.Builder{} - if err != nil { - return err - } + err = showAppleCerts(buf) - bus.Report(buf.String()) + if err != nil { + return err + } - return nil - })) + bus.Report(buf.String()) + + return nil }, }) } diff --git a/cmd/quill/cli/commands/extract_certificates.go b/cmd/quill/cli/commands/extract_certificates.go index fdb063d..cea7906 100644 --- a/cmd/quill/cli/commands/extract_certificates.go +++ b/cmd/quill/cli/commands/extract_certificates.go @@ -41,17 +41,17 @@ func ExtractCertificates(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - certs, err := extractCertificates(opts.Path, opts.Leaf) - if err != nil { - return err - } + defer bus.Exit() - bus.Report(certs) - bus.Notify("Try running 'openssl x509 -text -in .pem' to view the certificate details") + certs, err := extractCertificates(opts.Path, opts.Leaf) + if err != nil { + return err + } - return nil - })) + bus.Report(certs) + bus.Notify("Try running 'openssl x509 -text -in .pem' to view the certificate details") + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/notarize.go b/cmd/quill/cli/commands/notarize.go index b50937c..55b73b3 100644 --- a/cmd/quill/cli/commands/notarize.go +++ b/cmd/quill/cli/commands/notarize.go @@ -8,6 +8,7 @@ import ( "github.com/anchore/clio" "github.com/anchore/fangs" "github.com/anchore/quill/cmd/quill/cli/options" + "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" "github.com/anchore/quill/quill" "github.com/anchore/quill/quill/notary" @@ -47,16 +48,16 @@ func Notarize(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - // TODO: verify path is a signed darwin binary - // ... however, we may want to allow notarization of other kinds of assets (zip with darwin binary, etc) - if opts.DryRun { - log.Warn("[DRY RUN] skipping notarization...") - return nil - } - _, err := notarize(opts.Path, opts.Notary, opts.Status) - return err - })) + defer bus.Exit() + + // TODO: verify path is a signed darwin binary + // ... however, we may want to allow notarization of other kinds of assets (zip with darwin binary, etc) + if opts.DryRun { + log.Warn("[DRY RUN] skipping notarization...") + return nil + } + _, err := notarize(opts.Path, opts.Notary, opts.Status) + return err }, }, opts) } diff --git a/cmd/quill/cli/commands/p12_attach_chain.go b/cmd/quill/cli/commands/p12_attach_chain.go index deaeee7..dc0851d 100644 --- a/cmd/quill/cli/commands/p12_attach_chain.go +++ b/cmd/quill/cli/commands/p12_attach_chain.go @@ -54,22 +54,22 @@ func P12AttachChain(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - newFilename, err := writeP12WithChain(opts.Path, opts.P12.Password, opts.Keychain.Path, true) - if err != nil { - return fmt.Errorf("unable to write new p12 with chain attached file=%q : %w", opts.Path, err) - } + defer bus.Exit() - description, err := describeP12(newFilename, opts.P12.Password) - if err != nil { - return fmt.Errorf("unable to describe p12 file=%q : %w", newFilename, err) - } + newFilename, err := writeP12WithChain(opts.Path, opts.P12.Password, opts.Keychain.Path, true) + if err != nil { + return fmt.Errorf("unable to write new p12 with chain attached file=%q : %w", opts.Path, err) + } - bus.Report(description) - bus.Notify(fmt.Sprintf("Wrote new p12 file with certificate chain to %q", newFilename)) + description, err := describeP12(newFilename, opts.P12.Password) + if err != nil { + return fmt.Errorf("unable to describe p12 file=%q : %w", newFilename, err) + } - return nil - })) + bus.Report(description) + bus.Notify(fmt.Sprintf("Wrote new p12 file with certificate chain to %q", newFilename)) + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/p12_describe.go b/cmd/quill/cli/commands/p12_describe.go index 40ac2c6..cd00b00 100644 --- a/cmd/quill/cli/commands/p12_describe.go +++ b/cmd/quill/cli/commands/p12_describe.go @@ -37,16 +37,16 @@ func P12Describe(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - description, err := describeP12(opts.Path, opts.Password) - if err != nil { - return err - } + defer bus.Exit() - bus.Report(description) + description, err := describeP12(opts.Path, opts.Password) + if err != nil { + return err + } - return nil - })) + bus.Report(description) + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/root.go b/cmd/quill/cli/commands/root.go index 5a932d9..f190c87 100644 --- a/cmd/quill/cli/commands/root.go +++ b/cmd/quill/cli/commands/root.go @@ -1,50 +1,11 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/anchore/clio" - "github.com/anchore/quill/internal" ) -func Root(cfg *clio.Config, app clio.Application) *cobra.Command { - cmd := &cobra.Command{ - Use: "", - Version: app.Config().Version, - } - - cmd.SetUsageTemplate(helpUsageTemplate) - cmd.SetHelpTemplate(helpUsageTemplate) - - cmd.SetVersionTemplate(fmt.Sprintf("%s {{.Version}}\n", internal.ApplicationName)) - - return app.SetupPersistentCommand(cmd, cfg) +func Root(app clio.Application) *cobra.Command { + return app.SetupRootCommand(&cobra.Command{}) } - -var helpUsageTemplate = fmt.Sprintf(`{{if (or .Long .Short)}}{{.Long}}{{if not .Long}}{{.Short}}{{end}} - -{{end}}Usage:{{if (and .Runnable (ne .CommandPath "%s"))}} - {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} - {{.CommandPath}} [command]{{end}}{{if .HasExample}} - -{{.Example}}{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}} - -Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} - -{{if not .CommandPath}}Global {{end}}Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if (and .HasAvailableInheritedFlags (not .CommandPath))}} - -Global Flags: -{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} - -Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} - -Use "{{if .CommandPath}}{{.CommandPath}} {{end}}[command] --help" for more information about a command.{{end}} -`, internal.ApplicationName) diff --git a/cmd/quill/cli/commands/sign.go b/cmd/quill/cli/commands/sign.go index 2eb29e6..3f7f0b7 100644 --- a/cmd/quill/cli/commands/sign.go +++ b/cmd/quill/cli/commands/sign.go @@ -7,6 +7,7 @@ import ( "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli/options" + "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" "github.com/anchore/quill/quill" ) @@ -37,9 +38,9 @@ func Sign(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - return sign(opts.Path, opts.Signing) - })) + defer bus.Exit() + + return sign(opts.Path, opts.Signing) }, }, opts) } diff --git a/cmd/quill/cli/commands/sign_and_notarize.go b/cmd/quill/cli/commands/sign_and_notarize.go index 46a8807..822ab20 100644 --- a/cmd/quill/cli/commands/sign_and_notarize.go +++ b/cmd/quill/cli/commands/sign_and_notarize.go @@ -8,6 +8,7 @@ import ( "github.com/anchore/clio" "github.com/anchore/fangs" "github.com/anchore/quill/cmd/quill/cli/options" + "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" ) @@ -47,24 +48,24 @@ func SignAndNotarize(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - err := sign(opts.Path, opts.Signing) - if err != nil { - return fmt.Errorf("signing failed: %w", err) - } + defer bus.Exit() - if opts.DryRun { - log.Warn("[DRY RUN] skipping notarization...") - return nil - } - - _, err = notarize(opts.Path, opts.Notary, opts.Status) - if err != nil { - return fmt.Errorf("notarization failed: %w", err) - } + err := sign(opts.Path, opts.Signing) + if err != nil { + return fmt.Errorf("signing failed: %w", err) + } + if opts.DryRun { + log.Warn("[DRY RUN] skipping notarization...") return nil - })) + } + + _, err = notarize(opts.Path, opts.Notary, opts.Status) + if err != nil { + return fmt.Errorf("notarization failed: %w", err) + } + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/submission_list.go b/cmd/quill/cli/commands/submission_list.go index b4285f8..439d2e2 100644 --- a/cmd/quill/cli/commands/submission_list.go +++ b/cmd/quill/cli/commands/submission_list.go @@ -26,44 +26,44 @@ func SubmissionList(app clio.Application) *cobra.Command { Short: "list previous submissions to Apple's Notary service", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - log.Info("fetching previous submissions") + defer bus.Exit() - cfg := quill.NewNotarizeConfig( - opts.Notary.Issuer, - opts.Notary.PrivateKeyID, - opts.Notary.PrivateKey, - ) + log.Info("fetching previous submissions") - token, err := notary.NewSignedToken(cfg.TokenConfig) - if err != nil { - return err - } + cfg := quill.NewNotarizeConfig( + opts.Notary.Issuer, + opts.Notary.PrivateKeyID, + opts.Notary.PrivateKey, + ) - a := notary.NewAPIClient(token, cfg.HTTPTimeout) + token, err := notary.NewSignedToken(cfg.TokenConfig) + if err != nil { + return err + } - sub := notary.ExistingSubmission(a, "") + a := notary.NewAPIClient(token, cfg.HTTPTimeout) - submissions, err := sub.List(context.Background()) - if err != nil { - return err - } + sub := notary.ExistingSubmission(a, "") - // show list report + submissions, err := sub.List(context.Background()) + if err != nil { + return err + } - t := table.NewWriter() - t.SetStyle(table.StyleLight) + // show list report - t.AppendHeader(table.Row{"ID", "Name", "Status", "Created"}) + t := table.NewWriter() + t.SetStyle(table.StyleLight) - for _, item := range submissions { - t.AppendRow(table.Row{item.ID, item.Name, item.Status, item.CreatedDate}) - } + t.AppendHeader(table.Row{"ID", "Name", "Status", "Created"}) - bus.Report(t.Render()) + for _, item := range submissions { + t.AppendRow(table.Row{item.ID, item.Name, item.Status, item.CreatedDate}) + } - return nil - })) + bus.Report(t.Render()) + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/submission_logs.go b/cmd/quill/cli/commands/submission_logs.go index 0fb759e..cbc06c5 100644 --- a/cmd/quill/cli/commands/submission_logs.go +++ b/cmd/quill/cli/commands/submission_logs.go @@ -35,33 +35,33 @@ func SubmissionLogs(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - log.Infof("fetching submission logs for %q", opts.ID) + defer bus.Exit() - cfg := quill.NewNotarizeConfig( - opts.Notary.Issuer, - opts.Notary.PrivateKeyID, - opts.Notary.PrivateKey, - ) + log.Infof("fetching submission logs for %q", opts.ID) - token, err := notary.NewSignedToken(cfg.TokenConfig) - if err != nil { - return err - } + cfg := quill.NewNotarizeConfig( + opts.Notary.Issuer, + opts.Notary.PrivateKeyID, + opts.Notary.PrivateKey, + ) - a := notary.NewAPIClient(token, cfg.HTTPTimeout) + token, err := notary.NewSignedToken(cfg.TokenConfig) + if err != nil { + return err + } - sub := notary.ExistingSubmission(a, opts.ID) + a := notary.NewAPIClient(token, cfg.HTTPTimeout) - content, err := sub.Logs(cmd.Context()) - if err != nil { - return err - } + sub := notary.ExistingSubmission(a, opts.ID) - bus.Report(content) + content, err := sub.Logs(cmd.Context()) + if err != nil { + return err + } - return nil - })) + bus.Report(content) + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/submission_status.go b/cmd/quill/cli/commands/submission_status.go index bc2cc24..0b52549 100644 --- a/cmd/quill/cli/commands/submission_status.go +++ b/cmd/quill/cli/commands/submission_status.go @@ -42,44 +42,44 @@ func SubmissionStatus(app clio.Application) *cobra.Command { }, ), RunE: func(cmd *cobra.Command, args []string) error { - return app.Run(cmd.Context(), async(func() error { - log.Infof("checking submission status for %q", opts.ID) + defer bus.Exit() - cfg := quill.NewNotarizeConfig( - opts.Notary.Issuer, - opts.Notary.PrivateKeyID, - opts.Notary.PrivateKey, - ).WithStatusConfig( - notary.StatusConfig{ - Timeout: time.Duration(int64(opts.TimeoutSeconds) * int64(time.Second)), - Poll: time.Duration(int64(opts.PollSeconds) * int64(time.Second)), - Wait: opts.Wait, - }, - ) + log.Infof("checking submission status for %q", opts.ID) - token, err := notary.NewSignedToken(cfg.TokenConfig) - if err != nil { - return err - } + cfg := quill.NewNotarizeConfig( + opts.Notary.Issuer, + opts.Notary.PrivateKeyID, + opts.Notary.PrivateKey, + ).WithStatusConfig( + notary.StatusConfig{ + Timeout: time.Duration(int64(opts.TimeoutSeconds) * int64(time.Second)), + Poll: time.Duration(int64(opts.PollSeconds) * int64(time.Second)), + Wait: opts.Wait, + }, + ) - a := notary.NewAPIClient(token, cfg.HTTPTimeout) + token, err := notary.NewSignedToken(cfg.TokenConfig) + if err != nil { + return err + } - sub := notary.ExistingSubmission(a, opts.ID) + a := notary.NewAPIClient(token, cfg.HTTPTimeout) - var status notary.SubmissionStatus - if opts.Wait { - status, err = notary.PollStatus(cmd.Context(), sub, cfg.StatusConfig) - } else { - status, err = sub.Status(cmd.Context()) - } - if err != nil { - return err - } + sub := notary.ExistingSubmission(a, opts.ID) - bus.Report(string(status)) + var status notary.SubmissionStatus + if opts.Wait { + status, err = notary.PollStatus(cmd.Context(), sub, cfg.StatusConfig) + } else { + status, err = sub.Status(cmd.Context()) + } + if err != nil { + return err + } - return nil - })) + bus.Report(string(status)) + + return nil }, }, opts) } diff --git a/cmd/quill/cli/commands/utils.go b/cmd/quill/cli/commands/utils.go index fd8de38..6e6f445 100644 --- a/cmd/quill/cli/commands/utils.go +++ b/cmd/quill/cli/commands/utils.go @@ -9,7 +9,7 @@ import ( "software.sslmate.com/src/go-pkcs12" "github.com/anchore/quill/internal/bus" - "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/internal/redact" "github.com/anchore/quill/quill/pki/load" ) @@ -34,7 +34,7 @@ func loadP12Interactively(p12Path, password string) (*load.P12Contents, error) { return nil, fmt.Errorf("unable to get password from prompt: %w", err) } - log.Redact(newPassword) + redact.Add(newPassword) key, cert, certs, err := pkcs12.DecodeChain(by, newPassword) if err != nil { @@ -48,19 +48,6 @@ func loadP12Interactively(p12Path, password string) (*load.P12Contents, error) { }, nil } -func async(f func() error) <-chan error { - errs := make(chan error) - go func() { - defer close(errs) - if err := f(); err != nil { - errs <- err - } - bus.Exit() - }() - - return errs -} - func chainArgs(processors ...func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { for _, p := range processors { diff --git a/cmd/quill/cli/options/p12.go b/cmd/quill/cli/options/p12.go index 973e464..bad36c8 100644 --- a/cmd/quill/cli/options/p12.go +++ b/cmd/quill/cli/options/p12.go @@ -2,7 +2,7 @@ package options import ( "github.com/anchore/fangs" - "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/internal/redact" ) var _ interface { @@ -15,7 +15,7 @@ type P12 struct { } func (o *P12) PostLoad() error { - log.Redact(o.Password) + redact.Add(o.Password) return nil } diff --git a/cmd/quill/cli/options/signing.go b/cmd/quill/cli/options/signing.go index 21206cb..db51271 100644 --- a/cmd/quill/cli/options/signing.go +++ b/cmd/quill/cli/options/signing.go @@ -2,7 +2,7 @@ package options import ( "github.com/anchore/fangs" - "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/internal/redact" ) var _ interface { @@ -31,7 +31,7 @@ func DefaultSigning() Signing { } func (o *Signing) PostLoad() error { - log.Redact(o.Password) + redact.Add(o.Password) redactNonFileOrEnvHint(o.P12) return nil } diff --git a/cmd/quill/cli/options/utils.go b/cmd/quill/cli/options/utils.go index b91ebf0..84d718f 100644 --- a/cmd/quill/cli/options/utils.go +++ b/cmd/quill/cli/options/utils.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/internal/redact" ) func FormatPositionalArgsHelp(args map[string]string) string { @@ -40,5 +40,5 @@ func redactNonFileOrEnvHint(value string) { return } // path does not exist OR there was an access issue and we cannot verify... either way, redact - log.Redact(value) + redact.Add(value) } diff --git a/cmd/quill/main.go b/cmd/quill/main.go index 0b6b438..ceb4680 100644 --- a/cmd/quill/main.go +++ b/cmd/quill/main.go @@ -10,6 +10,7 @@ import ( "github.com/anchore/clio" "github.com/anchore/quill/cmd/quill/cli" + "github.com/anchore/quill/internal" "github.com/anchore/quill/internal/log" ) @@ -23,7 +24,8 @@ var buildDate = valueNotProvided func main() { cmd := cli.New( - clio.Version{ + clio.Identification{ + Name: internal.ApplicationName, Version: version, GitCommit: gitCommit, GitDescription: gitDescription, diff --git a/go.mod b/go.mod index 2c865d3..467e53a 100644 --- a/go.mod +++ b/go.mod @@ -6,25 +6,25 @@ require ( github.com/PuerkitoBio/goquery v1.8.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/anchore/bubbly v0.0.0-20230518153401-87b6af8ccf22 - github.com/anchore/clio v0.0.0-20230531204310-4a8bc623b049 - github.com/anchore/fangs v0.0.0-20230531202914-48a718c6b4ba + github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817 + github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097 github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb github.com/aws/aws-sdk-go v1.44.114 - github.com/blacktop/go-macho v1.1.154 + github.com/blacktop/go-macho v1.1.161 github.com/charmbracelet/bubbletea v0.22.1 github.com/charmbracelet/lipgloss v0.7.1 github.com/gabriel-vasile/mimetype v1.4.2 github.com/github/smimesign v0.2.0 github.com/go-restruct/restruct v1.2.0-alpha - github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-cmp v0.5.9 github.com/gookit/color v1.5.3 github.com/jedib0t/go-pretty v4.3.0+incompatible - github.com/klauspost/compress v1.16.4 + github.com/klauspost/compress v1.16.6 github.com/scylladb/go-set v1.0.2 github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.4 github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 github.com/wagoodman/go-progress v0.0.0-20220614130704-4b1c25a33c7c software.sslmate.com/src/go-pkcs12 v0.2.0 @@ -86,8 +86,8 @@ require ( go.mongodb.org/mongo-driver v1.10.0 // indirect golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/term v0.9.0 // indirect golang.org/x/text v0.8.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 03b4417..eadb21d 100644 --- a/go.sum +++ b/go.sum @@ -46,10 +46,10 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/anchore/bubbly v0.0.0-20230518153401-87b6af8ccf22 h1:5NFK6VGgqBUOAX2SYyzFYvNdOiYDxzim8jga386FlZY= github.com/anchore/bubbly v0.0.0-20230518153401-87b6af8ccf22/go.mod h1:Kv+Mm9CdtnV8iem48iEPIwy7/N4Wmk0hpxYNH5gTwKQ= -github.com/anchore/clio v0.0.0-20230531204310-4a8bc623b049 h1:zxyFABR9NlcjsgiumKEBfQyOu8h9hgBZ0U/BVlYcsI0= -github.com/anchore/clio v0.0.0-20230531204310-4a8bc623b049/go.mod h1:q5s//y4jA9/wxYNGxgkeCtgkJu6fZgS18gO3lVxSbHs= -github.com/anchore/fangs v0.0.0-20230531202914-48a718c6b4ba h1:tJ186HK8e0Lf+hhNWX4fJrq14yj3mw8JQkkLhA0nFhE= -github.com/anchore/fangs v0.0.0-20230531202914-48a718c6b4ba/go.mod h1:E3zNHEz7mizIFGJhuX+Ga7AbCmEN5TfzVDxmOfj7XZw= +github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817 h1:YsE91GT81FQOAOKByAnJVeJY2q8AunJ1eNf1bDC/o8g= +github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817/go.mod h1:H5f7dtqPQ6kbL0OHcLrc5N0zkIxLZPBL2oKUE03fLgA= +github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097 h1:79jSyWO6WOV8HPEpOQBOr7WsC2DnBRpyl7zsdaahCcg= +github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097/go.mod h1:E3zNHEz7mizIFGJhuX+Ga7AbCmEN5TfzVDxmOfj7XZw= github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe h1:Df867YMmymdMG6z5IW8pR0/2CRpLIjYnaTXLp6j+s0k= github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= @@ -67,8 +67,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/blacktop/go-dwarf v1.0.9 h1:eT/L7gt0gllvvgnRXY0MFKjNB6+jtOY5DTm2ynVX2dY= github.com/blacktop/go-dwarf v1.0.9/go.mod h1:4W2FKgSFYcZLDwnR7k+apv5i3nrau4NGl9N6VQ9DSTo= -github.com/blacktop/go-macho v1.1.154 h1:oVLHxDw+JUoj4P1EfWz4sKGl6MPn83G538y+xU3J3Ck= -github.com/blacktop/go-macho v1.1.154/go.mod h1:f2X4noFBob4G5bWUrzvPBKDVcFWZgDCM7rIn7ygTID0= +github.com/blacktop/go-macho v1.1.161 h1:LLxfxS/XF5p9BzouQSgF+fU95gZBJCzW1FhLMLmCd8s= +github.com/blacktop/go-macho v1.1.161/go.mod h1:f2X4noFBob4G5bWUrzvPBKDVcFWZgDCM7rIn7ygTID0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q= @@ -129,8 +129,8 @@ github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtK github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= -github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -220,8 +220,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= -github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= +github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -328,8 +328,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -522,14 +522,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/bus/helpers.go b/internal/bus/helpers.go index ab4c2a3..950af8b 100644 --- a/internal/bus/helpers.go +++ b/internal/bus/helpers.go @@ -5,7 +5,7 @@ import ( "github.com/wagoodman/go-progress" "github.com/anchore/bubbly" - "github.com/anchore/quill/internal/log" + "github.com/anchore/quill/internal/redact" "github.com/anchore/quill/quill/event" ) @@ -41,22 +41,33 @@ func Exit() { } func Report(report string) { - report = log.Redactor.RedactString(report) + if publisher == nil { + // prevent any further actions taken on the report (such as redaction) since it won't be published anyway + return + } publish(partybus.Event{ Type: event.CLIReportType, - Value: report, + Value: redact.Apply(report), }) } func Notify(message string) { + if publisher == nil { + // prevent any further actions taken on the report (such as redaction) since it won't be published anyway + return + } publish(partybus.Event{ Type: event.CLINotificationType, - Value: message, + Value: redact.Apply(message), }) } func PromptForInput(message string, sensitive bool, validators ...func(string) error) *bubbly.Prompter { - p := bubbly.NewPrompter(message, sensitive, validators...) + if publisher == nil { + // prevent any further actions taken on the report (such as redaction) since it won't be published anyway + return nil + } + p := bubbly.NewPrompter(redact.Apply(message), sensitive, validators...) publish(partybus.Event{ Type: event.CLIInputPromptType, Value: bubbly.PromptWriter(p), diff --git a/internal/log/log.go b/internal/log/log.go index 3425e04..6c5e27e 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -7,29 +7,27 @@ import ( "github.com/anchore/go-logger" "github.com/anchore/go-logger/adapter/discard" "github.com/anchore/go-logger/adapter/redact" + intRedact "github.com/anchore/quill/internal/redact" ) -var ( - // log is the singleton used to facilitate logging internally within - log = discard.New() - - store = redact.NewStore() - - Redactor = store.(redact.Redactor) -) +var log = discard.New() func Set(l logger.Logger) { - log = redact.New(l, store) + // though quill the application will automatically have a redaction logger, library consumers may not be doing this. + // for this reason we additionally ensure there is a redaction logger configured for any logger passed. The + // source of truth for redaction values is still in the internal redact package. If the passed logger is already + // redacted, then this is a no-op. + store := intRedact.Get() + if store != nil { + l = redact.New(l, store) + } + log = l } func Get() logger.Logger { return log } -func Redact(values ...string) { - store.Add(values...) -} - // Errorf takes a formatted template string and template arguments for the error logging level. func Errorf(format string, args ...interface{}) { log.Errorf(format, args...) diff --git a/internal/redact/redact.go b/internal/redact/redact.go new file mode 100644 index 0000000..3bb76e2 --- /dev/null +++ b/internal/redact/redact.go @@ -0,0 +1,36 @@ +package redact + +import "github.com/anchore/go-logger/adapter/redact" + +var store redact.Store + +func Set(s redact.Store) { + if store != nil { + // if someone is trying to set a redaction store and we already have one then something is wrong. The store + // that we're replacing might already have values in it, so we should never replace it. + panic("replace existing redaction store (probably unintentional)") + } + store = s +} + +func Get() redact.Store { + return store +} + +func Add(vs ...string) { + if store == nil { + // if someone is trying to add values that should never be output and we don't have a store then something is wrong. + // we should never accidentally output values that should be redacted, thus we panic here. + panic("cannot add redactions without a store") + } + store.Add(vs...) +} + +func Apply(value string) string { + if store == nil { + // if someone is trying to add values that should never be output and we don't have a store then something is wrong. + // we should never accidentally output values that should be redacted, thus we panic here. + panic("cannot apply redactions without a store") + } + return store.RedactString(value) +} diff --git a/quill/lib.go b/quill/lib.go index ed3fb63..e6f7852 100644 --- a/quill/lib.go +++ b/quill/lib.go @@ -4,16 +4,29 @@ import ( "github.com/wagoodman/go-partybus" "github.com/anchore/go-logger" + "github.com/anchore/go-logger/adapter/redact" "github.com/anchore/quill/internal/bus" "github.com/anchore/quill/internal/log" + intRedact "github.com/anchore/quill/internal/redact" ) // SetLogger sets the logger object used for all logging calls. func SetLogger(logger logger.Logger) { + useOrAddRedactor() log.Set(logger) } // SetBus sets the event bus for all library bus publish events onto (in-library subscriptions are not allowed). func SetBus(b *partybus.Bus) { + useOrAddRedactor() bus.Set(b) } + +func useOrAddRedactor() { + // since it is possible to read secrets from the environment during lib calls, we want to ensure that the logger + // is redacted even if the user did not explicitly set a redaction store. + store := intRedact.Get() + if store == nil { + intRedact.Set(redact.NewStore()) + } +}