Skip to content

cli/registry/login: Add the --password-env flag #5972

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions cli/command/registry/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type loginOptions struct {
user string
password string
passwordStdin bool
passwordEnv string
}

// NewLoginCommand creates a new `docker login` command
Expand Down Expand Up @@ -56,31 +57,45 @@ func NewLoginCommand(dockerCLI command.Cli) *cobra.Command {
flags.StringVarP(&opts.user, "username", "u", "", "Username")
flags.StringVarP(&opts.password, "password", "p", "", "Password or Personal Access Token (PAT)")
flags.BoolVar(&opts.passwordStdin, "password-stdin", false, "Take the Password or Personal Access Token (PAT) from stdin")
flags.StringVar(&opts.passwordEnv, "password-env", "", "Take the Password or Personal Access Token (PAT) from an environment variable")

return cmd
}

func verifyLoginOptions(dockerCLI command.Cli, opts *loginOptions) error {
if opts.password != "" {
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
if opts.passwordStdin {
return errors.New("--password and --password-stdin are mutually exclusive")
switch {
case opts.password != "":
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin or --password-env.")
if opts.passwordStdin || opts.passwordEnv != "" {
return errors.New("--password, --password-stdin, and --password-env are mutually exclusive")
}
}

if opts.passwordStdin {
case opts.passwordStdin:
if opts.user == "" {
return errors.New("Must provide --username with --password-stdin")
}

if opts.passwordEnv != "" {
return errors.New("--password, --password-stdin, and --password-env are mutually exclusive")
}

contents, err := io.ReadAll(dockerCLI.In())
if err != nil {
return err
}

opts.password = strings.TrimSuffix(string(contents), "\n")
opts.password = strings.TrimSuffix(opts.password, "\r")

case opts.passwordEnv != "":
pw, ok := os.LookupEnv(opts.passwordEnv)
if !ok {
return fmt.Errorf("the environment variable %q is not defined", opts.passwordEnv)
}

opts.password = pw
}

return nil
}

Expand Down
38 changes: 38 additions & 0 deletions cli/command/registry/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func TestRunLogin(t *testing.T) {
testCases := []struct {
doc string
priorCredentials map[string]configtypes.AuthConfig
env map[string]string
input loginOptions
expectedCredentials map[string]configtypes.AuthConfig
expectedErr string
Expand Down Expand Up @@ -286,6 +287,39 @@ func TestRunLogin(t *testing.T) {
},
},
},
// Password from environment
{
doc: "valid password from environment variable",
priorCredentials: map[string]configtypes.AuthConfig{},
env: map[string]string{
"TEST_PASSWORD": "pw0",
},
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
passwordEnv: "TEST_PASSWORD",
},
expectedCredentials: map[string]configtypes.AuthConfig{
"reg1": {
Username: "my-username",
Password: "pw0",
ServerAddress: "reg1",
},
},
},
{
doc: "given environment variable is unset",
priorCredentials: map[string]configtypes.AuthConfig{},
env: map[string]string{
"TEST_PASSWORD": "pw0",
},
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
passwordEnv: "DOES_NOT_EXIST",
},
expectedErr: `the environment variable "DOES_NOT_EXIST" is not defined`,
},
}

for _, tc := range testCases {
Expand All @@ -303,6 +337,10 @@ func TestRunLogin(t *testing.T) {
assert.NilError(t, err)
assert.DeepEqual(t, storedCreds, tc.priorCredentials)

for k, v := range tc.env {
t.Setenv(k, v)
}

loginErr := runLogin(context.Background(), cli, tc.input)
if tc.expectedErr != "" {
assert.Error(t, loginErr, tc.expectedErr)
Expand Down
26 changes: 21 additions & 5 deletions docs/reference/commandline/login.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ Defaults to Docker Hub if no server is specified.

### Options

| Name | Type | Default | Description |
|:---------------------------------------------|:---------|:--------|:------------------------------------------------------------|
| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT) |
| [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin |
| [`-u`](#username), [`--username`](#username) | `string` | | Username |
| Name | Type | Default | Description |
|:---------------------------------------------|:---------|:--------|:------------------------------------------------------------------------------|
| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT) |
| [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin |
| [`--password-env`](#password-env) | `string` | | Take the Password or Personal Access Token (PAT) from an environment variable |
| [`-u`](#username), [`--username`](#username) | `string` | | Username |


<!---MARKER_GEN_END-->
Expand Down Expand Up @@ -244,6 +245,21 @@ The following example reads a password from a file, and passes it to the
$ cat ~/my_password.txt | docker login --username foo --password-stdin
```

### <a name="password-env"></a> Provide a password using an environment variable (--password-env)

To avoid providing a password or access token on the command line, you can set
the `--password-env` flag to provide a password through an environment
variable. Using an environment variable prevents the password from ending up in
the shell's history, or log-files. This is particularly useful for CI
pipelines, that often get access to secrets through environment variables.

The following example authenticates to a registry using an access token stored
in the `DOCKER_TOKEN` environment variable:

```console
$ docker login --username foo --password-env "DOCKER_TOKEN"
```

## Related commands

* [logout](logout.md)