github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/login.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "strings" 8 9 buildahcli "github.com/containers/buildah/pkg/cli" 10 "github.com/containers/image/v5/docker" 11 "github.com/containers/image/v5/pkg/docker/config" 12 "github.com/containers/image/v5/types" 13 "github.com/containers/libpod/cmd/podman/cliconfig" 14 "github.com/containers/libpod/libpod/image" 15 "github.com/containers/libpod/pkg/registries" 16 "github.com/docker/docker-credential-helpers/credentials" 17 "github.com/pkg/errors" 18 "github.com/sirupsen/logrus" 19 "github.com/spf13/cobra" 20 "golang.org/x/crypto/ssh/terminal" 21 ) 22 23 var ( 24 loginCommand cliconfig.LoginValues 25 26 loginDescription = "Login to a container registry on a specified server." 27 _loginCommand = &cobra.Command{ 28 Use: "login [flags] REGISTRY", 29 Short: "Login to a container registry", 30 Long: loginDescription, 31 RunE: func(cmd *cobra.Command, args []string) error { 32 loginCommand.InputArgs = args 33 loginCommand.GlobalFlags = MainGlobalOpts 34 loginCommand.Remote = remoteclient 35 return loginCmd(&loginCommand) 36 }, 37 Example: `podman login -u testuser -p testpassword localhost:5000 38 podman login -u testuser -p testpassword localhost:5000`, 39 } 40 ) 41 42 func init() { 43 if !remote { 44 _loginCommand.Example = fmt.Sprintf("%s\n podman login --authfile authdir/myauths.json quay.io", _loginCommand.Example) 45 46 } 47 loginCommand.Command = _loginCommand 48 loginCommand.SetHelpTemplate(HelpTemplate()) 49 loginCommand.SetUsageTemplate(UsageTemplate()) 50 flags := loginCommand.Flags() 51 52 flags.BoolVar(&loginCommand.GetLogin, "get-login", true, "Return the current login user for the registry") 53 flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry") 54 flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry") 55 flags.BoolVar(&loginCommand.StdinPassword, "password-stdin", false, "Take the password from stdin") 56 // Disabled flags for the remote client 57 if !remote { 58 flags.StringVar(&loginCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") 59 flags.StringVar(&loginCommand.CertDir, "cert-dir", "", "Pathname of a directory containing TLS certificates and keys used to connect to the registry") 60 flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") 61 } 62 } 63 64 // loginCmd uses the authentication package to store a user's authenticated credentials 65 // in an auth.json file for future use 66 func loginCmd(c *cliconfig.LoginValues) error { 67 args := c.InputArgs 68 if len(args) > 1 { 69 return errors.Errorf("too many arguments, login takes only 1 argument") 70 } 71 var server string 72 if len(args) == 0 { 73 registriesFromFile, err := registries.GetRegistries() 74 if err != nil || len(registriesFromFile) == 0 { 75 return errors.Errorf("please specify a registry to login to") 76 } 77 78 server = registriesFromFile[0] 79 logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", server) 80 81 } else { 82 server = registryFromFullName(scrubServer(args[0])) 83 } 84 85 if c.Flag("password").Changed { 86 fmt.Fprintf(os.Stderr, "WARNING! Using --password via the cli is insecure. Please consider using --password-stdin\n") 87 } 88 89 sc := image.GetSystemContext("", c.Authfile, false) 90 if c.Flag("tls-verify").Changed { 91 sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) 92 } 93 if c.CertDir != "" { 94 sc.DockerCertPath = c.CertDir 95 } 96 97 // username of user logged in to server (if one exists) 98 authConfig, err := config.GetCredentials(sc, server) 99 // Do not return error if no credentials found in credHelpers, new credentials will be stored by config.SetAuthentication 100 if err != nil && err != credentials.NewErrCredentialsNotFound() { 101 return errors.Wrapf(err, "error reading auth file") 102 } 103 if authConfig.IdentityToken != "" { 104 return errors.Errorf("currently logged in, auth file contains an Identity token") 105 } 106 if c.Flag("get-login").Changed { 107 if authConfig.Username == "" { 108 return errors.Errorf("not logged into %s", server) 109 } 110 fmt.Printf("%s\n", authConfig.Username) 111 return nil 112 } 113 114 ctx := getContext() 115 116 password := c.Password 117 118 if c.Flag("password-stdin").Changed { 119 var stdinPasswordStrBuilder strings.Builder 120 if c.Password != "" { 121 return errors.Errorf("Can't specify both --password-stdin and --password") 122 } 123 if c.Username == "" { 124 return errors.Errorf("Must provide --username with --password-stdin") 125 } 126 scanner := bufio.NewScanner(os.Stdin) 127 for scanner.Scan() { 128 fmt.Fprint(&stdinPasswordStrBuilder, scanner.Text()) 129 } 130 password = stdinPasswordStrBuilder.String() 131 } 132 133 // If no username and no password is specified, try to use existing ones. 134 if c.Username == "" && password == "" && authConfig.Username == "" && authConfig.Password != "" { 135 fmt.Println("Authenticating with existing credentials...") 136 if err := docker.CheckAuth(ctx, sc, authConfig.Username, authConfig.Password, server); err == nil { 137 fmt.Println("Existing credentials are valid. Already logged in to", server) 138 return nil 139 } 140 fmt.Println("Existing credentials are invalid, please enter valid username and password") 141 } 142 143 username, password, err := getUserAndPass(c.Username, password, authConfig.Username) 144 if err != nil { 145 return errors.Wrapf(err, "error getting username and password") 146 } 147 148 if err = docker.CheckAuth(ctx, sc, username, password, server); err == nil { 149 // Write the new credentials to the authfile 150 if err = config.SetAuthentication(sc, server, username, password); err != nil { 151 return err 152 } 153 } 154 if err == nil { 155 fmt.Println("Login Succeeded!") 156 return nil 157 } 158 if unauthorizedError, ok := err.(docker.ErrUnauthorizedForCredentials); ok { 159 logrus.Debugf("error logging into %q: %v", server, unauthorizedError) 160 return errors.Errorf("error logging into %q: invalid username/password", server) 161 } 162 return errors.Wrapf(err, "error authenticating creds for %q", server) 163 } 164 165 // getUserAndPass gets the username and password from STDIN if not given 166 // using the -u and -p flags. If the username prompt is left empty, the 167 // displayed userFromAuthFile will be used instead. 168 func getUserAndPass(username, password, userFromAuthFile string) (string, string, error) { 169 var err error 170 reader := bufio.NewReader(os.Stdin) 171 if username == "" { 172 if userFromAuthFile != "" { 173 fmt.Printf("Username (%s): ", userFromAuthFile) 174 } else { 175 fmt.Print("Username: ") 176 } 177 username, err = reader.ReadString('\n') 178 if err != nil { 179 return "", "", errors.Wrapf(err, "error reading username") 180 } 181 // If the user just hit enter, use the displayed user from the 182 // the authentication file. This allows to do a lazy 183 // `$ podman login -p $NEW_PASSWORD` without specifying the 184 // user. 185 if strings.TrimSpace(username) == "" { 186 username = userFromAuthFile 187 } 188 } 189 if password == "" { 190 fmt.Print("Password: ") 191 pass, err := terminal.ReadPassword(0) 192 if err != nil { 193 return "", "", errors.Wrapf(err, "error reading password") 194 } 195 password = string(pass) 196 fmt.Println() 197 } 198 return strings.TrimSpace(username), password, err 199 } 200 201 // registryFromFullName gets the registry from the input. If the input is of the form 202 // quay.io/myuser/myimage, it will parse it and just return quay.io 203 // It also returns true if a full image name was given 204 func registryFromFullName(input string) string { 205 split := strings.Split(input, "/") 206 if len(split) > 1 { 207 return split[0] 208 } 209 return split[0] 210 }