open-vault/command/auth.go

453 lines
13 KiB
Go

package command
import (
"fmt"
"io"
"os"
"strings"
"github.com/hashicorp/vault/api"
"github.com/posener/complete"
)
// AuthHandler is the interface that any auth handlers must implement
// to enable auth via the CLI.
type AuthHandler interface {
Auth(*api.Client, map[string]string) (*api.Secret, error)
Help() string
}
// AuthCommand is a Command that handles authentication.
type AuthCommand struct {
*BaseCommand
Handlers map[string]AuthHandler
flagMethod string
flagPath string
flagNoVerify bool
flagNoStore bool
flagOnlyToken bool
// Deprecations
// TODO: remove in 0.9.0
flagTokenOnly bool
flagMethods bool
flagMethodHelp bool
testStdin io.Reader // for tests
}
func (c *AuthCommand) Synopsis() string {
return "Authenticates users or machines"
}
func (c *AuthCommand) Help() string {
helpText := `
Usage: vault auth [options] [AUTH K=V...]
Authenticates users or machines to Vault using the provided arguments. By
default, the authentication method is "token". If not supplied via the CLI,
Vault will prompt for input. If argument is "-", the configuration options
are read from stdin.
The -method flag allows alternative authentication providers to be used,
such as userpass, github, or cert. For these, additional "key=value" pairs
may be required. For example, to authenticate to the userpass auth backend:
$ vault auth -method=userpass username=my-username
Use "vault auth-help TYPE" for more information about the list of
configuration parameters and examples for a particular provider. Use the
"vault auth-list" command to see a list of enabled authentication providers.
If an authentication provider is mounted at a different path, the -method
flag should by the canonical type, and the -path flag should be set to the
mount path. If a github authentication provider was mounted at "github-ent",
you would authenticate to this backend like this:
$ vault auth -method=github -path=github-prod
If the authentication is requested with response wrapping (via -wrap-ttl),
the returned token is automatically unwrapped unless:
- The -only-token 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.
For a full list of examples, please see the documentation.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *AuthCommand) 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 mount path. Use " +
"-path to specify the path where the authentication is mounted.",
})
f.StringVar(&StringVar{
Name: "path",
Target: &c.flagPath,
Default: "",
Completion: c.PredictVaultAuths(),
Usage: "Mount point where the auth backend is enabled. This defaults to " +
"the TYPE of backend (e.g. userpass -> userpass/).",
})
f.BoolVar(&BoolVar{
Name: "no-verify",
Target: &c.flagNoVerify,
Default: false,
Usage: "Do not verify the token after authentication. By default, Vault " +
"issue a request to get more metdata about the token. This request " +
"against the use-limit of the token. Set this to true to disable the " +
"post-authenication lookup.",
})
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: "only-token",
Target: &c.flagOnlyToken,
Default: false,
Usage: "Output only the token with no verification. This flag is a " +
"shortcut for \"-field=token -no-store -no-verify\". Setting those " +
"flags to other values will have no affect.",
})
// Deprecations
// TODO: remove in Vault 0.9.0
f.BoolVar(&BoolVar{
Name: "token-only", // Prefer only-token
Target: &c.flagTokenOnly,
Default: false,
Hidden: true,
})
f.BoolVar(&BoolVar{
Name: "methods", // Prefer auth-list
Target: &c.flagMethods,
Default: false,
Hidden: true,
})
f.BoolVar(&BoolVar{
Name: "method-help", // Prefer auth-help
Target: &c.flagMethodHelp,
Default: false,
Hidden: true,
})
return set
}
func (c *AuthCommand) AutocompleteArgs() complete.Predictor {
return nil
}
func (c *AuthCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *AuthCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
// Deprecations - do this before any argument validations
// TODO: remove in 0.9.0
switch {
case c.flagMethods:
c.UI.Warn(wrapAtLength(
"WARNING! The -methods flag is deprecated. Please use " +
"\"vault auth-list\". This flag will be removed in the next major " +
"release of Vault."))
cmd := &AuthListCommand{
BaseCommand: &BaseCommand{
UI: c.UI,
client: c.client,
},
}
return cmd.Run(nil)
case c.flagMethodHelp:
c.UI.Warn(wrapAtLength(
"WARNING! The -method-help flag is deprecated. Please use " +
"\"vault auth-help\". This flag will be removed in the next major " +
"release of Vault."))
cmd := &AuthHelpCommand{
BaseCommand: &BaseCommand{
UI: c.UI,
client: c.client,
},
Handlers: c.Handlers,
}
return cmd.Run([]string{c.flagMethod})
}
// TODO: remove in 0.9.0
if c.flagTokenOnly {
c.UI.Warn(wrapAtLength(
"WARNING! The -token-only flag is deprecated. Plase use -only-token " +
"instead. This flag will be removed in the next major release of " +
"Vault."))
c.flagOnlyToken = c.flagTokenOnly
}
// Set the right flags if the user requested only-token - this overrides
// any previously configured values, as documented.
if c.flagOnlyToken {
c.flagNoStore = true
c.flagNoVerify = true
c.flagField = "token"
}
// Get the auth method
authMethod := sanitizePath(c.flagMethod)
if authMethod == "" {
authMethod = "token"
}
// If no path is specified, we default the path to the backend type
// or use the plugin name if it's a plugin backend
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 authentication method: %s. Use \"vault auth-list\" to see the "+
"complete list of authentication providers. Additionally, some "+
"authentication providers 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 provier.
if authMethod == "token" && len(args) == 1 {
args = []string{"token=" + args[0]}
}
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
}
// Authenticate delegation to the auth handler
secret, err := authHandler.Auth(client, config)
if err != nil {
c.UI.Error(wrapAtLength(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.flagOnlyToken && !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.flagOnlyToken {
return PrintRawField(c.UI, secret, "wrapping_token")
}
if c.flagNoStore {
return OutputSecret(c.UI, c.flagFormat, 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
tokenHelper, err := c.TokenHelper()
if err != nil {
c.UI.Error(fmt.Sprintf(
"Error initializing token helper: %s\n\n"+
"Please verify that the token helper is available and properly\n"+
"configured for your system. Please refer to the documentation\n"+
"on token helpers for more information.",
err))
return 1
}
if !c.flagNoVerify {
// Verify the token and pull it's list of policies
client.SetToken(token)
client.SetWrappingLookupFunc(func(string, string) string { return "" })
secret, err = client.Auth().Token().LookupSelf()
if err != nil {
c.UI.Error(fmt.Sprintf("Error verifying token: %s", err))
return 2
}
if secret == nil {
c.UI.Error("Empty response from lookup-self")
return 2
}
}
if !c.flagNoStore {
// 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."))
OutputSecret(c.UI, c.flagFormat, secret)
return 2
}
// Warn if the VAULT_TOKEN environment variable is set, as that will take
// precedence. Don't output on token-only since we're likely piping output.
if c.flagField == "" && c.flagFormat == "table" {
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."))
}
}
}
// 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)
}
// Output the secret as json or yaml if requested. We have to maintain
// backwards compatiability
if c.flagFormat != "table" {
return OutputSecret(c.UI, c.flagFormat, secret)
}
output := "Success! You are now authenticated. "
if c.flagNoVerify {
output += "The token was not verified for validity. "
}
if c.flagNoStore {
output += "The token was not stored in the token helper. "
} else {
output += "The token information displayed below is already stored in " +
"the token helper. You do NOT need to run \"vault auth\" again."
}
c.UI.Output(wrapAtLength(output) + "\n")
// TODO make this consistent with other printed token secrets.
c.UI.Output(fmt.Sprintf("token: %s", secret.TokenID()))
c.UI.Output(fmt.Sprintf("accessor: %s", secret.TokenAccessor()))
if ttl := secret.TokenTTL(); ttl != 0 {
c.UI.Output(fmt.Sprintf("duration: %s", ttl))
}
c.UI.Output(fmt.Sprintf("renewable: %t", secret.TokenIsRenewable()))
if policies := secret.TokenPolicies(); len(policies) > 0 {
c.UI.Output(fmt.Sprintf("policies: %s", policies))
}
return 0
}
// 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 resonse that was not unwrapped.
func (c *AuthCommand) 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")
}
}