github.com/olljanat/moby@v1.13.1/cli/command/registry.go (about)

     1  package command
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"runtime"
    11  	"strings"
    12  
    13  	"golang.org/x/net/context"
    14  
    15  	"github.com/docker/docker/api/types"
    16  	registrytypes "github.com/docker/docker/api/types/registry"
    17  	"github.com/docker/docker/pkg/term"
    18  	"github.com/docker/docker/reference"
    19  	"github.com/docker/docker/registry"
    20  )
    21  
    22  // ElectAuthServer returns the default registry to use (by asking the daemon)
    23  func ElectAuthServer(ctx context.Context, cli *DockerCli) string {
    24  	// The daemon `/info` endpoint informs us of the default registry being
    25  	// used. This is essential in cross-platforms environment, where for
    26  	// example a Linux client might be interacting with a Windows daemon, hence
    27  	// the default registry URL might be Windows specific.
    28  	serverAddress := registry.IndexServer
    29  	if info, err := cli.Client().Info(ctx); err != nil {
    30  		fmt.Fprintf(cli.Out(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
    31  	} else {
    32  		serverAddress = info.IndexServerAddress
    33  	}
    34  	return serverAddress
    35  }
    36  
    37  // EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
    38  func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
    39  	buf, err := json.Marshal(authConfig)
    40  	if err != nil {
    41  		return "", err
    42  	}
    43  	return base64.URLEncoding.EncodeToString(buf), nil
    44  }
    45  
    46  // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
    47  // for the given command.
    48  func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
    49  	return func() (string, error) {
    50  		fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
    51  		indexServer := registry.GetAuthConfigKey(index)
    52  		isDefaultRegistry := indexServer == ElectAuthServer(context.Background(), cli)
    53  		authConfig, err := ConfigureAuth(cli, "", "", indexServer, isDefaultRegistry)
    54  		if err != nil {
    55  			return "", err
    56  		}
    57  		return EncodeAuthToBase64(authConfig)
    58  	}
    59  }
    60  
    61  // ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
    62  // default index, it uses the default index name for the daemon's platform,
    63  // not the client's platform.
    64  func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes.IndexInfo) types.AuthConfig {
    65  	configKey := index.Name
    66  	if index.Official {
    67  		configKey = ElectAuthServer(ctx, cli)
    68  	}
    69  
    70  	a, _ := cli.CredentialsStore(configKey).Get(configKey)
    71  	return a
    72  }
    73  
    74  // ConfigureAuth returns an AuthConfig from the specified user, password and server.
    75  func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
    76  	// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
    77  	if runtime.GOOS == "windows" {
    78  		cli.in = NewInStream(os.Stdin)
    79  	}
    80  
    81  	if !isDefaultRegistry {
    82  		serverAddress = registry.ConvertToHostname(serverAddress)
    83  	}
    84  
    85  	authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
    86  	if err != nil {
    87  		return authconfig, err
    88  	}
    89  
    90  	// Some links documenting this:
    91  	// - https://code.google.com/archive/p/mintty/issues/56
    92  	// - https://github.com/docker/docker/issues/15272
    93  	// - https://mintty.github.io/ (compatibility)
    94  	// Linux will hit this if you attempt `cat | docker login`, and Windows
    95  	// will hit this if you attempt docker login from mintty where stdin
    96  	// is a pipe, not a character based console.
    97  	if flPassword == "" && !cli.In().IsTerminal() {
    98  		return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device")
    99  	}
   100  
   101  	authconfig.Username = strings.TrimSpace(authconfig.Username)
   102  
   103  	if flUser = strings.TrimSpace(flUser); flUser == "" {
   104  		if isDefaultRegistry {
   105  			// if this is a default registry (docker hub), then display the following message.
   106  			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.")
   107  		}
   108  		promptWithDefault(cli.Out(), "Username", authconfig.Username)
   109  		flUser = readInput(cli.In(), cli.Out())
   110  		flUser = strings.TrimSpace(flUser)
   111  		if flUser == "" {
   112  			flUser = authconfig.Username
   113  		}
   114  	}
   115  	if flUser == "" {
   116  		return authconfig, fmt.Errorf("Error: Non-null Username Required")
   117  	}
   118  	if flPassword == "" {
   119  		oldState, err := term.SaveState(cli.In().FD())
   120  		if err != nil {
   121  			return authconfig, err
   122  		}
   123  		fmt.Fprintf(cli.Out(), "Password: ")
   124  		term.DisableEcho(cli.In().FD(), oldState)
   125  
   126  		flPassword = readInput(cli.In(), cli.Out())
   127  		fmt.Fprint(cli.Out(), "\n")
   128  
   129  		term.RestoreTerminal(cli.In().FD(), oldState)
   130  		if flPassword == "" {
   131  			return authconfig, fmt.Errorf("Error: Password Required")
   132  		}
   133  	}
   134  
   135  	authconfig.Username = flUser
   136  	authconfig.Password = flPassword
   137  	authconfig.ServerAddress = serverAddress
   138  	authconfig.IdentityToken = ""
   139  
   140  	return authconfig, nil
   141  }
   142  
   143  func readInput(in io.Reader, out io.Writer) string {
   144  	reader := bufio.NewReader(in)
   145  	line, _, err := reader.ReadLine()
   146  	if err != nil {
   147  		fmt.Fprintln(out, err.Error())
   148  		os.Exit(1)
   149  	}
   150  	return string(line)
   151  }
   152  
   153  func promptWithDefault(out io.Writer, prompt string, configDefault string) {
   154  	if configDefault == "" {
   155  		fmt.Fprintf(out, "%s: ", prompt)
   156  	} else {
   157  		fmt.Fprintf(out, "%s (%s): ", prompt, configDefault)
   158  	}
   159  }
   160  
   161  // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
   162  func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image string) (string, error) {
   163  	// Retrieve encoded auth token from the image reference
   164  	authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
   165  	if err != nil {
   166  		return "", err
   167  	}
   168  	encodedAuth, err := EncodeAuthToBase64(authConfig)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	return encodedAuth, nil
   173  }
   174  
   175  // resolveAuthConfigFromImage retrieves that AuthConfig using the image string
   176  func resolveAuthConfigFromImage(ctx context.Context, cli *DockerCli, image string) (types.AuthConfig, error) {
   177  	registryRef, err := reference.ParseNamed(image)
   178  	if err != nil {
   179  		return types.AuthConfig{}, err
   180  	}
   181  	repoInfo, err := registry.ParseRepositoryInfo(registryRef)
   182  	if err != nil {
   183  		return types.AuthConfig{}, err
   184  	}
   185  	return ResolveAuthConfig(ctx, cli, repoInfo.Index), nil
   186  }