aboutsummaryrefslogtreecommitdiff
path: root/cmd/podman/login.go
blob: e09117833ab2a1edab7a0523ae2c0d55e5362f3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	buildahcli "github.com/containers/buildah/pkg/cli"
	"github.com/containers/image/v5/docker"
	"github.com/containers/image/v5/pkg/docker/config"
	"github.com/containers/image/v5/types"
	"github.com/containers/libpod/cmd/podman/cliconfig"
	"github.com/containers/libpod/libpod/image"
	"github.com/containers/libpod/pkg/registries"
	"github.com/docker/docker-credential-helpers/credentials"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	loginCommand cliconfig.LoginValues

	loginDescription = "Login to a container registry on a specified server."
	_loginCommand    = &cobra.Command{
		Use:   "login [flags] REGISTRY",
		Short: "Login to a container registry",
		Long:  loginDescription,
		RunE: func(cmd *cobra.Command, args []string) error {
			loginCommand.InputArgs = args
			loginCommand.GlobalFlags = MainGlobalOpts
			loginCommand.Remote = remoteclient
			return loginCmd(&loginCommand)
		},
		Example: `podman login -u testuser -p testpassword localhost:5000
  podman login -u testuser -p testpassword localhost:5000`,
	}
)

func init() {
	if !remote {
		_loginCommand.Example = fmt.Sprintf("%s\n  podman login --authfile authdir/myauths.json quay.io", _loginCommand.Example)

	}
	loginCommand.Command = _loginCommand
	loginCommand.SetHelpTemplate(HelpTemplate())
	loginCommand.SetUsageTemplate(UsageTemplate())
	flags := loginCommand.Flags()

	flags.BoolVar(&loginCommand.GetLogin, "get-login", true, "Return the current login user for the registry")
	flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry")
	flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry")
	flags.BoolVar(&loginCommand.StdinPassword, "password-stdin", false, "Take the password from stdin")
	// Disabled flags for the remote client
	if !remote {
		flags.StringVar(&loginCommand.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
		flags.StringVar(&loginCommand.CertDir, "cert-dir", "", "Pathname of a directory containing TLS certificates and keys used to connect to the registry")
		flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
	}
}

// loginCmd uses the authentication package to store a user's authenticated credentials
// in an auth.json file for future use
func loginCmd(c *cliconfig.LoginValues) error {
	args := c.InputArgs
	if len(args) > 1 {
		return errors.Errorf("too many arguments, login takes only 1 argument")
	}
	var server string
	if len(args) == 0 {
		registriesFromFile, err := registries.GetRegistries()
		if err != nil || len(registriesFromFile) == 0 {
			return errors.Errorf("please specify a registry to login to")
		}

		server = registriesFromFile[0]
		logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", server)

	} else {
		server = registryFromFullName(scrubServer(args[0]))
	}

	if c.Flag("password").Changed {
		fmt.Fprintf(os.Stderr, "WARNING! Using --password via the cli is insecure. Please consider using --password-stdin\n")
	}

	sc := image.GetSystemContext("", c.Authfile, false)
	if c.Flag("tls-verify").Changed {
		sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify)
	}
	if c.CertDir != "" {
		sc.DockerCertPath = c.CertDir
	}

	// username of user logged in to server (if one exists)
	userFromAuthFile, passFromAuthFile, err := config.GetAuthentication(sc, server)
	// Do not return error if no credentials found in credHelpers, new credentials will be stored by config.SetAuthentication
	if err != nil && err != credentials.NewErrCredentialsNotFound() {
		return errors.Wrapf(err, "error reading auth file")
	}

	if c.Flag("get-login").Changed {
		if userFromAuthFile == "" {
			return errors.Errorf("not logged into %s", server)
		}
		fmt.Printf("%s\n", userFromAuthFile)
		return nil
	}

	ctx := getContext()

	password := c.Password

	if c.Flag("password-stdin").Changed {
		var stdinPasswordStrBuilder strings.Builder
		if c.Password != "" {
			return errors.Errorf("Can't specify both --password-stdin and --password")
		}
		if c.Username == "" {
			return errors.Errorf("Must provide --username with --password-stdin")
		}
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			fmt.Fprint(&stdinPasswordStrBuilder, scanner.Text())
		}
		password = stdinPasswordStrBuilder.String()
	}

	// If no username and no password is specified, try to use existing ones.
	if c.Username == "" && password == "" && userFromAuthFile != "" && passFromAuthFile != "" {
		fmt.Println("Authenticating with existing credentials...")
		if err := docker.CheckAuth(ctx, sc, userFromAuthFile, passFromAuthFile, server); err == nil {
			fmt.Println("Existing credentials are valid. Already logged in to", server)
			return nil
		}
		fmt.Println("Existing credentials are invalid, please enter valid username and password")
	}

	username, password, err := getUserAndPass(c.Username, password, userFromAuthFile)
	if err != nil {
		return errors.Wrapf(err, "error getting username and password")
	}

	if err = docker.CheckAuth(ctx, sc, username, password, server); err == nil {
		// Write the new credentials to the authfile
		if err = config.SetAuthentication(sc, server, username, password); err != nil {
			return err
		}
	}
	if err == nil {
		fmt.Println("Login Succeeded!")
		return nil
	}
	if unauthorizedError, ok := err.(docker.ErrUnauthorizedForCredentials); ok {
		logrus.Debugf("error logging into %q: %v", server, unauthorizedError)
		return errors.Errorf("error logging into %q: invalid username/password", server)
	}
	return errors.Wrapf(err, "error authenticating creds for %q", server)
}

// getUserAndPass gets the username and password from STDIN if not given
// using the -u and -p flags.  If the username prompt is left empty, the
// displayed userFromAuthFile will be used instead.
func getUserAndPass(username, password, userFromAuthFile string) (string, string, error) {
	var err error
	reader := bufio.NewReader(os.Stdin)
	if username == "" {
		if userFromAuthFile != "" {
			fmt.Printf("Username (%s): ", userFromAuthFile)
		} else {
			fmt.Print("Username: ")
		}
		username, err = reader.ReadString('\n')
		if err != nil {
			return "", "", errors.Wrapf(err, "error reading username")
		}
		// If the user just hit enter, use the displayed user from the
		// the authentication file.  This allows to do a lazy
		// `$ podman login -p $NEW_PASSWORD` without specifying the
		// user.
		if strings.TrimSpace(username) == "" {
			username = userFromAuthFile
		}
	}
	if password == "" {
		fmt.Print("Password: ")
		pass, err := terminal.ReadPassword(0)
		if err != nil {
			return "", "", errors.Wrapf(err, "error reading password")
		}
		password = string(pass)
		fmt.Println()
	}
	return strings.TrimSpace(username), password, err
}

// registryFromFullName gets the registry from the input. If the input is of the form
// quay.io/myuser/myimage, it will parse it and just return quay.io
// It also returns true if a full image name was given
func registryFromFullName(input string) string {
	split := strings.Split(input, "/")
	if len(split) > 1 {
		return split[0]
	}
	return split[0]
}