From eeedf80d4362301578b5ad8de03f614bb3ec7397 Mon Sep 17 00:00:00 2001 From: Alexis Girault Date: Wed, 3 Jun 2026 21:57:23 -0400 Subject: [PATCH] cli/registry: support password dash stdin Signed-off-by: Alexis Girault --- cli/command/registry/login.go | 11 +++-- cli/command/registry/login_test.go | 75 ++++++++++++++++++++++++++++- docs/reference/commandline/login.md | 9 +++- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index e7f590976580..471e73f8238e 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -62,7 +62,7 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command { flags := cmd.Flags() flags.StringVarP(&opts.user, "username", "u", "", "Username") - flags.StringVarP(&opts.password, "password", "p", "", "Password or Personal Access Token (PAT)") + flags.StringVarP(&opts.password, "password", "p", "", `Password or Personal Access Token (PAT), or "-" to read from stdin`) flags.BoolVar(&opts.passwordStdin, "password-stdin", false, "Take the Password or Personal Access Token (PAT) from stdin") return cmd @@ -72,8 +72,8 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command { // // TODO(thaJeztah); combine with verifyLoginOptions, but this requires rewrites of many tests. func verifyLoginFlags(flags *pflag.FlagSet, opts loginOptions) error { - if flags.Changed("password-stdin") { - if flags.Changed("password") { + if flags.Changed("password-stdin") || opts.password == "-" { + if flags.Changed("password") && opts.password != "-" { return errors.New("conflicting options: cannot specify both --password and --password-stdin") } if !flags.Changed("username") { @@ -122,6 +122,11 @@ func readSecretFromStdin(r io.Reader) (string, error) { } func verifyLoginOptions(dockerCLI command.Streams, opts *loginOptions) error { + if opts.password == "-" { + opts.password = "" + opts.passwordStdin = true + } + if opts.password != "" { _, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.") } diff --git a/cli/command/registry/login_test.go b/cli/command/registry/login_test.go index b392dab444a9..32d0e6f8cf7c 100644 --- a/cli/command/registry/login_test.go +++ b/cli/command/registry/login_test.go @@ -339,6 +339,57 @@ func TestRunLogin(t *testing.T) { }, }, }, + { + doc: "password dash reads password from stdin", + priorCredentials: map[string]configtypes.AuthConfig{}, + stdIn: "my password\r\n", + input: loginOptions{ + serverAddress: "reg1", + user: "my-username", + password: "-", + }, + expectedCredentials: map[string]configtypes.AuthConfig{ + "reg1": { + Username: "my-username", + Password: "my password", + ServerAddress: "reg1", + }, + }, + }, + { + doc: "password dash empty stdin", + priorCredentials: map[string]configtypes.AuthConfig{}, + input: loginOptions{ + serverAddress: "reg1", + user: "my-username", + password: "-", + }, + expectedErr: `password is empty`, + expectedCredentials: map[string]configtypes.AuthConfig{ + "reg1": { + Username: "my-username", + ServerAddress: "reg1", + }, + }, + }, + { + doc: "password dash and password stdin read password from stdin", + priorCredentials: map[string]configtypes.AuthConfig{}, + stdIn: "my password\r\n", + input: loginOptions{ + serverAddress: "reg1", + user: "my-username", + password: "-", + passwordStdin: true, + }, + expectedCredentials: map[string]configtypes.AuthConfig{ + "reg1": { + Username: "my-username", + Password: "my password", + ServerAddress: "reg1", + }, + }, + }, { doc: "password with leading and trailing spaces", priorCredentials: map[string]configtypes.AuthConfig{}, @@ -397,7 +448,7 @@ func TestRunLogin(t *testing.T) { cfg := configfile.New(filepath.Join(tmpDir, "config.json")) cli := test.NewFakeCli(&fakeClient{}) cli.SetConfigFile(cfg) - if tc.input.passwordStdin { + if tc.input.passwordStdin || tc.input.password == "-" || tc.stdIn != "" { if tc.expectedErr == "TEST_READ_ERR" { cli.SetIn(streams.NewIn(io.NopCloser(iotest.ErrReader(errors.New(tc.expectedErr))))) } else { @@ -632,6 +683,21 @@ func TestLoginValidateFlags(t *testing.T) { args: []string{"--password-stdin", "--password", ""}, expectedErr: `conflicting options: cannot specify both --password and --password-stdin`, }, + { + name: "password stdin and password dash without stdin", + args: []string{"--password-stdin", "--username", "my-username", "--password", "-"}, + expectedErr: `password is empty`, + }, + { + name: "password dash without username", + args: []string{"--password", "-"}, + expectedErr: `the --password-stdin option requires --username to be set`, + }, + { + name: "short password dash without username", + args: []string{"-p", "-"}, + expectedErr: `the --password-stdin option requires --username to be set`, + }, { name: "empty --password", args: []string{"--password", ""}, @@ -658,3 +724,10 @@ func TestLoginValidateFlags(t *testing.T) { }) } } + +func TestLoginHelpDocumentsPasswordDash(t *testing.T) { + cmd := newLoginCommand(test.NewFakeCli(&fakeClient{})) + flag := cmd.Flags().Lookup("password") + assert.Check(t, flag != nil) + assert.Check(t, is.Contains(flag.Usage, `"-"`)) +} diff --git a/docs/reference/commandline/login.md b/docs/reference/commandline/login.md index 95efbc9de245..971ae718d89d 100644 --- a/docs/reference/commandline/login.md +++ b/docs/reference/commandline/login.md @@ -8,7 +8,7 @@ Defaults to Docker Hub if no server is specified. | Name | Type | Default | Description | |:---------------------------------------------|:---------|:--------|:------------------------------------------------------------| -| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT) | +| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT), or `-` to read from stdin | | [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin | | [`-u`](#username), [`--username`](#username) | `string` | | Username | @@ -244,6 +244,13 @@ The following example reads a password from a file, and passes it to the $ cat ~/my_password.txt | docker login --username foo --password-stdin ``` +You can also pass `-` as the value for `--password` or `-p` to read the +password from `STDIN`. + +```console +$ cat ~/my_password.txt | docker login --username foo --password - +``` + ## Related commands * [logout](logout.md)