check / check (push) Waiting to run
secret list, secret version list, secret unlocker list and secret info printed times in the machine's time zone without naming it, so one vault showed different times on two machines. Entry() now sets time.Local to UTC before anything else, as AGENTS.md policy 6 asks, so every time the program prints or writes is in UTC: --json times end in Z, and a new version's name takes the UTC date. The README says so. A test runs the built secret list with TZ=America/New_York and checks the table and the JSON against the UTC time set on the secret's current file. Rule suppressed: gosmopolitan, which flags any use of time.Local. Model: opus-5-5
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"os"
|
|
"time"
|
|
|
|
"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 {
|
|
// Every time secret prints or writes is in UTC, whatever TZ or the
|
|
// system's time zone is set to.
|
|
time.Local = time.UTC //nolint:gosmopolitan // this makes local time UTC
|
|
|
|
raiseLockedMemoryLimit()
|
|
|
|
// On SIGINT or SIGTERM memguard runs this function, wipes every buffer
|
|
// and exits with status 1. 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()))
|
|
}
|
|
|
|
memguard.CatchSignal(func(os.Signal) {
|
|
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)
|
|
}
|
|
}, os.Interrupt, unix.SIGTERM)
|
|
|
|
defer memguard.Purge()
|
|
|
|
err := newRootCmd().Execute()
|
|
if err != nil {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// raiseLockedMemoryLimit raises this process's limit on locked memory, which
|
|
// holds every secret value, to the hard limit, the most a process may set
|
|
// without privileges. When that fails the limit stays as it was, and
|
|
// checkLockedMemoryLimit names it in its error.
|
|
func raiseLockedMemoryLimit() {
|
|
var limit unix.Rlimit
|
|
|
|
if unix.Getrlimit(unix.RLIMIT_MEMLOCK, &limit) == nil {
|
|
limit.Cur = limit.Max
|
|
_ = unix.Setrlimit(unix.RLIMIT_MEMLOCK, &limit)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|