2016-09-08 13:11:39 -04:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2024-05-10 15:17:56 +02:00
|
|
|
"context"
|
2016-09-08 13:11:39 -04:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
|
2023-08-31 00:36:58 +02:00
|
|
|
"github.com/distribution/reference"
|
2023-07-10 17:24:07 +02:00
|
|
|
"github.com/docker/cli/cli/config/configfile"
|
2022-02-26 20:10:38 +01:00
|
|
|
"github.com/docker/cli/cli/config/credentials"
|
2017-10-15 21:39:56 +02:00
|
|
|
configtypes "github.com/docker/cli/cli/config/types"
|
2023-07-28 10:26:48 +02:00
|
|
|
"github.com/docker/cli/cli/hints"
|
2019-01-28 14:30:31 +01:00
|
|
|
"github.com/docker/cli/cli/streams"
|
2025-03-22 13:24:25 +01:00
|
|
|
"github.com/docker/cli/internal/prompt"
|
2025-01-27 12:08:55 +01:00
|
|
|
"github.com/docker/cli/internal/tui"
|
2016-09-08 13:11:39 -04:00
|
|
|
registrytypes "github.com/docker/docker/api/types/registry"
|
2025-01-27 12:08:55 +01:00
|
|
|
"github.com/morikuni/aec"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
2024-10-19 13:23:29 +02:00
|
|
|
const (
|
|
|
|
registerSuggest = "Log in with your Docker ID or email address to push and pull images from Docker Hub. " +
|
|
|
|
"If you don't have a Docker ID, head over to https://hub.docker.com/ to create one."
|
|
|
|
patSuggest = "You can log in with your password or a Personal Access " +
|
|
|
|
"Token (PAT). Using a limited-scope PAT grants better security and is required " +
|
|
|
|
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
|
|
|
|
)
|
2023-07-28 10:26:48 +02:00
|
|
|
|
2025-03-26 11:42:25 +01:00
|
|
|
// authConfigKey is the key used to store credentials for Docker Hub. It is
|
|
|
|
// a copy of [registry.IndexServer].
|
|
|
|
//
|
|
|
|
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer
|
2025-03-26 14:43:57 +01:00
|
|
|
const authConfigKey = "https://index.docker.io/v1/"
|
2025-03-26 11:42:25 +01:00
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
|
|
|
// for the given command.
|
2024-09-12 19:18:46 +02:00
|
|
|
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
2025-03-26 11:42:25 +01:00
|
|
|
configKey := getAuthConfigKey(index.Name)
|
|
|
|
isDefaultRegistry := configKey == authConfigKey || index.Official
|
2024-05-10 15:17:56 +02:00
|
|
|
return func(ctx context.Context) (string, error) {
|
2024-10-19 12:52:32 +02:00
|
|
|
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
2025-03-26 11:42:25 +01:00
|
|
|
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, configKey, isDefaultRegistry)
|
2017-05-30 14:36:15 -07:00
|
|
|
if err != nil {
|
2025-03-26 11:42:25 +01:00
|
|
|
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", authConfigKey, err)
|
2017-05-30 14:36:15 -07:00
|
|
|
}
|
2024-05-10 15:17:56 +02:00
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return "", ctx.Err()
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
2025-03-26 11:42:25 +01:00
|
|
|
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, authConfigKey)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2023-04-11 18:16:30 +02:00
|
|
|
return registrytypes.EncodeAuthConfig(authConfig)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-12 10:23:47 +02:00
|
|
|
// ResolveAuthConfig returns auth-config for the given registry from the
|
|
|
|
// credential-store. It returns an empty AuthConfig if no credentials were
|
|
|
|
// found.
|
|
|
|
//
|
|
|
|
// It is similar to [registry.ResolveAuthConfig], but uses the credentials-
|
|
|
|
// store, instead of looking up credentials from a map.
|
2023-07-10 17:24:07 +02:00
|
|
|
func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig {
|
2016-09-08 13:11:39 -04:00
|
|
|
configKey := index.Name
|
|
|
|
if index.Official {
|
2025-03-26 11:42:25 +01:00
|
|
|
configKey = authConfigKey
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2023-07-10 17:24:07 +02:00
|
|
|
a, _ := cfg.GetAuthConfig(configKey)
|
2023-02-08 02:31:59 +01:00
|
|
|
return registrytypes.AuthConfig(a)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-05-30 14:36:15 -07:00
|
|
|
// GetDefaultAuthConfig gets the default auth config given a serverAddress
|
|
|
|
// If credentials for given serverAddress exists in the credential store, the configuration will be populated with values in it
|
2023-07-10 17:24:07 +02:00
|
|
|
func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serverAddress string, isDefaultRegistry bool) (registrytypes.AuthConfig, error) {
|
2016-09-08 13:11:39 -04:00
|
|
|
if !isDefaultRegistry {
|
2022-02-26 20:10:38 +01:00
|
|
|
serverAddress = credentials.ConvertToHostname(serverAddress)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2022-09-29 17:21:51 +02:00
|
|
|
authconfig := configtypes.AuthConfig{}
|
2017-05-30 14:36:15 -07:00
|
|
|
var err error
|
|
|
|
if checkCredStore {
|
2023-07-10 17:24:07 +02:00
|
|
|
authconfig, err = cfg.GetAuthConfig(serverAddress)
|
2020-10-29 01:39:30 +01:00
|
|
|
if err != nil {
|
2023-02-08 02:31:59 +01:00
|
|
|
return registrytypes.AuthConfig{
|
2021-07-21 17:59:42 -07:00
|
|
|
ServerAddress: serverAddress,
|
|
|
|
}, err
|
2020-10-29 01:39:30 +01:00
|
|
|
}
|
2017-05-30 14:36:15 -07:00
|
|
|
}
|
|
|
|
authconfig.ServerAddress = serverAddress
|
|
|
|
authconfig.IdentityToken = ""
|
2023-07-10 17:24:07 +02:00
|
|
|
return registrytypes.AuthConfig(authconfig), nil
|
2017-05-30 14:36:15 -07:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2024-07-08 23:48:13 +01:00
|
|
|
// ConfigureAuth handles prompting of user's username and password if needed.
|
2024-10-19 13:05:31 +02:00
|
|
|
//
|
|
|
|
// Deprecated: use [PromptUserForCredentials] instead.
|
2024-07-08 23:48:13 +01:00
|
|
|
func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, authConfig *registrytypes.AuthConfig, _ bool) error {
|
|
|
|
defaultUsername := authConfig.Username
|
|
|
|
serverAddress := authConfig.ServerAddress
|
|
|
|
|
|
|
|
newAuthConfig, err := PromptUserForCredentials(ctx, cli, flUser, flPassword, defaultUsername, serverAddress)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
authConfig.Username = newAuthConfig.Username
|
|
|
|
authConfig.Password = newAuthConfig.Password
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PromptUserForCredentials handles the CLI prompt for the user to input
|
|
|
|
// credentials.
|
|
|
|
// If argUser is not empty, then the user is only prompted for their password.
|
|
|
|
// If argPassword is not empty, then the user is only prompted for their username
|
|
|
|
// If neither argUser nor argPassword are empty, then the user is not prompted and
|
|
|
|
// an AuthConfig is returned with those values.
|
|
|
|
// If defaultUsername is not empty, the username prompt includes that username
|
|
|
|
// and the user can hit enter without inputting a username to use that default
|
|
|
|
// username.
|
2024-10-19 11:46:21 +02:00
|
|
|
func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword, defaultUsername, serverAddress string) (registrytypes.AuthConfig, error) {
|
2023-04-12 10:29:27 +02:00
|
|
|
// On Windows, force the use of the regular OS stdin stream.
|
|
|
|
//
|
|
|
|
// See:
|
|
|
|
// - https://github.com/moby/moby/issues/14336
|
|
|
|
// - https://github.com/moby/moby/issues/14210
|
|
|
|
// - https://github.com/moby/moby/pull/17738
|
|
|
|
//
|
|
|
|
// TODO(thaJeztah): we need to confirm if this special handling is still needed, as we may not be doing this in other places.
|
2017-05-30 14:36:15 -07:00
|
|
|
if runtime.GOOS == "windows" {
|
2019-01-28 14:30:31 +01:00
|
|
|
cli.SetIn(streams.NewIn(os.Stdin))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2024-10-19 12:07:51 +02:00
|
|
|
argUser = strings.TrimSpace(argUser)
|
|
|
|
if argUser == "" {
|
2025-03-26 11:42:25 +01:00
|
|
|
if serverAddress == authConfigKey {
|
2024-10-19 13:23:29 +02:00
|
|
|
// When signing in to the default (Docker Hub) registry, we display
|
|
|
|
// hints for creating an account, and (if hints are enabled), using
|
|
|
|
// a token instead of a password.
|
|
|
|
_, _ = fmt.Fprintln(cli.Out(), registerSuggest)
|
2023-07-28 10:26:48 +02:00
|
|
|
if hints.Enabled() {
|
2024-10-19 12:52:32 +02:00
|
|
|
_, _ = fmt.Fprintln(cli.Out(), patSuggest)
|
|
|
|
_, _ = fmt.Fprintln(cli.Out())
|
2023-07-28 10:26:48 +02:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2024-06-18 14:18:49 +02:00
|
|
|
|
2025-03-22 13:24:25 +01:00
|
|
|
var msg string
|
2024-10-19 12:07:51 +02:00
|
|
|
defaultUsername = strings.TrimSpace(defaultUsername)
|
2024-07-08 23:48:13 +01:00
|
|
|
if defaultUsername == "" {
|
2025-03-22 13:24:25 +01:00
|
|
|
msg = "Username: "
|
2024-06-18 14:18:49 +02:00
|
|
|
} else {
|
2025-03-22 13:24:25 +01:00
|
|
|
msg = fmt.Sprintf("Username (%s): ", defaultUsername)
|
2024-06-18 14:18:49 +02:00
|
|
|
}
|
2024-10-19 11:46:21 +02:00
|
|
|
|
|
|
|
var err error
|
2025-03-22 13:24:25 +01:00
|
|
|
argUser, err = prompt.ReadInput(ctx, cli.In(), cli.Out(), msg)
|
2023-04-12 21:10:13 +02:00
|
|
|
if err != nil {
|
2024-10-19 11:46:21 +02:00
|
|
|
return registrytypes.AuthConfig{}, err
|
2023-04-12 21:10:13 +02:00
|
|
|
}
|
2024-07-08 23:48:13 +01:00
|
|
|
if argUser == "" {
|
|
|
|
argUser = defaultUsername
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2024-10-19 12:01:25 +02:00
|
|
|
if argUser == "" {
|
|
|
|
return registrytypes.AuthConfig{}, errors.Errorf("Error: Non-null Username Required")
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2024-10-19 12:01:25 +02:00
|
|
|
|
2024-10-19 12:10:46 +02:00
|
|
|
argPassword = strings.TrimSpace(argPassword)
|
2024-07-08 23:48:13 +01:00
|
|
|
if argPassword == "" {
|
2025-03-22 13:24:25 +01:00
|
|
|
restoreInput, err := prompt.DisableInputEcho(cli.In())
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2024-10-19 11:46:21 +02:00
|
|
|
return registrytypes.AuthConfig{}, err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2024-10-19 12:49:44 +02:00
|
|
|
defer func() {
|
|
|
|
if err := restoreInput(); err != nil {
|
|
|
|
// TODO(thaJeztah): we should consider printing instructions how
|
|
|
|
// to restore this manually (other than restarting the shell).
|
|
|
|
// e.g., 'run stty echo' when in a Linux or macOS shell, but
|
|
|
|
// PowerShell and CMD.exe may need different instructions.
|
|
|
|
_, _ = fmt.Fprintln(cli.Err(), "Error: failed to restore terminal state to echo input:", err)
|
|
|
|
}
|
|
|
|
}()
|
2024-06-18 14:18:49 +02:00
|
|
|
|
2025-04-16 17:17:23 +02:00
|
|
|
if serverAddress == authConfigKey {
|
|
|
|
out := tui.NewOutput(cli.Err())
|
|
|
|
out.PrintNote("A Personal Access Token (PAT) can be used instead.\n" +
|
|
|
|
"To create a PAT, visit " + aec.Underline.Apply("https://app.docker.com/settings") + "\n\n")
|
|
|
|
}
|
|
|
|
|
2025-03-22 13:24:25 +01:00
|
|
|
argPassword, err = prompt.ReadInput(ctx, cli.In(), cli.Out(), "Password: ")
|
2023-04-12 21:10:13 +02:00
|
|
|
if err != nil {
|
2024-10-19 11:46:21 +02:00
|
|
|
return registrytypes.AuthConfig{}, err
|
2023-04-12 21:10:13 +02:00
|
|
|
}
|
2024-10-19 12:52:32 +02:00
|
|
|
_, _ = fmt.Fprintln(cli.Out())
|
2024-07-08 23:48:13 +01:00
|
|
|
if argPassword == "" {
|
2024-10-19 11:46:21 +02:00
|
|
|
return registrytypes.AuthConfig{}, errors.Errorf("Error: Password Required")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-19 11:46:21 +02:00
|
|
|
return registrytypes.AuthConfig{
|
|
|
|
Username: argUser,
|
|
|
|
Password: argPassword,
|
|
|
|
ServerAddress: serverAddress,
|
|
|
|
}, nil
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2023-04-11 18:16:30 +02:00
|
|
|
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete
|
|
|
|
// image. The auth configuration is serialized as a base64url encoded RFC4648,
|
|
|
|
// section 5) JSON string for sending through the X-Registry-Auth header.
|
|
|
|
//
|
|
|
|
// For details on base64url encoding, see:
|
|
|
|
// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
|
2023-07-10 17:24:07 +02:00
|
|
|
func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (string, error) {
|
2016-09-08 13:11:39 -04:00
|
|
|
// Retrieve encoded auth token from the image reference
|
2023-07-10 17:24:07 +02:00
|
|
|
authConfig, err := resolveAuthConfigFromImage(cfg, image)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2023-04-11 18:16:30 +02:00
|
|
|
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return encodedAuth, nil
|
|
|
|
}
|
|
|
|
|
2016-09-09 10:49:52 -04:00
|
|
|
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
|
2023-07-10 17:24:07 +02:00
|
|
|
func resolveAuthConfigFromImage(cfg *configfile.ConfigFile, image string) (registrytypes.AuthConfig, error) {
|
2017-01-11 13:54:52 -08:00
|
|
|
registryRef, err := reference.ParseNormalizedNamed(image)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2023-02-08 02:31:59 +01:00
|
|
|
return registrytypes.AuthConfig{}, err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2025-03-26 11:42:25 +01:00
|
|
|
configKey := getAuthConfigKey(reference.Domain(registryRef))
|
|
|
|
a, err := cfg.GetAuthConfig(configKey)
|
2016-09-09 10:49:52 -04:00
|
|
|
if err != nil {
|
2023-02-08 02:31:59 +01:00
|
|
|
return registrytypes.AuthConfig{}, err
|
2016-09-09 10:49:52 -04:00
|
|
|
}
|
2025-03-26 11:42:25 +01:00
|
|
|
return registrytypes.AuthConfig(a), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAuthConfigKey special-cases using the full index address of the official
|
|
|
|
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
|
|
|
//
|
|
|
|
// It is similar to [registry.GetAuthConfigKey], but does not require on
|
|
|
|
// [registrytypes.IndexInfo] as intermediate.
|
|
|
|
//
|
|
|
|
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker/registry#GetAuthConfigKey
|
|
|
|
// [registrytypes.IndexInfo]:https://pkg.go.dev/github.com/docker/docker/api/types/registry#IndexInfo
|
|
|
|
func getAuthConfigKey(domainName string) string {
|
|
|
|
if domainName == "docker.io" || domainName == "index.docker.io" {
|
|
|
|
return authConfigKey
|
|
|
|
}
|
|
|
|
return domainName
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|