package cli import ( "os" "os/signal" "github.com/awnumar/memguard" "github.com/spf13/cobra" "golang.org/x/sys/unix" "golang.org/x/term" "sneak.berlin/go/secret/internal/secret" ) // Entry runs the secret CLI and returns the process exit code. It wipes // every memguard buffer before it returns, so the caller must do nothing // but exit with the code. func Entry() int { // On SIGINT, SIGTERM or SIGHUP memguard runs this function, wipes every // buffer and exits with status 1. The function removes the temporary // file that encrypt or decrypt is writing. The passphrase prompt turns // echo off on the controlling terminal until the read finishes, so a // signal there would leave echo off. Only a process in the terminal's // foreground process group may reset it: one in the background that // tries is stopped instead of exiting. terminal, terminalErr := os.Open(secret.TerminalPath) var terminalState *term.State if terminalErr == nil { defer func() { _ = terminal.Close() }() terminalState, terminalErr = term.GetState(int(terminal.Fd())) } signals := []os.Signal{os.Interrupt, unix.SIGTERM} // A command started with nohup must go on ignoring SIGHUP, which // catching it would undo if !signal.Ignored(unix.SIGHUP) { signals = append(signals, unix.SIGHUP) } memguard.CatchSignal(func(os.Signal) { name := temporaryOutput.Load() if name != nil { _ = os.Remove(*name) } if terminalErr != nil { return } foreground, err := unix.IoctlGetInt(int(terminal.Fd()), unix.TIOCGPGRP) if err == nil && foreground == unix.Getpgrp() { _ = term.Restore(int(terminal.Fd()), terminalState) } }, signals...) defer memguard.Purge() err := newRootCmd().Execute() if err != nil { return 1 } return 0 } func newRootCmd() *cobra.Command { secret.Debug("newRootCmd starting") cmd := &cobra.Command{ Use: "secret", Short: "A simple secrets manager", Long: `A simple secrets manager to store and retrieve sensitive ` + `information securely.`, // Cobra prints the error a command returns; Entry does not. SilenceErrors: false, // Usage belongs only to a command called wrongly. Cobra has // checked its arguments and flag values before this runs, but // checks required flags (ValidateRequiredFlags) and flag groups // (ValidateFlagGroups) only after it, so both are checked here // to keep usage for them. An error after that comes from running // the command, and usage would only bury it. A subcommand that // sets its own PersistentPreRun replaces this one. PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { err := cmd.ValidateRequiredFlags() if err != nil { return err } err = cmd.ValidateFlagGroups() if err != nil { return err } cmd.SilenceUsage = true return nil }, } secret.Debug("Adding subcommands to root command") // Add subcommands cmd.AddCommand(NewInitCmd()) cmd.AddCommand(newGenerateCmd()) cmd.AddCommand(newVaultCmd()) cmd.AddCommand(newAddCmd()) cmd.AddCommand(newGetCmd()) cmd.AddCommand(newListCmd()) cmd.AddCommand(newRemoveCmd()) cmd.AddCommand(newMoveCmd()) cmd.AddCommand(newUnlockerCmd()) cmd.AddCommand(newImportCmd()) cmd.AddCommand(newEncryptCmd()) cmd.AddCommand(newDecryptCmd()) cmd.AddCommand(newVersionCmd()) cmd.AddCommand(newInfoCmd()) cmd.AddCommand(newCompletionCmd()) secret.Debug("newRootCmd completed") return cmd }