1package command
2
3import (
4	"fmt"
5	"io"
6	"os"
7	"strings"
8
9	"github.com/hashicorp/vault/api"
10	"github.com/posener/complete"
11)
12
13// LoginHandler is the interface that any auth handlers must implement to enable
14// auth via the CLI.
15type LoginHandler interface {
16	Auth(*api.Client, map[string]string) (*api.Secret, error)
17	Help() string
18}
19
20type LoginCommand struct {
21	*BaseCommand
22
23	Handlers map[string]LoginHandler
24
25	flagMethod    string
26	flagPath      string
27	flagNoStore   bool
28	flagNoPrint   bool
29	flagTokenOnly bool
30
31	testStdin io.Reader // for tests
32}
33
34func (c *LoginCommand) Synopsis() string {
35	return "Authenticate locally"
36}
37
38func (c *LoginCommand) Help() string {
39	helpText := `
40Usage: vault login [options] [AUTH K=V...]
41
42  Authenticates users or machines to Vault using the provided arguments. A
43  successful authentication results in a Vault token - conceptually similar to
44  a session token on a website. By default, this token is cached on the local
45  machine for future requests.
46
47  The default auth method is "token". If not supplied via the CLI,
48  Vault will prompt for input. If the argument is "-", the values are read
49  from stdin.
50
51  The -method flag allows using other auth methods, such as userpass, github, or
52  cert. For these, additional "K=V" pairs may be required. For example, to
53  authenticate to the userpass auth method:
54
55      $ vault login -method=userpass username=my-username
56
57  For more information about the list of configuration parameters available for
58  a given auth method, use the "vault auth help TYPE". You can also use "vault
59  auth list" to see the list of enabled auth methods.
60
61  If an auth method is enabled at a non-standard path, the -method flag still
62  refers to the canonical type, but the -path flag refers to the enabled path.
63  If a github auth method was enabled at "github-ent", authenticate like this:
64
65      $ vault login -method=github -path=github-prod
66
67  If the authentication is requested with response wrapping (via -wrap-ttl),
68  the returned token is automatically unwrapped unless:
69
70    - The -token-only flag is used, in which case this command will output
71      the wrapping token.
72
73    - The -no-store flag is used, in which case this command will output the
74      details of the wrapping token.
75
76` + c.Flags().Help()
77
78	return strings.TrimSpace(helpText)
79}
80
81func (c *LoginCommand) Flags() *FlagSets {
82	set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
83
84	f := set.NewFlagSet("Command Options")
85
86	f.StringVar(&StringVar{
87		Name:       "method",
88		Target:     &c.flagMethod,
89		Default:    "token",
90		Completion: c.PredictVaultAvailableAuths(),
91		Usage: "Type of authentication to use such as \"userpass\" or " +
92			"\"ldap\". Note this corresponds to the TYPE, not the enabled path. " +
93			"Use -path to specify the path where the authentication is enabled.",
94	})
95
96	f.StringVar(&StringVar{
97		Name:       "path",
98		Target:     &c.flagPath,
99		Default:    "",
100		Completion: c.PredictVaultAuths(),
101		Usage: "Remote path in Vault where the auth method is enabled. " +
102			"This defaults to the TYPE of method (e.g. userpass -> userpass/).",
103	})
104
105	f.BoolVar(&BoolVar{
106		Name:    "no-store",
107		Target:  &c.flagNoStore,
108		Default: false,
109		Usage: "Do not persist the token to the token helper (usually the " +
110			"local filesystem) after authentication for use in future requests. " +
111			"The token will only be displayed in the command output.",
112	})
113
114	f.BoolVar(&BoolVar{
115		Name:    "no-print",
116		Target:  &c.flagNoPrint,
117		Default: false,
118		Usage: "Do not display the token. The token will be still be stored to the " +
119			"configured token helper.",
120	})
121
122	f.BoolVar(&BoolVar{
123		Name:    "token-only",
124		Target:  &c.flagTokenOnly,
125		Default: false,
126		Usage: "Output only the token with no verification. This flag is a " +
127			"shortcut for \"-field=token -no-store\". Setting those flags to other " +
128			"values will have no affect.",
129	})
130
131	return set
132}
133
134func (c *LoginCommand) AutocompleteArgs() complete.Predictor {
135	return nil
136}
137
138func (c *LoginCommand) AutocompleteFlags() complete.Flags {
139	return c.Flags().Completions()
140}
141
142func (c *LoginCommand) Run(args []string) int {
143	f := c.Flags()
144
145	if err := f.Parse(args); err != nil {
146		c.UI.Error(err.Error())
147		return 1
148	}
149
150	args = f.Args()
151
152	// Set the right flags if the user requested token-only - this overrides
153	// any previously configured values, as documented.
154	if c.flagTokenOnly {
155		c.flagNoStore = true
156		c.flagField = "token"
157	}
158
159	if c.flagNoStore && c.flagNoPrint {
160		c.UI.Error(wrapAtLength(
161			"-no-store and -no-print cannot be used together"))
162		return 1
163	}
164
165	// Get the auth method
166	authMethod := sanitizePath(c.flagMethod)
167	if authMethod == "" {
168		authMethod = "token"
169	}
170
171	// If no path is specified, we default the path to the method type
172	// or use the plugin name if it's a plugin
173	authPath := c.flagPath
174	if authPath == "" {
175		authPath = ensureTrailingSlash(authMethod)
176	}
177
178	// Get the handler function
179	authHandler, ok := c.Handlers[authMethod]
180	if !ok {
181		c.UI.Error(wrapAtLength(fmt.Sprintf(
182			"Unknown auth method: %s. Use \"vault auth list\" to see the "+
183				"complete list of auth methods. Additionally, some "+
184				"auth methods are only available via the HTTP API.",
185			authMethod)))
186		return 1
187	}
188
189	// Pull our fake stdin if needed
190	stdin := (io.Reader)(os.Stdin)
191	if c.testStdin != nil {
192		stdin = c.testStdin
193	}
194
195	// If the user provided a token, pass it along to the auth provider.
196	if authMethod == "token" && len(args) > 0 && !strings.Contains(args[0], "=") {
197		args = append([]string{"token=" + args[0]}, args[1:]...)
198	}
199
200	config, err := parseArgsDataString(stdin, args)
201	if err != nil {
202		c.UI.Error(fmt.Sprintf("Error parsing configuration: %s", err))
203		return 1
204	}
205
206	// If the user did not specify a mount path, use the provided mount path.
207	if config["mount"] == "" && authPath != "" {
208		config["mount"] = authPath
209	}
210
211	// Create the client
212	client, err := c.Client()
213	if err != nil {
214		c.UI.Error(err.Error())
215		return 2
216	}
217
218	// Authenticate delegation to the auth handler
219	secret, err := authHandler.Auth(client, config)
220	if err != nil {
221		c.UI.Error(fmt.Sprintf("Error authenticating: %s", err))
222		return 2
223	}
224
225	// Unset any previous token wrapping functionality. If the original request
226	// was for a wrapped token, we don't want future requests to be wrapped.
227	client.SetWrappingLookupFunc(func(string, string) string { return "" })
228
229	// Recursively extract the token, handling wrapping
230	unwrap := !c.flagTokenOnly && !c.flagNoStore
231	secret, isWrapped, err := c.extractToken(client, secret, unwrap)
232	if err != nil {
233		c.UI.Error(fmt.Sprintf("Error extracting token: %s", err))
234		return 2
235	}
236	if secret == nil {
237		c.UI.Error("Vault returned an empty secret")
238		return 2
239	}
240
241	// Handle special cases if the token was wrapped
242	if isWrapped {
243		if c.flagTokenOnly {
244			return PrintRawField(c.UI, secret, "wrapping_token")
245		}
246		if c.flagNoStore {
247			return OutputSecret(c.UI, secret)
248		}
249	}
250
251	// If we got this far, verify we have authentication data before continuing
252	if secret.Auth == nil {
253		c.UI.Error(wrapAtLength(
254			"Vault returned a secret, but the secret has no authentication " +
255				"information attached. This should never happen and is likely a " +
256				"bug."))
257		return 2
258	}
259
260	// Pull the token itself out, since we don't need the rest of the auth
261	// information anymore/.
262	token := secret.Auth.ClientToken
263
264	if !c.flagNoStore {
265		// Grab the token helper so we can store
266		tokenHelper, err := c.TokenHelper()
267		if err != nil {
268			c.UI.Error(wrapAtLength(fmt.Sprintf(
269				"Error initializing token helper. Please verify that the token "+
270					"helper is available and properly configured for your system. The "+
271					"error was: %s", err)))
272			return 1
273		}
274
275		// Store the token in the local client
276		if err := tokenHelper.Store(token); err != nil {
277			c.UI.Error(fmt.Sprintf("Error storing token: %s", err))
278			c.UI.Error(wrapAtLength(
279				"Authentication was successful, but the token was not persisted. The "+
280					"resulting token is shown below for your records.") + "\n")
281			OutputSecret(c.UI, secret)
282			return 2
283		}
284
285		// Warn if the VAULT_TOKEN environment variable is set, as that will take
286		// precedence. We output as a warning, so piping should still work since it
287		// will be on a different stream.
288		if os.Getenv("VAULT_TOKEN") != "" {
289			c.UI.Warn(wrapAtLength("WARNING! The VAULT_TOKEN environment variable "+
290				"is set! This takes precedence over the value set by this command. To "+
291				"use the value set by this command, unset the VAULT_TOKEN environment "+
292				"variable or set it to the token displayed below.") + "\n")
293		}
294	} else if !c.flagTokenOnly {
295		// If token-only the user knows it won't be stored, so don't warn
296		c.UI.Warn(wrapAtLength(
297			"The token was not stored in token helper. Set the VAULT_TOKEN "+
298				"environment variable or pass the token below with each request to "+
299				"Vault.") + "\n")
300	}
301
302	if c.flagNoPrint {
303		return 0
304	}
305
306	// If the user requested a particular field, print that out now since we
307	// are likely piping to another process.
308	if c.flagField != "" {
309		return PrintRawField(c.UI, secret, c.flagField)
310	}
311
312	// Print some yay! text, but only in table mode.
313	if Format(c.UI) == "table" {
314		c.UI.Output(wrapAtLength(
315			"Success! You are now authenticated. The token information displayed "+
316				"below is already stored in the token helper. You do NOT need to run "+
317				"\"vault login\" again. Future Vault requests will automatically use "+
318				"this token.") + "\n")
319	}
320
321	return OutputSecret(c.UI, secret)
322}
323
324// extractToken extracts the token from the given secret, automatically
325// unwrapping responses and handling error conditions if unwrap is true. The
326// result also returns whether it was a wrapped response that was not unwrapped.
327func (c *LoginCommand) extractToken(client *api.Client, secret *api.Secret, unwrap bool) (*api.Secret, bool, error) {
328	switch {
329	case secret == nil:
330		return nil, false, fmt.Errorf("empty response from auth helper")
331
332	case secret.Auth != nil:
333		return secret, false, nil
334
335	case secret.WrapInfo != nil:
336		if secret.WrapInfo.WrappedAccessor == "" {
337			return nil, false, fmt.Errorf("wrapped response does not contain a token")
338		}
339
340		if !unwrap {
341			return secret, true, nil
342		}
343
344		client.SetToken(secret.WrapInfo.Token)
345		secret, err := client.Logical().Unwrap("")
346		if err != nil {
347			return nil, false, err
348		}
349		return c.extractToken(client, secret, unwrap)
350
351	default:
352		return nil, false, fmt.Errorf("no auth or wrapping info in response")
353	}
354}
355