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  }