From 9f799050abf190354c6941e8e85f037148962e95 Mon Sep 17 00:00:00 2001 From: Pulok Saha Date: Fri, 17 Jul 2026 13:51:51 +0600 Subject: [PATCH 1/6] added echo or stdin password update support --- pkg/key.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++ pkg/update_key.go | 9 +++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/pkg/key.go b/pkg/key.go index 39f7bff8..98bb9489 100644 --- a/pkg/key.go +++ b/pkg/key.go @@ -18,6 +18,7 @@ package pkg import ( "fmt" + "io" "os" "os/exec" "os/user" @@ -39,6 +40,86 @@ type keyOptions struct { config *rest.Config repo *storageapi.Repository paths []string + + // Alternative sources for the new password. Exactly one of + // opt.File (--new-password-file), newPassword (--new-password) and + // newPasswordStdin (--new-password-stdin) must be set. They are resolved + // into opt.File by preparePasswordFile() before the rest of the flow runs. + newPassword string + newPasswordStdin bool +} + +// preparePasswordFile resolves the new-password source into opt.File. +// +// - --new-password-file: opt.File is used as-is; the caller's file is left +// intact and the returned cleanup is a no-op. +// - --new-password / --new-password-stdin: the value (or stdin) is written to a +// temporary file, opt.File is pointed at it, and the returned cleanup removes +// it. The temp file is created with mode 0600 by os.CreateTemp. +// +// Exactly one source must be provided. Because both the pod path +// (copyPasswordFileToPod) and the docker path (runCmdViaDocker) only ever +// consume opt.File, materializing it here keeps every downstream path working +// unchanged. +func (opt *keyOptions) preparePasswordFile() (func(), error) { + noop := func() {} + + sources := 0 + if opt.File != "" { + sources++ + } + if opt.newPassword != "" { + sources++ + } + if opt.newPasswordStdin { + sources++ + } + + if sources == 0 { + return noop, fmt.Errorf("one of --new-password-file, --new-password or --new-password-stdin is required") + } + if sources > 1 { + return noop, fmt.Errorf("at most one of --new-password-file, --new-password or --new-password-stdin may be set") + } + + // --new-password-file: use the provided file directly. + if opt.File != "" { + return noop, nil + } + + // --new-password / --new-password-stdin: materialize a temp file. + 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) + } + + // Write verbatim; restic trims trailing newlines from password files, so + // `echo 'pw' | --new-password-stdin` (data "pw\n") matches the existing + // `echo 'pw' > file; --new-password-file` behavior. + 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..3469afa3 100644 --- a/pkg/update_key.go +++ b/pkg/update_key.go @@ -37,9 +37,14 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { Args: cobra.ExactArgs(1), DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { + cleanup, err := opt.preparePasswordFile() + if err != nil { + return err + } + defer cleanup() + repoName := args[0] - var err error opt.repo, err = getRepository(kmapi.ObjectReference{ Name: repoName, Namespace: srcNamespace, @@ -92,6 +97,8 @@ 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") return cmd From 8f594289daa3c3213be1a4681000732164bc04a4 Mon Sep 17 00:00:00 2001 From: Pulok Saha Date: Tue, 11 Aug 2026 11:57:57 +0600 Subject: [PATCH 2/6] removed unnecessary comments Signed-off-by: Pulok Saha --- pkg/key.go | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/pkg/key.go b/pkg/key.go index 98bb9489..db91d192 100644 --- a/pkg/key.go +++ b/pkg/key.go @@ -37,30 +37,13 @@ import ( type keyOptions struct { restic.KeyOptions - config *rest.Config - repo *storageapi.Repository - paths []string - - // Alternative sources for the new password. Exactly one of - // opt.File (--new-password-file), newPassword (--new-password) and - // newPasswordStdin (--new-password-stdin) must be set. They are resolved - // into opt.File by preparePasswordFile() before the rest of the flow runs. + config *rest.Config + repo *storageapi.Repository + paths []string newPassword string newPasswordStdin bool } -// preparePasswordFile resolves the new-password source into opt.File. -// -// - --new-password-file: opt.File is used as-is; the caller's file is left -// intact and the returned cleanup is a no-op. -// - --new-password / --new-password-stdin: the value (or stdin) is written to a -// temporary file, opt.File is pointed at it, and the returned cleanup removes -// it. The temp file is created with mode 0600 by os.CreateTemp. -// -// Exactly one source must be provided. Because both the pod path -// (copyPasswordFileToPod) and the docker path (runCmdViaDocker) only ever -// consume opt.File, materializing it here keeps every downstream path working -// unchanged. func (opt *keyOptions) preparePasswordFile() (func(), error) { noop := func() {} @@ -87,7 +70,6 @@ func (opt *keyOptions) preparePasswordFile() (func(), error) { return noop, nil } - // --new-password / --new-password-stdin: materialize a temp file. f, err := os.CreateTemp("", "kubestash-newpw-") if err != nil { return noop, fmt.Errorf("failed to create temp password file: %w", err) @@ -105,9 +87,6 @@ func (opt *keyOptions) preparePasswordFile() (func(), error) { data = []byte(opt.newPassword) } - // Write verbatim; restic trims trailing newlines from password files, so - // `echo 'pw' | --new-password-stdin` (data "pw\n") matches the existing - // `echo 'pw' > file; --new-password-file` behavior. if _, err := f.Write(data); err != nil { _ = f.Close() _ = os.Remove(f.Name()) From 1e4e1de497f249628d5c7df13647597cd1f5845a Mon Sep 17 00:00:00 2001 From: Pulok Saha Date: Tue, 11 Aug 2026 12:48:40 +0600 Subject: [PATCH 3/6] added requested change Signed-off-by: Pulok Saha --- pkg/key.go | 18 ------------------ pkg/update_key.go | 13 ++++++++----- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/pkg/key.go b/pkg/key.go index db91d192..858f8415 100644 --- a/pkg/key.go +++ b/pkg/key.go @@ -47,24 +47,6 @@ type keyOptions struct { func (opt *keyOptions) preparePasswordFile() (func(), error) { noop := func() {} - sources := 0 - if opt.File != "" { - sources++ - } - if opt.newPassword != "" { - sources++ - } - if opt.newPasswordStdin { - sources++ - } - - if sources == 0 { - return noop, fmt.Errorf("one of --new-password-file, --new-password or --new-password-stdin is required") - } - if sources > 1 { - return noop, fmt.Errorf("at most one of --new-password-file, --new-password or --new-password-stdin may be set") - } - // --new-password-file: use the provided file directly. if opt.File != "" { return noop, nil diff --git a/pkg/update_key.go b/pkg/update_key.go index 3469afa3..43ae7bf4 100644 --- a/pkg/update_key.go +++ b/pkg/update_key.go @@ -37,11 +37,6 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { Args: cobra.ExactArgs(1), DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - cleanup, err := opt.preparePasswordFile() - if err != nil { - return err - } - defer cleanup() repoName := args[0] @@ -88,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) } @@ -100,6 +101,8 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { 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 } From f4cecf6f3ed35bf40463666c8f3e494a31033bd4 Mon Sep 17 00:00:00 2001 From: Pulok Saha Date: Tue, 11 Aug 2026 14:15:06 +0600 Subject: [PATCH 4/6] added stdin support for add key Signed-off-by: Pulok Saha --- pkg/add_key.go | 12 +++++++++--- pkg/update_key.go | 11 +++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pkg/add_key.go b/pkg/add_key.go index 5986b18e..ddf04fbc 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,11 +37,14 @@ 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") + cleanup, err := opt.preparePasswordFile() + if err != nil { + return err + } + defer cleanup() repoName := args[0] - var err error opt.repo, err = getRepository(kmapi.ObjectReference{ Name: repoName, Namespace: srcNamespace, @@ -97,7 +99,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/update_key.go b/pkg/update_key.go index 43ae7bf4..725de6b6 100644 --- a/pkg/update_key.go +++ b/pkg/update_key.go @@ -37,6 +37,11 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { Args: cobra.ExactArgs(1), DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { + cleanup, err := opt.preparePasswordFile() + if err != nil { + return err + } + defer cleanup() repoName := args[0] @@ -83,12 +88,6 @@ 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) } From eba2da1b0a8756ecb6d14adfc4c537f8b166a059 Mon Sep 17 00:00:00 2001 From: Pulok Saha Date: Tue, 11 Aug 2026 14:24:09 +0600 Subject: [PATCH 5/6] changed prepare password position Signed-off-by: Pulok Saha --- pkg/add_key.go | 13 +++++++------ pkg/update_key.go | 12 +++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pkg/add_key.go b/pkg/add_key.go index ddf04fbc..cce41d1e 100644 --- a/pkg/add_key.go +++ b/pkg/add_key.go @@ -37,14 +37,9 @@ func NewCmdAddKey(opt *keyOptions) *cobra.Command { Args: cobra.ExactArgs(1), DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - cleanup, err := opt.preparePasswordFile() - if err != nil { - return err - } - defer cleanup() - repoName := args[0] + var err error opt.repo, err = getRepository(kmapi.ObjectReference{ Name: repoName, Namespace: srcNamespace, @@ -88,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) } diff --git a/pkg/update_key.go b/pkg/update_key.go index 725de6b6..78ffe1bc 100644 --- a/pkg/update_key.go +++ b/pkg/update_key.go @@ -37,14 +37,10 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { Args: cobra.ExactArgs(1), DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - cleanup, err := opt.preparePasswordFile() - if err != nil { - return err - } - defer cleanup() repoName := args[0] + var err error opt.repo, err = getRepository(kmapi.ObjectReference{ Name: repoName, Namespace: srcNamespace, @@ -88,6 +84,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) } From cbb67402d1b5cdae5ca719f71c89bd621c22b930 Mon Sep 17 00:00:00 2001 From: Pulok Saha Date: Tue, 11 Aug 2026 14:29:56 +0600 Subject: [PATCH 6/6] added make gen fmt Signed-off-by: Pulok Saha --- pkg/update_key.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/update_key.go b/pkg/update_key.go index 78ffe1bc..89ee3700 100644 --- a/pkg/update_key.go +++ b/pkg/update_key.go @@ -37,7 +37,6 @@ func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { Args: cobra.ExactArgs(1), DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - repoName := args[0] var err error