github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/cli/command/registry/login.go (about) 1 package registry 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "strings" 8 9 "github.com/docker/cli/cli" 10 "github.com/docker/cli/cli/command" 11 configtypes "github.com/docker/cli/cli/config/types" 12 "github.com/docker/docker/api/types" 13 registrytypes "github.com/docker/docker/api/types/registry" 14 "github.com/docker/docker/client" 15 "github.com/docker/docker/registry" 16 "github.com/pkg/errors" 17 "github.com/spf13/cobra" 18 ) 19 20 const unencryptedWarning = `WARNING! Your password will be stored unencrypted in %s. 21 Configure a credential helper to remove this warning. See 22 https://docs.docker.com/engine/reference/commandline/login/#credentials-store 23 ` 24 25 type loginOptions struct { 26 serverAddress string 27 user string 28 password string 29 passwordStdin bool 30 } 31 32 // NewLoginCommand creates a new `docker login` command 33 func NewLoginCommand(dockerCli command.Cli) *cobra.Command { 34 var opts loginOptions 35 36 cmd := &cobra.Command{ 37 Use: "login [OPTIONS] [SERVER]", 38 Short: "Log in to a Docker registry", 39 Long: "Log in to a Docker registry.\nIf no server is specified, the default is defined by the daemon.", 40 Args: cli.RequiresMaxArgs(1), 41 RunE: func(cmd *cobra.Command, args []string) error { 42 if len(args) > 0 { 43 opts.serverAddress = args[0] 44 } 45 return runLogin(dockerCli, opts) 46 }, 47 } 48 49 flags := cmd.Flags() 50 51 flags.StringVarP(&opts.user, "username", "u", "", "Username") 52 flags.StringVarP(&opts.password, "password", "p", "", "Password") 53 flags.BoolVarP(&opts.passwordStdin, "password-stdin", "", false, "Take the password from stdin") 54 55 return cmd 56 } 57 58 // displayUnencryptedWarning warns the user when using an insecure credential storage. 59 // After a deprecation period, user will get prompted if stdin and stderr are a terminal. 60 // Otherwise, we'll assume they want it (sadly), because people may have been scripting 61 // insecure logins and we don't want to break them. Maybe they'll see the warning in their 62 // logs and fix things. 63 func displayUnencryptedWarning(dockerCli command.Streams, filename string) error { 64 _, err := fmt.Fprintln(dockerCli.Err(), fmt.Sprintf(unencryptedWarning, filename)) 65 66 return err 67 } 68 69 type isFileStore interface { 70 IsFileStore() bool 71 GetFilename() string 72 } 73 74 func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error { 75 if opts.password != "" { 76 fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.") 77 if opts.passwordStdin { 78 return errors.New("--password and --password-stdin are mutually exclusive") 79 } 80 } 81 82 if opts.passwordStdin { 83 if opts.user == "" { 84 return errors.New("Must provide --username with --password-stdin") 85 } 86 87 contents, err := ioutil.ReadAll(dockerCli.In()) 88 if err != nil { 89 return err 90 } 91 92 opts.password = strings.TrimSuffix(string(contents), "\n") 93 opts.password = strings.TrimSuffix(opts.password, "\r") 94 } 95 return nil 96 } 97 98 func runLogin(dockerCli command.Cli, opts loginOptions) error { //nolint: gocyclo 99 ctx := context.Background() 100 clnt := dockerCli.Client() 101 if err := verifyloginOptions(dockerCli, &opts); err != nil { 102 return err 103 } 104 var ( 105 serverAddress string 106 authServer = command.ElectAuthServer(ctx, dockerCli) 107 ) 108 if opts.serverAddress != "" && opts.serverAddress != registry.DefaultNamespace { 109 serverAddress = opts.serverAddress 110 } else { 111 serverAddress = authServer 112 } 113 114 var err error 115 var authConfig *types.AuthConfig 116 var response registrytypes.AuthenticateOKBody 117 isDefaultRegistry := serverAddress == authServer 118 authConfig, err = command.GetDefaultAuthConfig(dockerCli, opts.user == "" && opts.password == "", serverAddress, isDefaultRegistry) 119 if err == nil && authConfig.Username != "" && authConfig.Password != "" { 120 response, err = loginWithCredStoreCreds(ctx, dockerCli, authConfig) 121 } 122 if err != nil || authConfig.Username == "" || authConfig.Password == "" { 123 err = command.ConfigureAuth(dockerCli, opts.user, opts.password, authConfig, isDefaultRegistry) 124 if err != nil { 125 return err 126 } 127 128 response, err = clnt.RegistryLogin(ctx, *authConfig) 129 if err != nil && client.IsErrConnectionFailed(err) { 130 // If the server isn't responding (yet) attempt to login purely client side 131 response, err = loginClientSide(ctx, *authConfig) 132 } 133 // If we (still) have an error, give up 134 if err != nil { 135 return err 136 } 137 } 138 if response.IdentityToken != "" { 139 authConfig.Password = "" 140 authConfig.IdentityToken = response.IdentityToken 141 } 142 143 creds := dockerCli.ConfigFile().GetCredentialsStore(serverAddress) 144 145 store, isDefault := creds.(isFileStore) 146 // Display a warning if we're storing the users password (not a token) 147 if isDefault && authConfig.Password != "" { 148 err = displayUnencryptedWarning(dockerCli, store.GetFilename()) 149 if err != nil { 150 return err 151 } 152 } 153 154 if err := creds.Store(configtypes.AuthConfig(*authConfig)); err != nil { 155 return errors.Errorf("Error saving credentials: %v", err) 156 } 157 158 if response.Status != "" { 159 fmt.Fprintln(dockerCli.Out(), response.Status) 160 } 161 return nil 162 } 163 164 func loginWithCredStoreCreds(ctx context.Context, dockerCli command.Cli, authConfig *types.AuthConfig) (registrytypes.AuthenticateOKBody, error) { 165 fmt.Fprintf(dockerCli.Out(), "Authenticating with existing credentials...\n") 166 cliClient := dockerCli.Client() 167 response, err := cliClient.RegistryLogin(ctx, *authConfig) 168 if err != nil { 169 if client.IsErrUnauthorized(err) { 170 fmt.Fprintf(dockerCli.Err(), "Stored credentials invalid or expired\n") 171 } else { 172 fmt.Fprintf(dockerCli.Err(), "Login did not succeed, error: %s\n", err) 173 } 174 } 175 return response, err 176 } 177 178 func loginClientSide(ctx context.Context, auth types.AuthConfig) (registrytypes.AuthenticateOKBody, error) { 179 svc, err := registry.NewService(registry.ServiceOptions{}) 180 if err != nil { 181 return registrytypes.AuthenticateOKBody{}, err 182 } 183 184 status, token, err := svc.Auth(ctx, &auth, command.UserAgent()) 185 186 return registrytypes.AuthenticateOKBody{ 187 Status: status, 188 IdentityToken: token, 189 }, err 190 }