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