diff --git a/pkg/add_key.go b/pkg/add_key.go index 5986b18e..cce41d1e 100644 --- a/pkg/add_key.go +++ b/pkg/add_key.go @@ -22,7 +22,6 @@ import ( "path/filepath" "github.com/spf13/cobra" - "gomodules.xyz/flags" "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/klog/v2" @@ -38,8 +37,6 @@ func NewCmdAddKey(opt *keyOptions) *cobra.Command { Args: cobra.ExactArgs(1), DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - flags.EnsureRequiredFlags(cmd, "new-password-file") - repoName := args[0] var err error @@ -86,6 +83,12 @@ func NewCmdAddKey(opt *keyOptions) *cobra.Command { return err } + cleanup, err := opt.preparePasswordFile() + if err != nil { + return err + } + defer cleanup() + if yes { return opt.addResticKeyViaPod(&operatorPod) } @@ -97,7 +100,11 @@ func NewCmdAddKey(opt *keyOptions) *cobra.Command { cmd.Flags().StringVar(&opt.Host, "host", opt.Host, "Host for the new key") cmd.Flags().StringVar(&opt.User, "user", opt.User, "User for the new key") cmd.Flags().StringVar(&opt.File, "new-password-file", opt.File, "File from which to read the new password") + cmd.Flags().StringVar(&opt.newPassword, "new-password", opt.newPassword, "New password for the restic repository (inline). Note: this exposes the password in shell history and the process list; prefer --new-password-stdin or --new-password-file") + cmd.Flags().BoolVar(&opt.newPasswordStdin, "new-password-stdin", opt.newPasswordStdin, "Read the new password from stdin (e.g. echo '' | kubectl kubestash pw add ...)") cmd.Flags().StringSliceVar(&opt.paths, "paths", opt.paths, "List of component paths (restic repositories) to add the new password") + cmd.MarkFlagsMutuallyExclusive("new-password-file", "new-password", "new-password-stdin") + cmd.MarkFlagsOneRequired("new-password-file", "new-password", "new-password-stdin") return cmd } diff --git a/pkg/key.go b/pkg/key.go index 39f7bff8..858f8415 100644 --- a/pkg/key.go +++ b/pkg/key.go @@ -18,6 +18,7 @@ package pkg import ( "fmt" + "io" "os" "os/exec" "os/user" @@ -36,9 +37,50 @@ import ( type keyOptions struct { restic.KeyOptions - config *rest.Config - repo *storageapi.Repository - paths []string + config *rest.Config + repo *storageapi.Repository + paths []string + newPassword string + newPasswordStdin bool +} + +func (opt *keyOptions) preparePasswordFile() (func(), error) { + noop := func() {} + + // --new-password-file: use the provided file directly. + if opt.File != "" { + return noop, nil + } + + f, err := os.CreateTemp("", "kubestash-newpw-") + if err != nil { + return noop, fmt.Errorf("failed to create temp password file: %w", err) + } + + var data []byte + if opt.newPasswordStdin { + data, err = io.ReadAll(os.Stdin) + if err != nil { + _ = f.Close() + _ = os.Remove(f.Name()) + return noop, fmt.Errorf("failed to read new password from stdin: %w", err) + } + } else { + data = []byte(opt.newPassword) + } + + if _, err := f.Write(data); err != nil { + _ = f.Close() + _ = os.Remove(f.Name()) + return noop, fmt.Errorf("failed to write temp password file: %w", err) + } + if err := f.Close(); err != nil { + _ = os.Remove(f.Name()) + return noop, fmt.Errorf("failed to close temp password file: %w", err) + } + + opt.File = f.Name() + return func() { _ = os.Remove(f.Name()) }, nil } func NewCmdKey(clientGetter genericclioptions.RESTClientGetter) *cobra.Command { diff --git a/pkg/update_key.go b/pkg/update_key.go index cdaaeffc..89ee3700 100644 --- a/pkg/update_key.go +++ b/pkg/update_key.go @@ -83,6 +83,12 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { return err } + cleanup, err := opt.preparePasswordFile() + if err != nil { + return err + } + defer cleanup() + if yes { return opt.updateResticKeyViaPod(&operatorPod) } @@ -92,7 +98,11 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { } cmd.Flags().StringVar(&opt.File, "new-password-file", opt.File, "File from which to read the new password") + cmd.Flags().StringVar(&opt.newPassword, "new-password", opt.newPassword, "New password for the restic repository (inline). Note: this exposes the password in shell history and the process list; prefer --new-password-stdin or --new-password-file") + cmd.Flags().BoolVar(&opt.newPasswordStdin, "new-password-stdin", opt.newPasswordStdin, "Read the new password from stdin (e.g. echo '' | kubectl kubestash pw update ...)") cmd.Flags().StringSliceVar(&opt.paths, "paths", opt.paths, "List of component paths (restic repositories) to update the password") + cmd.MarkFlagsMutuallyExclusive("new-password-file", "new-password", "new-password-stdin") + cmd.MarkFlagsOneRequired("new-password-file", "new-password", "new-password-stdin") return cmd }