cmd/helm: add '--password-stdin' option where '--password' is allowed

Mirrors similar behaviour in the Docker CLI client, minus the
warning that providing passwords on the command line is insecure.

Signed-off-by: Geoff Baskwill <me@geoffbaskwill.ca>
pull/4856/head
Geoff Baskwill 7 years ago
parent 1ebbd69896
commit 113e2e8dd1

@ -52,8 +52,8 @@ type fetchCmd struct {
destdir string
version string
repoURL string
username string
password string
credentials
verify bool
verifyLater bool
@ -76,6 +76,10 @@ func newFetchCmd(out io.Writer) *cobra.Command {
Short: "download a chart from a repository and (optionally) unpack it in local directory",
Long: fetchDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := fch.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
if len(args) == 0 {
return fmt.Errorf("need at least one argument, url or repo/name of the chart")
}
@ -108,8 +112,8 @@ func newFetchCmd(out io.Writer) *cobra.Command {
f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.StringVar(&fch.username, "username", "", "chart repository username")
f.StringVar(&fch.password, "password", "", "chart repository password")
fch.credentials.addFlags(f)
return cmd
}

@ -17,13 +17,18 @@ limitations under the License.
package main // import "k8s.io/helm/cmd/helm"
import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"syscall"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/status"
"k8s.io/client-go/kubernetes"
@ -291,3 +296,50 @@ func newClient() helm.Interface {
}
return helm.NewClient(options...)
}
type credentials struct {
username string
password string
passwordStdin bool
}
func (c *credentials) addFlags(f *flag.FlagSet) {
f.StringVar(&c.username, "username", "", "chart repository username")
f.StringVar(&c.password, "password", "", "chart repository password")
f.BoolVar(&c.passwordStdin, "password-stdin", false, "take the chart repository password from stdin")
}
func (c *credentials) readPassword(r io.Reader, w io.Writer) error {
if c.password != "" && c.passwordStdin {
return errors.New("--password and --password-stdin are mutually exclusive")
}
if c.passwordStdin && c.username == "" {
return errors.New("must provide --username with --password-stdin")
}
if (c.username != "" && c.password == "") || c.passwordStdin {
if r == os.Stdin && terminal.IsTerminal(syscall.Stdin) {
fmt.Fprint(w, "Password: ")
defer fmt.Fprintln(w)
password, err := terminal.ReadPassword(int(syscall.Stdin))
if err == nil {
c.password = string(password)
}
return err
}
contents, err := ioutil.ReadAll(r)
if err != nil {
return err
}
c.password = strings.TrimSuffix(string(contents), "\n")
c.password = strings.TrimSuffix(c.password, "\r")
}
return nil
}

@ -550,3 +550,84 @@ func resetEnv() func() {
}
}
}
func Test_Credentials_ReadPassword(t *testing.T) {
tests := []struct {
name string
credentials credentials
r io.Reader
expectedPassword string
expectedOutput string
expectedError string
}{
{
name: "password and password-stdin are mutually exclusive",
credentials: credentials{
password: "password",
passwordStdin: true,
},
expectedError: "--password and --password-stdin are mutually exclusive",
expectedPassword: "password", // unchanged from initial state
},
{
name: "must provide username with password-stdin",
credentials: credentials{
passwordStdin: true,
},
expectedError: "must provide --username with --password-stdin",
},
{
name: "reads password if username provided but password not provided", // repo-add backwards compatibility
credentials: credentials{
username: "user",
},
r: bytes.NewBufferString("password"),
expectedPassword: "password",
},
{
name: "reads password if --password-stdin provided",
credentials: credentials{
username: "user",
passwordStdin: true,
},
r: bytes.NewBufferString("password"),
expectedPassword: "password",
},
{
name: "strips CRLF from input",
credentials: credentials{
username: "user",
passwordStdin: true,
},
r: bytes.NewBufferString("password\r\n"),
expectedPassword: "password",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := new(bytes.Buffer)
err := tt.credentials.readPassword(tt.r, w)
if err == nil && tt.expectedError != "" {
t.Errorf("expected error %v, got none", tt.expectedError)
return
}
if err != nil {
if s := err.Error(); s != tt.expectedError {
t.Errorf("expected error %v, got %v", tt.expectedError, s)
return
}
}
if s := w.String(); tt.expectedOutput != s {
t.Errorf("expected output %v, got %v", tt.expectedOutput, s)
}
if tt.expectedPassword != tt.credentials.password {
t.Errorf("expected password %v, got %v", tt.expectedPassword, tt.credentials.password)
}
})
}
}

@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/ghodss/yaml"
@ -59,8 +60,8 @@ type inspectCmd struct {
out io.Writer
version string
repoURL string
username string
password string
credentials
certFile string
keyFile string
@ -87,6 +88,10 @@ func newInspectCmd(out io.Writer) *cobra.Command {
Short: "inspect a chart",
Long: inspectDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := insp.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
@ -105,6 +110,10 @@ func newInspectCmd(out io.Writer) *cobra.Command {
Short: "shows inspect values",
Long: inspectValuesDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := insp.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
insp.output = valuesOnly
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
@ -124,6 +133,10 @@ func newInspectCmd(out io.Writer) *cobra.Command {
Short: "shows inspect chart",
Long: inspectChartDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := insp.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
insp.output = chartOnly
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
@ -143,6 +156,10 @@ func newInspectCmd(out io.Writer) *cobra.Command {
Short: "shows inspect readme",
Long: readmeChartDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if err := insp.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
insp.output = readmeOnly
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
@ -183,18 +200,6 @@ func newInspectCmd(out io.Writer) *cobra.Command {
subCmd.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc)
}
username := "username"
usernamedesc := "chart repository username where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.username, username, "", usernamedesc)
valuesSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc)
chartSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc)
password := "password"
passworddesc := "chart repository password where to locate the requested chart"
inspectCommand.Flags().StringVar(&insp.password, password, "", passworddesc)
valuesSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
chartSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc)
certFile := "cert-file"
certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle"
for _, subCmd := range cmds {
@ -213,6 +218,10 @@ func newInspectCmd(out io.Writer) *cobra.Command {
subCmd.Flags().StringVar(&insp.caFile, caFile, "", caFiledesc)
}
for _, subCmd := range cmds {
insp.credentials.addFlags(subCmd.Flags())
}
for _, subCmd := range cmds[1:] {
inspectCommand.AddCommand(subCmd)
}

@ -131,11 +131,12 @@ type installCmd struct {
timeout int64
wait bool
repoURL string
username string
password string
devel bool
depUp bool
description string
credentials
devel bool
depUp bool
description string
certFile string
keyFile string
@ -171,6 +172,10 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: installDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if err := inst.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
if err := checkArgsLength(len(args), "chart name"); err != nil {
return err
}
@ -211,8 +216,6 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
@ -220,6 +223,8 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart")
f.StringVar(&inst.description, "description", "", "specify a description for the release")
inst.credentials.addFlags(f)
// set defaults from environment
settings.InitTLS(f)

@ -19,24 +19,23 @@ package main
import (
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/repo"
"syscall"
)
type repoAddCmd struct {
name string
url string
username string
password string
home helmpath.Home
noupdate bool
credentials
certFile string
keyFile string
caFile string
@ -51,6 +50,10 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
Use: "add [flags] [NAME] [URL]",
Short: "add a chart repository",
RunE: func(cmd *cobra.Command, args []string) error {
if err := add.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
if err := checkArgsLength(len(args), "name for the chart repository", "the url of the chart repository"); err != nil {
return err
}
@ -64,27 +67,17 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&add.username, "username", "", "chart repository username")
f.StringVar(&add.password, "password", "", "chart repository password")
f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered")
f.StringVar(&add.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&add.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&add.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
add.credentials.addFlags(f)
return cmd
}
func (a *repoAddCmd) run() error {
if a.username != "" && a.password == "" {
fmt.Fprint(a.out, "Password:")
password, err := readPassword()
fmt.Fprintln(a.out)
if err != nil {
return err
}
a.password = password
}
if err := addRepository(a.name, a.url, a.username, a.password, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil {
return err
}
@ -92,14 +85,6 @@ func (a *repoAddCmd) run() error {
return nil
}
func readPassword() (string, error) {
password, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", err
}
return string(password), nil
}
func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error {
f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
if err != nil {

@ -19,6 +19,7 @@ package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/spf13/cobra"
@ -44,7 +45,7 @@ To customize the chart values, use any of
- '--set-string' to provide key=val forcing val to be stored as a string,
- '--set-file' to provide key=path to read a single large value from a file at path.
To edit or append to the existing customized values, add the
To edit or append to the existing customized values, add the
'--reuse-values' flag, otherwise any existing customized values are ignored.
If no chart value arguments are provided on the command line, any existing customized values are carried
@ -106,10 +107,11 @@ type upgradeCmd struct {
reuseValues bool
wait bool
repoURL string
username string
password string
devel bool
description string
credentials
devel bool
description string
certFile string
keyFile string
@ -129,6 +131,10 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: upgradeDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if err := upgrade.readPassword(os.Stdin, os.Stderr); err != nil {
return err
}
if err := checkArgsLength(len(args), "release name", "chart path"); err != nil {
return err
}
@ -167,14 +173,14 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.StringVar(&upgrade.description, "description", "", "specify the description to use for the upgrade, rather than the default")
upgrade.credentials.addFlags(f)
f.MarkDeprecated("disable-hooks", "use --no-hooks instead")
// set defaults from environment

@ -34,6 +34,7 @@ helm fetch [flags] [chart URL | repo/chartname] [...]
--key-file string identify HTTPS client using this SSL key file
--keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password
--password-stdin take the chart repository password from stdin
--prov fetch the provenance file, but don't perform verification
--repo string chart repository url where to locate the requested chart
--untar if set to true, will untar the chart after downloading it
@ -59,4 +60,4 @@ helm fetch [flags] [chart URL | repo/chartname] [...]
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 1-Aug-2018
###### Auto generated by spf13/cobra on 30-Oct-2018

@ -23,9 +23,10 @@ helm inspect [CHART] [flags]
-h, --help help for inspect
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password where to locate the requested chart
--password string chart repository password
--password-stdin take the chart repository password from stdin
--repo string chart repository url where to locate the requested chart
--username string chart repository username where to locate the requested chart
--username string chart repository username
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
@ -49,4 +50,4 @@ helm inspect [CHART] [flags]
* [helm inspect readme](helm_inspect_readme.md) - shows inspect readme
* [helm inspect values](helm_inspect_values.md) - shows inspect values
###### Auto generated by spf13/cobra on 1-Aug-2018
###### Auto generated by spf13/cobra on 8-Nov-2018

@ -21,9 +21,10 @@ helm inspect chart [CHART] [flags]
-h, --help help for chart
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password where to locate the requested chart
--password string chart repository password
--password-stdin take the chart repository password from stdin
--repo string chart repository url where to locate the requested chart
--username string chart repository username where to locate the requested chart
--username string chart repository username
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
@ -44,4 +45,4 @@ helm inspect chart [CHART] [flags]
* [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 1-Aug-2018
###### Auto generated by spf13/cobra on 8-Nov-2018

@ -21,7 +21,10 @@ helm inspect readme [CHART] [flags]
-h, --help help for readme
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password
--password-stdin take the chart repository password from stdin
--repo string chart repository url where to locate the requested chart
--username string chart repository username
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
@ -42,4 +45,4 @@ helm inspect readme [CHART] [flags]
* [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 1-Aug-2018
###### Auto generated by spf13/cobra on 8-Nov-2018

@ -21,9 +21,10 @@ helm inspect values [CHART] [flags]
-h, --help help for values
--key-file string identify HTTPS client using this SSL key file
--keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg")
--password string chart repository password where to locate the requested chart
--password string chart repository password
--password-stdin take the chart repository password from stdin
--repo string chart repository url where to locate the requested chart
--username string chart repository username where to locate the requested chart
--username string chart repository username
--verify verify the provenance data for this chart
--version string version of the chart. By default, the newest chart is shown
```
@ -44,4 +45,4 @@ helm inspect values [CHART] [flags]
* [helm inspect](helm_inspect.md) - inspect a chart
###### Auto generated by spf13/cobra on 1-Aug-2018
###### Auto generated by spf13/cobra on 8-Nov-2018

@ -92,7 +92,8 @@ helm install [CHART] [flags]
--namespace string namespace to install the release into. Defaults to the current kube config namespace.
--no-crd-hook prevent CRD hooks from running, but run other hooks
--no-hooks prevent hooks from running during install
--password string chart repository password where to locate the requested chart
--password string chart repository password
--password-stdin take the chart repository password from stdin
--replace re-use the given name, even if that name is already used. This is unsafe in production
--repo string chart repository url where to locate the requested chart
--set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
@ -105,7 +106,7 @@ helm install [CHART] [flags]
--tls-hostname string the server name used to verify the hostname on the returned certificates from the server
--tls-key string path to TLS key file (default "$HELM_HOME/key.pem")
--tls-verify enable TLS for request and verify remote
--username string chart repository username where to locate the requested chart
--username string chart repository username
-f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default [])
--verify verify the package before installing it
--version string specify the exact chart version to install. If this is not specified, the latest version is installed
@ -128,4 +129,4 @@ helm install [CHART] [flags]
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 10-Aug-2018
###### Auto generated by spf13/cobra on 7-Nov-2018

@ -19,6 +19,7 @@ helm repo add [flags] [NAME] [URL]
--key-file string identify HTTPS client using this SSL key file
--no-update raise error if repo is already registered
--password string chart repository password
--password-stdin take the chart repository password from stdin
--username string chart repository username
```
@ -38,4 +39,4 @@ helm repo add [flags] [NAME] [URL]
* [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories
###### Auto generated by spf13/cobra on 1-Aug-2018
###### Auto generated by spf13/cobra on 30-Oct-2018

@ -19,7 +19,7 @@ To customize the chart values, use any of
- '--set-string' to provide key=val forcing val to be stored as a string,
- '--set-file' to provide key=path to read a single large value from a file at path.
To edit or append to the existing customized values, add the
To edit or append to the existing customized values, add the
'--reuse-values' flag, otherwise any existing customized values are ignored.
If no chart value arguments are provided on the command line, any existing customized values are carried
@ -77,7 +77,8 @@ helm upgrade [RELEASE] [CHART] [flags]
--keyring string path to the keyring that contains public signing keys (default "~/.gnupg/pubring.gpg")
--namespace string namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace
--no-hooks disable pre/post upgrade hooks
--password string chart repository password where to locate the requested chart
--password string chart repository password
--password-stdin take the chart repository password from stdin
--recreate-pods performs pods restart for the resource if applicable
--repo string chart repository url where to locate the requested chart
--reset-values when upgrading, reset the values to the ones built into the chart
@ -92,7 +93,7 @@ helm upgrade [RELEASE] [CHART] [flags]
--tls-hostname string the server name used to verify the hostname on the returned certificates from the server
--tls-key string path to TLS key file (default "$HELM_HOME/key.pem")
--tls-verify enable TLS for request and verify remote
--username string chart repository username where to locate the requested chart
--username string chart repository username
-f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default [])
--verify verify the provenance of the chart before upgrading
--version string specify the exact chart version to use. If this is not specified, the latest version is used
@ -115,4 +116,4 @@ helm upgrade [RELEASE] [CHART] [flags]
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 24-Aug-2018
###### Auto generated by spf13/cobra on 7-Nov-2018

Loading…
Cancel
Save