From fc535647fcbc90abda5cdf2461e1dc0aac5f1f71 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 7 Sep 2017 21:56:39 -0400 Subject: [PATCH] Introduce auth as a subcommand --- command/auth.go | 491 ++++++--------------------------- command/auth_disable.go | 26 +- command/auth_disable_test.go | 4 +- command/auth_enable.go | 41 ++- command/auth_enable_test.go | 2 +- command/auth_help.go | 39 ++- command/auth_help_test.go | 10 +- command/auth_list.go | 28 +- command/auth_test.go | 515 ++++------------------------------- command/auth_tune.go | 120 ++++++++ command/auth_tune_test.go | 149 ++++++++++ command/token/helper.go | 2 +- 12 files changed, 473 insertions(+), 954 deletions(-) create mode 100644 command/auth_tune.go create mode 100644 command/auth_tune_test.go diff --git a/command/auth.go b/command/auth.go index 8ebf021bd1..fa8cc66268 100644 --- a/command/auth.go +++ b/command/auth.go @@ -1,452 +1,117 @@ package command import ( - "fmt" + "flag" "io" - "os" + "io/ioutil" "strings" - "github.com/hashicorp/vault/api" - "github.com/posener/complete" + "github.com/mitchellh/cli" ) -// 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 -} +var _ cli.Command = (*AuthCommand)(nil) -// 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 + Handlers map[string]LoginHandler testStdin io.Reader // for tests } func (c *AuthCommand) Synopsis() string { - return "Authenticates users or machines" + return "Interact with auth methods" } func (c *AuthCommand) Help() string { - helpText := ` -Usage: vault auth [options] [AUTH K=V...] + return strings.TrimSpace(` +Usage: vault auth [options] [args] - 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. + This command groups subcommands for interacting with Vault's auth methods. + Users can list, enable, disable, and get help for different auth methods. - 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: + To authenticate to Vault as a user or machine, use the "vault login" command + instead. This command is for interacting with the auth methods themselves, not + authenticating to Vault. - $ vault auth -method=userpass username=my-username + List all enabled auth methods: - 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. + $ vault auth list - 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: + Enable a new auth method "userpass"; - $ vault auth -method=github -path=github-prod + $ vault auth enable userpass - If the authentication is requested with response wrapping (via -wrap-ttl), - the returned token is automatically unwrapped unless: + Get detailed help information about how to authenticate to a particular auth + method: - - The -only-token flag is used, in which case this command will output - the wrapping token + $ vault auth help github - - 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() + Please see the individual subcommand help for detailed usage information. +`) } 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 + // If we entered the run method, none of the subcommands picked up. This + // means the user is still trying to use auth as "vault auth TOKEN" or + // similar, so direct them to vault login instead. + // + // This run command is a bit messy to maintain BC for a bit. In the future, + // it will just be a tiny function, but for now we have to maintain bc. + // + // Deprecation // 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 - } + // Parse the args for our deprecations and defer to the proper areas. + for _, arg := range args { + switch { + case strings.HasPrefix(arg, "-methods"): + c.UI.Warn(wrapAtLength( + "WARNING! The -methods flag is deprecated. Please use "+ + "\"vault auth list\" instead. This flag will be removed in the "+ + "next major release of Vault.") + "\n") + return (&AuthListCommand{ + BaseCommand: &BaseCommand{ + UI: c.UI, + client: c.client, + }, + }).Run(nil) + case strings.HasPrefix(arg, "-method-help"): + c.UI.Warn(wrapAtLength( + "WARNING! The -method-help flag is deprecated. Please use "+ + "\"vault auth help\" instead. This flag will be removed in the "+ + "next major release of Vault.") + "\n") + // Parse the args to pull out the method, surpressing any errors because + // there could be other flags that we don't care about. + f := flag.NewFlagSet("", flag.ContinueOnError) + f.Usage = func() {} + f.SetOutput(ioutil.Discard) + flagMethod := f.String("method", "", "") + f.Parse(args) - // 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) + return (&AuthHelpCommand{ + BaseCommand: &BaseCommand{ + UI: c.UI, + client: c.client, + }, + Handlers: c.Handlers, + }).Run([]string{*flagMethod}) } } - // 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") - } + // If we got this far, we have an arg or a series of args that should be + // passed directly to the new "vault login" command. + c.UI.Warn(wrapAtLength( + "WARNING! The \"vault auth ARG\" command is deprecated and is now a "+ + "subcommand for interacting with auth methods. To "+ + "authenticate locally to Vault, use \"vault login\" instead. This "+ + "backwards compatability will be removed in the next major release of "+ + "Vault.") + "\n") + return (&LoginCommand{ + BaseCommand: &BaseCommand{ + UI: c.UI, + client: c.client, + }, + Handlers: c.Handlers, + }).Run(args) } diff --git a/command/auth_disable.go b/command/auth_disable.go index 14cf092269..afcfe747df 100644 --- a/command/auth_disable.go +++ b/command/auth_disable.go @@ -8,35 +8,31 @@ import ( "github.com/posener/complete" ) -// Ensure we are implementing the right interfaces. var _ cli.Command = (*AuthDisableCommand)(nil) var _ cli.CommandAutocomplete = (*AuthDisableCommand)(nil) -// AuthDisableCommand is a Command that enables a new endpoint. type AuthDisableCommand struct { *BaseCommand } func (c *AuthDisableCommand) Synopsis() string { - return "Disables an auth provider" + return "Disables an auth method" } func (c *AuthDisableCommand) Help() string { helpText := ` -Usage: vault auth-disable [options] PATH +Usage: vault auth disable [options] PATH - Disables an existing authentication provider at the given PATH. The argument - corresponds to the PATH of the mount, not the TYPE!. Once the auth provider - is disabled its path can no longer be used to authenticate. All access tokens - generated via the disabled auth provider are revoked. + Disables an existing auth method at the given PATH. The argument corresponds + to the PATH of the mount, not the TYPE!. Once the auth method is disabled its + path can no longer be used to authenticate. - This command will block until all tokens are revoked. + All access tokens generated via the disabled auth method are immediately + revoked. This command will block until all tokens are revoked. - Disable the authentication provider at userpass/: + Disable the auth method at userpass/: - $ vault auth-disable userpass - - For a full list of examples, please see the documentation. + $ vault auth disable userpass/ ` + c.Flags().Help() @@ -82,10 +78,10 @@ func (c *AuthDisableCommand) Run(args []string) int { } if err := client.Sys().DisableAuth(path); err != nil { - c.UI.Error(fmt.Sprintf("Error disabling auth at %s: %s", path, err)) + c.UI.Error(fmt.Sprintf("Error disabling auth method at %s: %s", path, err)) return 2 } - c.UI.Output(fmt.Sprintf("Success! Disabled the auth provider (if it existed) at: %s", path)) + c.UI.Output(fmt.Sprintf("Success! Disabled the auth method (if it existed) at: %s", path)) return 0 } diff --git a/command/auth_disable_test.go b/command/auth_disable_test.go index 86b1f90560..dbe2e776f9 100644 --- a/command/auth_disable_test.go +++ b/command/auth_disable_test.go @@ -85,7 +85,7 @@ func TestAuthDisableCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Success! Disabled the auth provider" + expected := "Success! Disabled the auth method" combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) @@ -117,7 +117,7 @@ func TestAuthDisableCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Error disabling auth at my-auth/: " + expected := "Error disabling auth method at my-auth/: " combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) diff --git a/command/auth_enable.go b/command/auth_enable.go index a5ba67f27d..85b0f6fbfb 100644 --- a/command/auth_enable.go +++ b/command/auth_enable.go @@ -9,11 +9,9 @@ import ( "github.com/posener/complete" ) -// Ensure we are implementing the right interfaces. var _ cli.Command = (*AuthEnableCommand)(nil) var _ cli.CommandAutocomplete = (*AuthEnableCommand)(nil) -// AuthEnableCommand is a Command that enables a new endpoint. type AuthEnableCommand struct { *BaseCommand @@ -24,30 +22,28 @@ type AuthEnableCommand struct { } func (c *AuthEnableCommand) Synopsis() string { - return "Enables a new auth provider" + return "Enables a new auth method" } func (c *AuthEnableCommand) Help() string { helpText := ` -Usage: vault auth-enable [options] TYPE +Usage: vault auth enable [options] TYPE - Enables a new authentication provider. An authentication provider is - responsible for authenticating users or machiens and assigning them - policies with which they can access Vault. + Enables a new auth method. An auth method is responsible for authenticating + users or machines and assigning them policies with which they can access + Vault. - Enable the userpass auth provider at userpass/: + Enable the userpass auth method at userpass/: - $ vault auth-enable userpass + $ vault auth enable userpass - Enable the LDAP auth provider at auth-prod/: + Enable the LDAP auth method at auth-prod/: - $ vault auth-enable -path=auth-prod ldap + $ vault auth enable -path=auth-prod ldap - Enable a custom auth plugin (after it is registered in the plugin registry): + Enable a custom auth plugin (after it's registered in the plugin registry): - $ vault auth-enable -path=my-auth -plugin-name=my-auth-plugin plugin - - For a full list of examples, please see the documentation. + $ vault auth enable -path=my-auth -plugin-name=my-auth-plugin plugin ` + c.Flags().Help() @@ -64,7 +60,7 @@ func (c *AuthEnableCommand) Flags() *FlagSets { Target: &c.flagDescription, Completion: complete.PredictAnything, Usage: "Human-friendly description for the purpose of this " + - "authentication provider.", + "auth method.", }) f.StringVar(&StringVar{ @@ -72,16 +68,17 @@ func (c *AuthEnableCommand) Flags() *FlagSets { Target: &c.flagPath, Default: "", // The default is complex, so we have to manually document Completion: complete.PredictAnything, - Usage: "Place where the auth provider will be accessible. This must be " + - "unique across all auth providers. This defaults to the \"type\" of " + - "the mount. The auth provider will be accessible at \"/auth/\".", + Usage: "Place where the auth method will be accessible. This must be " + + "unique across all auth methods. This defaults to the \"type\" of " + + "the auth method. The auth method will be accessible at " + + "\"/auth/\".", }) f.StringVar(&StringVar{ Name: "plugin-name", Target: &c.flagPluginName, Completion: complete.PredictAnything, - Usage: "Name of the auth provider plugin. This plugin name must already " + + Usage: "Name of the auth method plugin. This plugin name must already " + "exist in the Vault server's plugin catalog.", }) @@ -89,7 +86,7 @@ func (c *AuthEnableCommand) Flags() *FlagSets { Name: "local", Target: &c.flagLocal, Default: false, - Usage: "Mark the auth provider as local-only. Local auth providers are " + + Usage: "Mark the auth method as local-only. Local auth methods are " + "not replicated nor removed by replication.", }) @@ -156,7 +153,7 @@ func (c *AuthEnableCommand) Run(args []string) int { return 2 } - authThing := authType + " auth provider" + authThing := authType + " auth method" if authType == "plugin" { authThing = c.flagPluginName + " plugin" } diff --git a/command/auth_enable_test.go b/command/auth_enable_test.go index 00016d0138..e4308f9934 100644 --- a/command/auth_enable_test.go +++ b/command/auth_enable_test.go @@ -89,7 +89,7 @@ func TestAuthEnableCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Success! Enabled userpass auth provider at:" + expected := "Success! Enabled userpass auth method at:" combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) diff --git a/command/auth_help.go b/command/auth_help.go index 7fbd5e1443..d18d1bf095 100644 --- a/command/auth_help.go +++ b/command/auth_help.go @@ -8,42 +8,41 @@ import ( "github.com/posener/complete" ) -// Ensure we are implementing the right interfaces. var _ cli.Command = (*AuthHelpCommand)(nil) var _ cli.CommandAutocomplete = (*AuthHelpCommand)(nil) -// AuthHelpCommand is a Command that prints help output for a given auth -// provider type AuthHelpCommand struct { *BaseCommand - Handlers map[string]AuthHandler + Handlers map[string]LoginHandler } func (c *AuthHelpCommand) Synopsis() string { - return "Prints usage for an auth provider" + return "Prints usage for an auth method" } func (c *AuthHelpCommand) Help() string { helpText := ` -Usage: vault path-help [options] TYPE | PATH +Usage: vault auth help [options] TYPE | PATH - Prints usage and help for an authentication provider. If provided a TYPE, - this command retrieves the default help for the given authentication - provider of that type. If given a PATH, this command returns the help - output for the authentication provider mounted at that path. If given a - PATH argument, the path must exist and be mounted. + Prints usage and help for an auth method. - Get usage instructions for the userpass authentication provider: + - If given a TYPE, this command prints the default help for the + auth method of that type. - $ vault auth-help userpass + - If given a PATH, this command prints the help output for the + auth method enabled at that path. This path must already + exist. - Print usage for the authentication provider mounted at my-provider/ + Get usage instructions for the userpass auth method: - $ vault auth-help my-provider/: + $ vault auth help userpass - Each authentication provider produces its own help output. For additional - information, please view the online documentation. + Print usage for the auth method enabled at my-method/: + + $ vault auth help my-method/ + + Each auth method produces its own help output. ` + c.Flags().Help() @@ -98,7 +97,7 @@ func (c *AuthHelpCommand) Run(args []string) int { // There was no auth type by that name, see if it's a mount auths, err := client.Sys().ListAuth() if err != nil { - c.UI.Error(fmt.Sprintf("Error listing authentication providers: %s", err)) + c.UI.Error(fmt.Sprintf("Error listing auth methods: %s", err)) return 2 } @@ -106,14 +105,14 @@ func (c *AuthHelpCommand) Run(args []string) int { auth, ok := auths[authPath] if !ok { c.UI.Error(fmt.Sprintf( - "Error retrieving help: unknown authentication provider: %s", args[0])) + "Error retrieving help: unknown auth method: %s", authType)) return 1 } authHandler, ok = c.Handlers[auth.Type] if !ok { c.UI.Error(wrapAtLength(fmt.Sprintf( - "INTERNAL ERROR! Found an authentication provider mounted at %s, but "+ + "INTERNAL ERROR! Found an auth method enabled at %s, but "+ "its type %q is not registered in Vault. This is a bug and should "+ "be reported. Please open an issue at github.com/hashicorp/vault.", authPath, authType))) diff --git a/command/auth_help_test.go b/command/auth_help_test.go index b157552019..9457bea0ec 100644 --- a/command/auth_help_test.go +++ b/command/auth_help_test.go @@ -17,7 +17,7 @@ func testAuthHelpCommand(tb testing.TB) (*cli.MockUi, *AuthHelpCommand) { BaseCommand: &BaseCommand{ UI: ui, }, - Handlers: map[string]AuthHandler{ + Handlers: map[string]LoginHandler{ "userpass": &credUserpass.CLIHandler{ DefaultMount: "userpass", }, @@ -88,7 +88,7 @@ func TestAuthHelpCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Usage: vault auth -method=userpass" + expected := "Usage: vault login -method=userpass" combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) @@ -101,7 +101,7 @@ func TestAuthHelpCommand_Run(t *testing.T) { client, closer := testVaultServer(t) defer closer() - // No mounted auth backends + // No mounted auth methods ui, cmd := testAuthHelpCommand(t) cmd.client = client @@ -113,7 +113,7 @@ func TestAuthHelpCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Usage: vault auth -method=userpass" + expected := "Usage: vault login -method=userpass" combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) @@ -136,7 +136,7 @@ func TestAuthHelpCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Error listing authentication providers: " + expected := "Error listing auth methods: " combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) diff --git a/command/auth_list.go b/command/auth_list.go index 7adda391e6..d67b5bac9e 100644 --- a/command/auth_list.go +++ b/command/auth_list.go @@ -11,12 +11,9 @@ import ( "github.com/posener/complete" ) -// Ensure we are implementing the right interfaces. var _ cli.Command = (*AuthListCommand)(nil) var _ cli.CommandAutocomplete = (*AuthListCommand)(nil) -// AuthListCommand is a Command that lists the enabled authentication methods -// and data about them. type AuthListCommand struct { *BaseCommand @@ -24,25 +21,24 @@ type AuthListCommand struct { } func (c *AuthListCommand) Synopsis() string { - return "Lists enabled auth providers" + return "Lists enabled auth methods" } func (c *AuthListCommand) Help() string { helpText := ` -Usage: vault auth-methods [options] +Usage: vault auth list [options] - Lists the enabled authentication providers on the Vault server. This command - also outputs information about the provider including configuration and - human-friendly descriptions. A TTL of "system" indicates that the system - default is in use. + Lists the enabled auth methods on the Vault server. This command also outputs + information about the method including configuration and human-friendly + descriptions. A TTL of "system" indicates that the system default is in use. - List all enabled authentication providers: + List all enabled auth methods: - $ vault auth-list + $ vault auth list - List all enabled authentication providers with detailed output: + List all enabled auth methods with detailed output: - $ vault auth-list -detailed + $ vault auth list -detailed ` + c.Flags().Help() @@ -59,7 +55,7 @@ func (c *AuthListCommand) Flags() *FlagSets { Target: &c.flagDetailed, Default: false, Usage: "Print detailed information such as configuration and replication " + - "status about each authentication provider.", + "status about each auth method.", }) return set @@ -100,11 +96,11 @@ func (c *AuthListCommand) Run(args []string) int { } if c.flagDetailed { - c.UI.Output(tableOutput(c.detailedMounts(auths))) + c.UI.Output(tableOutput(c.detailedMounts(auths), nil)) return 0 } - c.UI.Output(tableOutput(c.simpleMounts(auths))) + c.UI.Output(tableOutput(c.simpleMounts(auths), nil)) return 0 } diff --git a/command/auth_test.go b/command/auth_test.go index ca1e916284..5ec0cf60d3 100644 --- a/command/auth_test.go +++ b/command/auth_test.go @@ -6,7 +6,6 @@ import ( "github.com/mitchellh/cli" - "github.com/hashicorp/vault/api" credToken "github.com/hashicorp/vault/builtin/credential/token" credUserpass "github.com/hashicorp/vault/builtin/credential/userpass" "github.com/hashicorp/vault/command/token" @@ -23,7 +22,7 @@ func testAuthCommand(tb testing.TB) (*cli.MockUi, *AuthCommand) { // Override to our own token helper tokenHelper: token.NewTestingTokenHelper(), }, - Handlers: map[string]AuthHandler{ + Handlers: map[string]LoginHandler{ "token": &credToken.CLIHandler{}, "userpass": &credUserpass.CLIHandler{}, }, @@ -33,55 +32,61 @@ func testAuthCommand(tb testing.TB) (*cli.MockUi, *AuthCommand) { func TestAuthCommand_Run(t *testing.T) { t.Parallel() - deprecations := []struct { - name string - args []string - out string - code int - }{ - { - "methods", - []string{"-methods"}, - "token/", - 0, - }, - { - "method_help", - []string{"-method", "userpass", "-method-help"}, - "Usage: vault auth -method=userpass", - 0, - }, - } - - t.Run("deprecations", func(t *testing.T) { + // TODO: remove in 0.9.0 + t.Run("deprecated_methods", func(t *testing.T) { t.Parallel() - for _, tc := range deprecations { - tc := tc + client, closer := testVaultServer(t) + defer closer() - t.Run(tc.name, func(t *testing.T) { - t.Parallel() + ui, cmd := testAuthCommand(t) + cmd.client = client - client, closer := testVaultServer(t) - defer closer() + // vault auth -methods -> vault auth list + code := cmd.Run([]string{"-methods"}) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String()) + } + stdout, stderr := ui.OutputWriter.String(), ui.ErrorWriter.String() - ui, cmd := testAuthCommand(t) - cmd.client = client + if expected := "WARNING!"; !strings.Contains(stderr, expected) { + t.Errorf("expected %q to contain %q", stderr, expected) + } - code := cmd.Run(tc.args) - if code != tc.code { - t.Errorf("expected %d to be %d", code, tc.code) - } - - combined := ui.OutputWriter.String() + ui.ErrorWriter.String() - if !strings.Contains(combined, tc.out) { - t.Errorf("expected %q to contain %q", combined, tc.out) - } - }) + if expected := "token/"; !strings.Contains(stdout, expected) { + t.Errorf("expected %q to contain %q", stdout, expected) } }) - t.Run("custom_path", func(t *testing.T) { + t.Run("deprecated_method_help", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testAuthCommand(t) + cmd.client = client + + // vault auth -method=foo -method-help -> vault auth help foo + code := cmd.Run([]string{ + "-method=userpass", + "-method-help", + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String()) + } + stdout, stderr := ui.OutputWriter.String(), ui.ErrorWriter.String() + + if expected := "WARNING!"; !strings.Contains(stderr, expected) { + t.Errorf("expected %q to contain %q", stderr, expected) + } + + if expected := "vault login"; !strings.Contains(stdout, expected) { + t.Errorf("expected %q to contain %q", stdout, expected) + } + }) + + t.Run("deprecated_login", func(t *testing.T) { t.Parallel() client, closer := testVaultServer(t) @@ -100,11 +105,7 @@ func TestAuthCommand_Run(t *testing.T) { ui, cmd := testAuthCommand(t) cmd.client = client - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - + // vault auth ARGS -> vault login ARGS code := cmd.Run([]string{ "-method", "userpass", "-path", "my-auth", @@ -112,420 +113,16 @@ func TestAuthCommand_Run(t *testing.T) { "password=test", }) if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) + t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String()) + } + stdout, stderr := ui.OutputWriter.String(), ui.ErrorWriter.String() + + if expected := "WARNING!"; !strings.Contains(stderr, expected) { + t.Errorf("expected %q to contain %q", stderr, expected) } - expected := "Success! You are now authenticated." - combined := ui.OutputWriter.String() + ui.ErrorWriter.String() - if !strings.Contains(combined, expected) { - t.Errorf("expected %q to be %q", combined, expected) - } - - storedToken, err := tokenHelper.Get() - if err != nil { - t.Fatal(err) - } - - if l, exp := len(storedToken), 36; l != exp { - t.Errorf("expected token to be %d characters, was %d: %q", exp, l, storedToken) - } - }) - - t.Run("no_verify", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ - Policies: []string{"default"}, - TTL: "30m", - NumUses: 1, - }) - if err != nil { - t.Fatal(err) - } - token := secret.Auth.ClientToken - - _, cmd := testAuthCommand(t) - cmd.client = client - - code := cmd.Run([]string{ - "-no-verify", - token, - }) - if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - lookup, err := client.Auth().Token().Lookup(token) - if err != nil { - t.Fatal(err) - } - - // There was 1 use to start, make sure we didn't use it (verifying would - // use it). - uses := lookup.TokenRemainingUses() - if uses != 1 { - t.Errorf("expected %d to be %d", uses, 1) - } - }) - - t.Run("no_store", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ - Policies: []string{"default"}, - TTL: "30m", - }) - if err != nil { - t.Fatal(err) - } - token := secret.Auth.ClientToken - - _, cmd := testAuthCommand(t) - cmd.client = client - - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - - // Ensure we have no token to start - if storedToken, err := tokenHelper.Get(); err != nil || storedToken != "" { - t.Errorf("expected token helper to be empty: %s: %q", err, storedToken) - } - - code := cmd.Run([]string{ - "-no-store", - token, - }) - if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - storedToken, err := tokenHelper.Get() - if err != nil { - t.Fatal(err) - } - - if exp := ""; storedToken != exp { - t.Errorf("expected %q to be %q", storedToken, exp) - } - }) - - t.Run("stores", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ - Policies: []string{"default"}, - TTL: "30m", - }) - if err != nil { - t.Fatal(err) - } - token := secret.Auth.ClientToken - - _, cmd := testAuthCommand(t) - cmd.client = client - - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - - code := cmd.Run([]string{ - token, - }) - if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - storedToken, err := tokenHelper.Get() - if err != nil { - t.Fatal(err) - } - - if storedToken != token { - t.Errorf("expected %q to be %q", storedToken, token) - } - }) - - t.Run("only_token", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { - t.Fatal(err) - } - if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ - "password": "test", - "policies": "default", - }); err != nil { - t.Fatal(err) - } - - ui, cmd := testAuthCommand(t) - cmd.client = client - - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - - code := cmd.Run([]string{ - "-only-token", - "-method", "userpass", - "username=test", - "password=test", - }) - if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - // Verify only the token was printed - token := ui.OutputWriter.String() + ui.ErrorWriter.String() - if l, exp := len(token), 36; l != exp { - t.Errorf("expected token to be %d characters, was %d: %q", exp, l, token) - } - - // Verify the token was not stored - if storedToken, err := tokenHelper.Get(); err != nil || storedToken != "" { - t.Fatalf("expted token to not be stored: %s: %q", err, storedToken) - } - }) - - t.Run("failure_no_store", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - ui, cmd := testAuthCommand(t) - cmd.client = client - - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - - code := cmd.Run([]string{ - "not-a-real-token", - }) - if exp := 2; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - expected := "Error verifying token: " - combined := ui.OutputWriter.String() + ui.ErrorWriter.String() - if !strings.Contains(combined, expected) { - t.Errorf("expected %q to contain %q", combined, expected) - } - - if storedToken, err := tokenHelper.Get(); err != nil || storedToken != "" { - t.Fatalf("expected token to not be stored: %s: %q", err, storedToken) - } - }) - - t.Run("wrap_auto_unwrap", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { - t.Fatal(err) - } - if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ - "password": "test", - "policies": "default", - }); err != nil { - t.Fatal(err) - } - - _, cmd := testAuthCommand(t) - cmd.client = client - - // Set the wrapping ttl to 5s. We can't set this via the flag because we - // override the client object before that particular flag is parsed. - client.SetWrappingLookupFunc(func(string, string) string { return "5m" }) - - code := cmd.Run([]string{ - "-method", "userpass", - "username=test", - "password=test", - }) - if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - // Unset the wrapping - client.SetWrappingLookupFunc(func(string, string) string { return "" }) - - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - token, err := tokenHelper.Get() - if err != nil || token == "" { - t.Fatalf("expected token from helper: %s: %q", err, token) - } - - // Ensure the resulting token is unwrapped - secret, err := client.Auth().Token().LookupSelf() - if err != nil { - t.Error(err) - } - - if secret.WrapInfo != nil { - t.Errorf("expected to be unwrapped: %#v", secret) - } - }) - - t.Run("wrap_only_token", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { - t.Fatal(err) - } - if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ - "password": "test", - "policies": "default", - }); err != nil { - t.Fatal(err) - } - - ui, cmd := testAuthCommand(t) - cmd.client = client - - // Set the wrapping ttl to 5s. We can't set this via the flag because we - // override the client object before that particular flag is parsed. - client.SetWrappingLookupFunc(func(string, string) string { return "5m" }) - - code := cmd.Run([]string{ - "-only-token", - "-method", "userpass", - "username=test", - "password=test", - }) - if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - // Unset the wrapping - client.SetWrappingLookupFunc(func(string, string) string { return "" }) - - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - storedToken, err := tokenHelper.Get() - if err != nil || storedToken != "" { - t.Fatalf("expected token to not be stored: %s: %q", err, storedToken) - } - - token := ui.OutputWriter.String() - if token == "" { - t.Errorf("expected %q to not be %q", token, "") - } - if strings.Contains(token, "\n") { - t.Errorf("expected %q to not contain %q", token, "\n") - } - - // Ensure the resulting token is, in fact, still wrapped. - client.SetToken(token) - secret, err := client.Logical().Unwrap("") - if err != nil { - t.Error(err) - } - if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" { - t.Fatalf("expected secret to have auth: %#v", secret) - } - }) - - t.Run("wrap_no_store", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServer(t) - defer closer() - - if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { - t.Fatal(err) - } - if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ - "password": "test", - "policies": "default", - }); err != nil { - t.Fatal(err) - } - - ui, cmd := testAuthCommand(t) - cmd.client = client - - // Set the wrapping ttl to 5s. We can't set this via the flag because we - // override the client object before that particular flag is parsed. - client.SetWrappingLookupFunc(func(string, string) string { return "5m" }) - - code := cmd.Run([]string{ - "-no-store", - "-method", "userpass", - "username=test", - "password=test", - }) - if exp := 0; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - // Unset the wrapping - client.SetWrappingLookupFunc(func(string, string) string { return "" }) - - tokenHelper, err := cmd.TokenHelper() - if err != nil { - t.Fatal(err) - } - storedToken, err := tokenHelper.Get() - if err != nil || storedToken != "" { - t.Fatalf("expected token to not be stored: %s: %q", err, storedToken) - } - - expected := "wrapping_token" - output := ui.OutputWriter.String() + ui.ErrorWriter.String() - if !strings.Contains(output, expected) { - t.Errorf("expected %q to contain %q", output, expected) - } - }) - - t.Run("communication_failure", func(t *testing.T) { - t.Parallel() - - client, closer := testVaultServerBad(t) - defer closer() - - ui, cmd := testAuthCommand(t) - cmd.client = client - - code := cmd.Run([]string{ - "token", - }) - if exp := 2; code != exp { - t.Errorf("expected %d to be %d", code, exp) - } - - expected := "Error verifying token: " - combined := ui.OutputWriter.String() + ui.ErrorWriter.String() - if !strings.Contains(combined, expected) { - t.Errorf("expected %q to contain %q", combined, expected) + if expected := "Success! You are now authenticated."; !strings.Contains(stdout, expected) { + t.Errorf("expected %q to contain %q", stdout, expected) } }) diff --git a/command/auth_tune.go b/command/auth_tune.go new file mode 100644 index 0000000000..958d11bd1f --- /dev/null +++ b/command/auth_tune.go @@ -0,0 +1,120 @@ +package command + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var _ cli.Command = (*AuthTuneCommand)(nil) +var _ cli.CommandAutocomplete = (*AuthTuneCommand)(nil) + +type AuthTuneCommand struct { + *BaseCommand + + flagDefaultLeaseTTL time.Duration + flagMaxLeaseTTL time.Duration +} + +func (c *AuthTuneCommand) Synopsis() string { + return "Tunes an auth method configuration" +} + +func (c *AuthTuneCommand) Help() string { + helpText := ` +Usage: vault auth tune [options] PATH + + Tunes the configuration options for the auth method at the given PATH. The + argument corresponds to the PATH where the auth method is enabled, not the + TYPE! + + Tune the default lease for the github auth method: + + $ vault auth tune -default-lease-ttl=72h github/ + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *AuthTuneCommand) Flags() *FlagSets { + set := c.flagSet(FlagSetHTTP) + + f := set.NewFlagSet("Command Options") + + f.DurationVar(&DurationVar{ + Name: "default-lease-ttl", + Target: &c.flagDefaultLeaseTTL, + Default: 0, + EnvVar: "", + Completion: complete.PredictAnything, + Usage: "The default lease TTL for this auth method. If unspecified, this " + + "defaults to the Vault server's globally configured default lease TTL, " + + "or a previously configured value for the auth method.", + }) + + f.DurationVar(&DurationVar{ + Name: "max-lease-ttl", + Target: &c.flagMaxLeaseTTL, + Default: 0, + EnvVar: "", + Completion: complete.PredictAnything, + Usage: "The maximum lease TTL for this auth method. If unspecified, this " + + "defaults to the Vault server's globally configured maximum lease TTL, " + + "or a previously configured value for the auth method.", + }) + + return set +} + +func (c *AuthTuneCommand) AutocompleteArgs() complete.Predictor { + return c.PredictVaultAuths() +} + +func (c *AuthTuneCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *AuthTuneCommand) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + args = f.Args() + switch { + case len(args) < 1: + c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) + return 1 + case len(args) > 1: + c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) + return 1 + } + + client, err := c.Client() + if err != nil { + c.UI.Error(err.Error()) + return 2 + } + + // Append /auth (since that's where auths live) and a trailing slash to + // indicate it's a path in output + mountPath := ensureTrailingSlash(sanitizePath(args[0])) + + if err := client.Sys().TuneMount("/auth/"+mountPath, api.MountConfigInput{ + DefaultLeaseTTL: ttlToAPI(c.flagDefaultLeaseTTL), + MaxLeaseTTL: ttlToAPI(c.flagMaxLeaseTTL), + }); err != nil { + c.UI.Error(fmt.Sprintf("Error tuning auth method %s: %s", mountPath, err)) + return 2 + } + + c.UI.Output(fmt.Sprintf("Success! Tuned the auth method at: %s", mountPath)) + return 0 +} diff --git a/command/auth_tune_test.go b/command/auth_tune_test.go new file mode 100644 index 0000000000..61a36441d7 --- /dev/null +++ b/command/auth_tune_test.go @@ -0,0 +1,149 @@ +package command + +import ( + "strings" + "testing" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" +) + +func testAuthTuneCommand(tb testing.TB) (*cli.MockUi, *AuthTuneCommand) { + tb.Helper() + + ui := cli.NewMockUi() + return ui, &AuthTuneCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + +func TestAuthTuneCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "not_enough_args", + []string{}, + "Not enough arguments", + 1, + }, + { + "too_many_args", + []string{"foo", "bar"}, + "Too many arguments", + 1, + }, + } + + t.Run("validations", func(t *testing.T) { + t.Parallel() + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ui, cmd := testAuthTuneCommand(t) + + code := cmd.Run(tc.args) + if code != tc.code { + t.Errorf("expected %d to be %d", code, tc.code) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, tc.out) { + t.Errorf("expected %q to contain %q", combined, tc.out) + } + }) + } + }) + + t.Run("integration", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testAuthTuneCommand(t) + cmd.client = client + + // Mount + if err := client.Sys().EnableAuthWithOptions("my-auth", &api.EnableAuthOptions{ + Type: "userpass", + }); err != nil { + t.Fatal(err) + } + + code := cmd.Run([]string{ + "-default-lease-ttl", "30m", + "-max-lease-ttl", "1h", + "my-auth/", + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Success! Tuned the auth method at: my-auth/" + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + + auths, err := client.Sys().ListAuth() + if err != nil { + t.Fatal(err) + } + + mountInfo, ok := auths["my-auth/"] + if !ok { + t.Fatalf("expected auth to exist") + } + if exp := "userpass"; mountInfo.Type != exp { + t.Errorf("expected %q to be %q", mountInfo.Type, exp) + } + if exp := 1800; mountInfo.Config.DefaultLeaseTTL != exp { + t.Errorf("expected %d to be %d", mountInfo.Config.DefaultLeaseTTL, exp) + } + if exp := 3600; mountInfo.Config.MaxLeaseTTL != exp { + t.Errorf("expected %d to be %d", mountInfo.Config.MaxLeaseTTL, exp) + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testAuthTuneCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "userpass/", + }) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error tuning auth method userpass/: " + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("no_tabs", func(t *testing.T) { + t.Parallel() + + _, cmd := testAuthTuneCommand(t) + assertNoTabs(t, cmd) + }) +} diff --git a/command/token/helper.go b/command/token/helper.go index db068beb29..cac79487fa 100644 --- a/command/token/helper.go +++ b/command/token/helper.go @@ -3,7 +3,7 @@ package token // TokenHelper is an interface that contains basic operations that must be // implemented by a token helper type TokenHelper interface { - // Path displays a backend-specific path; for the internal helper this + // Path displays a method-specific path; for the internal helper this // is the location of the token stored on disk; for the external helper // this is the location of the binary being invoked Path() string