2016-09-08 13:11:39 -04:00
package command
import (
"bufio"
2018-05-03 21:02:44 -04:00
"context"
2016-09-08 13:11:39 -04:00
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"runtime"
"strings"
2018-03-19 18:56:51 -04:00
"github.com/docker/cli/cli/debug"
2019-01-28 08:30:31 -05:00
"github.com/docker/cli/cli/streams"
2017-01-11 16:54:52 -05:00
"github.com/docker/distribution/reference"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2016-09-08 13:11:39 -04:00
)
// ElectAuthServer returns the default registry to use (by asking the daemon)
2017-03-30 20:21:14 -04:00
func ElectAuthServer ( ctx context . Context , cli Cli ) string {
2016-09-08 13:11:39 -04:00
// The daemon `/info` endpoint informs us of the default registry being
// used. This is essential in cross-platforms environment, where for
// example a Linux client might be interacting with a Windows daemon, hence
// the default registry URL might be Windows specific.
serverAddress := registry . IndexServer
2018-03-19 18:56:51 -04:00
if info , err := cli . Client ( ) . Info ( ctx ) ; err != nil && debug . IsEnabled ( ) {
// Only report the warning if we're in debug mode to prevent nagging during engine initialization workflows
2017-05-24 00:45:38 -04:00
fmt . Fprintf ( cli . Err ( ) , "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n" , err , serverAddress )
2018-03-19 18:56:51 -04:00
} else if info . IndexServerAddress == "" && debug . IsEnabled ( ) {
2017-05-24 00:45:38 -04:00
fmt . Fprintf ( cli . Err ( ) , "Warning: Empty registry endpoint from daemon. Using system default: %s\n" , serverAddress )
2016-09-08 13:11:39 -04:00
} else {
serverAddress = info . IndexServerAddress
}
return serverAddress
}
// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
func EncodeAuthToBase64 ( authConfig types . AuthConfig ) ( string , error ) {
buf , err := json . Marshal ( authConfig )
if err != nil {
return "" , err
}
return base64 . URLEncoding . EncodeToString ( buf ) , nil
}
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command.
2017-03-30 20:21:14 -04:00
func RegistryAuthenticationPrivilegedFunc ( cli Cli , index * registrytypes . IndexInfo , cmdName string ) types . RequestPrivilegeFunc {
2016-09-08 13:11:39 -04:00
return func ( ) ( string , error ) {
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( cli . Out ( ) , "\nPlease login prior to %s:\n" , cmdName )
2016-09-08 13:11:39 -04:00
indexServer := registry . GetAuthConfigKey ( index )
2016-09-09 15:38:00 -04:00
isDefaultRegistry := indexServer == ElectAuthServer ( context . Background ( ) , cli )
2017-05-30 17:36:15 -04:00
authConfig , err := GetDefaultAuthConfig ( cli , true , indexServer , isDefaultRegistry )
if err != nil {
fmt . Fprintf ( cli . Err ( ) , "Unable to retrieve stored credentials for %s, error: %s.\n" , indexServer , err )
}
err = ConfigureAuth ( cli , "" , "" , authConfig , isDefaultRegistry )
2016-09-08 13:11:39 -04:00
if err != nil {
return "" , err
}
2017-05-30 17:36:15 -04:00
return EncodeAuthToBase64 ( * authConfig )
2016-09-08 13:11:39 -04:00
}
}
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
// default index, it uses the default index name for the daemon's platform,
// not the client's platform.
2017-03-30 20:21:14 -04:00
func ResolveAuthConfig ( ctx context . Context , cli Cli , index * registrytypes . IndexInfo ) types . AuthConfig {
2016-09-08 13:11:39 -04:00
configKey := index . Name
if index . Official {
2016-09-09 15:38:00 -04:00
configKey = ElectAuthServer ( ctx , cli )
2016-09-08 13:11:39 -04:00
}
2017-06-21 17:20:49 -04:00
a , _ := cli . ConfigFile ( ) . GetAuthConfig ( configKey )
2016-09-08 13:11:39 -04:00
return a
}
2017-05-30 17:36:15 -04: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
func GetDefaultAuthConfig ( cli Cli , checkCredStore bool , serverAddress string , isDefaultRegistry bool ) ( * types . AuthConfig , error ) {
2016-09-08 13:11:39 -04:00
if ! isDefaultRegistry {
serverAddress = registry . ConvertToHostname ( serverAddress )
}
2017-05-30 17:36:15 -04:00
var authconfig types . AuthConfig
var err error
if checkCredStore {
authconfig , err = cli . ConfigFile ( ) . GetAuthConfig ( serverAddress )
} else {
authconfig = types . AuthConfig { }
}
authconfig . ServerAddress = serverAddress
authconfig . IdentityToken = ""
return & authconfig , err
}
2016-09-08 13:11:39 -04:00
2017-05-30 17:36:15 -04:00
// ConfigureAuth handles prompting of user's username and password if needed
func ConfigureAuth ( cli Cli , flUser , flPassword string , authconfig * types . AuthConfig , isDefaultRegistry bool ) error {
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime . GOOS == "windows" {
2019-01-28 08:30:31 -05:00
cli . SetIn ( streams . NewIn ( os . Stdin ) )
2016-09-08 13:11:39 -04:00
}
// Some links documenting this:
// - https://code.google.com/archive/p/mintty/issues/56
// - https://github.com/docker/docker/issues/15272
// - https://mintty.github.io/ (compatibility)
// Linux will hit this if you attempt `cat | docker login`, and Windows
// will hit this if you attempt docker login from mintty where stdin
// is a pipe, not a character based console.
if flPassword == "" && ! cli . In ( ) . IsTerminal ( ) {
2017-05-30 17:36:15 -04:00
return errors . Errorf ( "Error: Cannot perform an interactive login from a non TTY device" )
2016-09-08 13:11:39 -04:00
}
authconfig . Username = strings . TrimSpace ( authconfig . Username )
if flUser = strings . TrimSpace ( flUser ) ; flUser == "" {
if isDefaultRegistry {
// if this is a default registry (docker hub), then display the following message.
2016-09-09 15:38:00 -04:00
fmt . Fprintln ( cli . Out ( ) , "Login with your Docker ID 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." )
2016-09-08 13:11:39 -04:00
}
2016-09-09 15:38:00 -04:00
promptWithDefault ( cli . Out ( ) , "Username" , authconfig . Username )
flUser = readInput ( cli . In ( ) , cli . Out ( ) )
2016-09-08 13:11:39 -04:00
flUser = strings . TrimSpace ( flUser )
if flUser == "" {
flUser = authconfig . Username
}
}
if flUser == "" {
2017-05-30 17:36:15 -04:00
return errors . Errorf ( "Error: Non-null Username Required" )
2016-09-08 13:11:39 -04:00
}
if flPassword == "" {
oldState , err := term . SaveState ( cli . In ( ) . FD ( ) )
if err != nil {
2017-05-30 17:36:15 -04:00
return err
2016-09-08 13:11:39 -04:00
}
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( cli . Out ( ) , "Password: " )
2016-09-08 13:11:39 -04:00
term . DisableEcho ( cli . In ( ) . FD ( ) , oldState )
2016-09-09 15:38:00 -04:00
flPassword = readInput ( cli . In ( ) , cli . Out ( ) )
fmt . Fprint ( cli . Out ( ) , "\n" )
2016-09-08 13:11:39 -04:00
term . RestoreTerminal ( cli . In ( ) . FD ( ) , oldState )
if flPassword == "" {
2017-05-30 17:36:15 -04:00
return errors . Errorf ( "Error: Password Required" )
2016-09-08 13:11:39 -04:00
}
}
authconfig . Username = flUser
authconfig . Password = flPassword
2017-05-30 17:36:15 -04:00
return nil
2016-09-08 13:11:39 -04:00
}
2016-09-09 10:49:52 -04:00
func readInput ( in io . Reader , out io . Writer ) string {
reader := bufio . NewReader ( in )
line , _ , err := reader . ReadLine ( )
2016-09-08 13:11:39 -04:00
if err != nil {
2016-09-09 10:49:52 -04:00
fmt . Fprintln ( out , err . Error ( ) )
os . Exit ( 1 )
2016-09-08 13:11:39 -04:00
}
2016-09-09 10:49:52 -04:00
return string ( line )
}
2016-09-09 15:38:00 -04:00
func promptWithDefault ( out io . Writer , prompt string , configDefault string ) {
2016-09-09 10:49:52 -04:00
if configDefault == "" {
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( out , "%s: " , prompt )
2016-09-09 10:49:52 -04:00
} else {
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( out , "%s (%s): " , prompt , configDefault )
2016-09-08 13:11:39 -04:00
}
}
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
2017-03-30 20:21:14 -04:00
func RetrieveAuthTokenFromImage ( ctx context . Context , cli Cli , image string ) ( string , error ) {
2016-09-08 13:11:39 -04:00
// Retrieve encoded auth token from the image reference
2016-09-09 15:38:00 -04:00
authConfig , err := resolveAuthConfigFromImage ( ctx , cli , image )
2016-09-08 13:11:39 -04:00
if err != nil {
return "" , err
}
encodedAuth , err := EncodeAuthToBase64 ( authConfig )
if err != nil {
return "" , err
}
return encodedAuth , nil
}
2016-09-09 10:49:52 -04:00
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
2017-03-30 20:21:14 -04:00
func resolveAuthConfigFromImage ( ctx context . Context , cli Cli , image string ) ( types . AuthConfig , error ) {
2017-01-11 16:54:52 -05:00
registryRef , err := reference . ParseNormalizedNamed ( image )
2016-09-08 13:11:39 -04:00
if err != nil {
2016-09-09 10:49:52 -04:00
return types . AuthConfig { } , err
2016-09-08 13:11:39 -04:00
}
2016-09-09 10:49:52 -04:00
repoInfo , err := registry . ParseRepositoryInfo ( registryRef )
if err != nil {
return types . AuthConfig { } , err
}
2016-09-09 15:38:00 -04:00
return ResolveAuthConfig ( ctx , cli , repoInfo . Index ) , nil
2016-09-08 13:11:39 -04:00
}