-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
login.go
360 lines (296 loc) · 10.5 KB
/
login.go
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
package command
import (
"fmt"
"io"
"os"
"strings"
"github.com/hashicorp/vault/api"
"github.com/posener/complete"
)
// LoginHandler is the interface that any auth handlers must implement to enable
// auth via the CLI.
type LoginHandler interface {
Auth(*api.Client, map[string]string) (*api.Secret, error)
Help() string
}
type LoginCommand struct {
*BaseCommand
Handlers map[string]LoginHandler
flagMethod string
flagPath string
flagNoStore bool
flagNoPrint bool
flagTokenOnly bool
testStdin io.Reader // for tests
}
func (c *LoginCommand) Synopsis() string {
return "Authenticate locally"
}
func (c *LoginCommand) Help() string {
helpText := `
Usage: vault login [options] [AUTH K=V...]
Authenticates users or machines to Vault using the provided arguments. A
successful authentication results in a Vault token - conceptually similar to
a session token on a website. By default, this token is cached on the local
machine for future requests.
The default auth method is "token". If not supplied via the CLI,
Vault will prompt for input. If the argument is "-", the values are read
from stdin.
The -method flag allows using other auth methods, such as userpass, github, or
cert. For these, additional "K=V" pairs may be required. For example, to
authenticate to the userpass auth method:
$ vault login -method=userpass username=my-username
For more information about the list of configuration parameters available for
a given auth method, use the "vault auth help TYPE". You can also use "vault
auth list" to see the list of enabled auth methods.
If an auth method is enabled at a non-standard path, the -method flag still
refers to the canonical type, but the -path flag refers to the enabled path.
If a github auth method was enabled at "github-prod", authenticate like this:
$ vault login -method=github -path=github-prod
If the authentication is requested with response wrapping (via -wrap-ttl),
the returned token is automatically unwrapped unless:
- The -token-only flag is used, in which case this command will output
the wrapping token.
- The -no-store flag is used, in which case this command will output the
details of the wrapping token.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *LoginCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
f.StringVar(&StringVar{
Name: "method",
Target: &c.flagMethod,
Default: "token",
Completion: c.PredictVaultAvailableAuths(),
Usage: "Type of authentication to use such as \"userpass\" or " +
"\"ldap\". Note this corresponds to the TYPE, not the enabled path. " +
"Use -path to specify the path where the authentication is enabled.",
})
f.StringVar(&StringVar{
Name: "path",
Target: &c.flagPath,
Default: "",
Completion: c.PredictVaultAuths(),
Usage: "Remote path in Vault where the auth method is enabled. " +
"This defaults to the TYPE of method (e.g. userpass -> userpass/).",
})
f.BoolVar(&BoolVar{
Name: "no-store",
Target: &c.flagNoStore,
Default: false,
Usage: "Do not persist the token to the token helper (usually the " +
"local filesystem) after authentication for use in future requests. " +
"The token will only be displayed in the command output.",
})
f.BoolVar(&BoolVar{
Name: "no-print",
Target: &c.flagNoPrint,
Default: false,
Usage: "Do not display the token. The token will be still be stored to the " +
"configured token helper.",
})
f.BoolVar(&BoolVar{
Name: "token-only",
Target: &c.flagTokenOnly,
Default: false,
Usage: "Output only the token with no verification. This flag is a " +
"shortcut for \"-field=token -no-store\". Setting those flags to other " +
"values will have no affect.",
})
return set
}
func (c *LoginCommand) AutocompleteArgs() complete.Predictor {
return nil
}
func (c *LoginCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *LoginCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
// Set the right flags if the user requested token-only - this overrides
// any previously configured values, as documented.
if c.flagTokenOnly {
c.flagNoStore = true
c.flagField = "token"
}
if c.flagNoStore && c.flagNoPrint {
c.UI.Error(wrapAtLength(
"-no-store and -no-print cannot be used together"))
return 1
}
// Get the auth method
authMethod := sanitizePath(c.flagMethod)
if authMethod == "" {
authMethod = "token"
}
// If no path is specified, we default the path to the method type
// or use the plugin name if it's a plugin
authPath := c.flagPath
if authPath == "" {
authPath = ensureTrailingSlash(authMethod)
}
// Get the handler function
authHandler, ok := c.Handlers[authMethod]
if !ok {
c.UI.Error(wrapAtLength(fmt.Sprintf(
"Unknown auth method: %s. Use \"vault auth list\" to see the "+
"complete list of auth methods. Additionally, some "+
"auth methods are only available via the HTTP API.",
authMethod)))
return 1
}
// Pull our fake stdin if needed
stdin := (io.Reader)(os.Stdin)
if c.testStdin != nil {
stdin = c.testStdin
}
// If the user provided a token, pass it along to the auth provider.
if authMethod == "token" && len(args) > 0 && !strings.Contains(args[0], "=") {
args = append([]string{"token=" + args[0]}, args[1:]...)
}
config, err := parseArgsDataString(stdin, args)
if err != nil {
c.UI.Error(fmt.Sprintf("Error parsing configuration: %s", err))
return 1
}
// If the user did not specify a mount path, use the provided mount path.
if config["mount"] == "" && authPath != "" {
config["mount"] = authPath
}
// Create the client
client, err := c.Client()
if err != nil {
c.UI.Error(err.Error())
return 2
}
// Evolving token formats across Vault versions have caused issues during CLI logins. Unless
// token auth is being used, omit any token picked up from TokenHelper.
if authMethod != "token" {
client.SetToken("")
}
// Authenticate delegation to the auth handler
secret, err := authHandler.Auth(client, config)
if err != nil {
c.UI.Error(fmt.Sprintf("Error authenticating: %s", err))
return 2
}
// Unset any previous token wrapping functionality. If the original request
// was for a wrapped token, we don't want future requests to be wrapped.
client.SetWrappingLookupFunc(func(string, string) string { return "" })
// Recursively extract the token, handling wrapping
unwrap := !c.flagTokenOnly && !c.flagNoStore
secret, isWrapped, err := c.extractToken(client, secret, unwrap)
if err != nil {
c.UI.Error(fmt.Sprintf("Error extracting token: %s", err))
return 2
}
if secret == nil {
c.UI.Error("Vault returned an empty secret")
return 2
}
// Handle special cases if the token was wrapped
if isWrapped {
if c.flagTokenOnly {
return PrintRawField(c.UI, secret, "wrapping_token")
}
if c.flagNoStore {
return OutputSecret(c.UI, secret)
}
}
// If we got this far, verify we have authentication data before continuing
if secret.Auth == nil {
c.UI.Error(wrapAtLength(
"Vault returned a secret, but the secret has no authentication " +
"information attached. This should never happen and is likely a " +
"bug."))
return 2
}
// Pull the token itself out, since we don't need the rest of the auth
// information anymore/.
token := secret.Auth.ClientToken
if !c.flagNoStore {
// Grab the token helper so we can store
tokenHelper, err := c.TokenHelper()
if err != nil {
c.UI.Error(wrapAtLength(fmt.Sprintf(
"Error initializing token helper. Please verify that the token "+
"helper is available and properly configured for your system. The "+
"error was: %s", err)))
return 1
}
// Store the token in the local client
if err := tokenHelper.Store(token); err != nil {
c.UI.Error(fmt.Sprintf("Error storing token: %s", err))
c.UI.Error(wrapAtLength(
"Authentication was successful, but the token was not persisted. The "+
"resulting token is shown below for your records.") + "\n")
OutputSecret(c.UI, secret)
return 2
}
// Warn if the VAULT_TOKEN environment variable is set, as that will take
// precedence. We output as a warning, so piping should still work since it
// will be on a different stream.
if os.Getenv("VAULT_TOKEN") != "" {
c.UI.Warn(wrapAtLength("WARNING! The VAULT_TOKEN environment variable "+
"is set! This takes precedence over the value set by this command. To "+
"use the value set by this command, unset the VAULT_TOKEN environment "+
"variable or set it to the token displayed below.") + "\n")
}
} else if !c.flagTokenOnly {
// If token-only the user knows it won't be stored, so don't warn
c.UI.Warn(wrapAtLength(
"The token was not stored in token helper. Set the VAULT_TOKEN "+
"environment variable or pass the token below with each request to "+
"Vault.") + "\n")
}
if c.flagNoPrint {
return 0
}
// If the user requested a particular field, print that out now since we
// are likely piping to another process.
if c.flagField != "" {
return PrintRawField(c.UI, secret, c.flagField)
}
// Print some yay! text, but only in table mode.
if Format(c.UI) == "table" {
c.UI.Output(wrapAtLength(
"Success! You are now authenticated. The token information displayed "+
"below is already stored in the token helper. You do NOT need to run "+
"\"vault login\" again. Future Vault requests will automatically use "+
"this token.") + "\n")
}
return OutputSecret(c.UI, secret)
}
// extractToken extracts the token from the given secret, automatically
// unwrapping responses and handling error conditions if unwrap is true. The
// result also returns whether it was a wrapped response that was not unwrapped.
func (c *LoginCommand) extractToken(client *api.Client, secret *api.Secret, unwrap bool) (*api.Secret, bool, error) {
switch {
case secret == nil:
return nil, false, fmt.Errorf("empty response from auth helper")
case secret.Auth != nil:
return secret, false, nil
case secret.WrapInfo != nil:
if secret.WrapInfo.WrappedAccessor == "" {
return nil, false, fmt.Errorf("wrapped response does not contain a token")
}
if !unwrap {
return secret, true, nil
}
client.SetToken(secret.WrapInfo.Token)
secret, err := client.Logical().Unwrap("")
if err != nil {
return nil, false, err
}
return c.extractToken(client, secret, unwrap)
default:
return nil, false, fmt.Errorf("no auth or wrapping info in response")
}
}