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