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