Update token-revoke command

This commit is contained in:
Seth Vargo 2017-09-05 00:05:14 -04:00
parent 5dcd51302e
commit c63e1a7b96
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
2 changed files with 343 additions and 185 deletions

View File

@ -4,135 +4,173 @@ import (
"fmt"
"strings"
"github.com/hashicorp/vault/meta"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*TokenRevokeCommand)(nil)
var _ cli.CommandAutocomplete = (*TokenRevokeCommand)(nil)
// TokenRevokeCommand is a Command that mounts a new mount.
type TokenRevokeCommand struct {
meta.Meta
*BaseCommand
flagAccessor bool
flagSelf bool
flagMode string
}
func (c *TokenRevokeCommand) Synopsis() string {
return "Revokes tokens and their children"
}
func (c *TokenRevokeCommand) Help() string {
helpText := `
Usage: vault token-revoke [options] [TOKEN | ACCESSOR]
Revokes authentication tokens and their children. If a TOKEN is not provided,
the locally authenticated token is used. The "-mode" flag can be used to
control the behavior of the revocation. See the "-mode" flag documentation
for more information.
Revoke a token and all the token's children:
$ vault token-revoke 96ddf4bc-d217-f3ba-f9bd-017055595017
Revoke a token leaving the token's children:
$ vault token-revoke -mode=orphan 96ddf4bc-d217-f3ba-f9bd-017055595017
Revoke a token by accessor:
$ vault token-revoke -accessor 9793c9b3-e04a-46f3-e7b8-748d7da248da
For a full list of examples, please see the documentation.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *TokenRevokeCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
f := set.NewFlagSet("Command Options")
f.BoolVar(&BoolVar{
Name: "accessor",
Target: &c.flagAccessor,
Default: false,
EnvVar: "",
Completion: complete.PredictNothing,
Usage: "Treat the argument as an accessor instead of a token.",
})
f.BoolVar(&BoolVar{
Name: "self",
Target: &c.flagSelf,
Default: false,
EnvVar: "",
Completion: complete.PredictNothing,
Usage: "Perform the revocation on the currently authenticated token.",
})
f.StringVar(&StringVar{
Name: "mode",
Target: &c.flagMode,
Default: "",
EnvVar: "",
Completion: complete.PredictSet("orphan", "path"),
Usage: "Type of revocation to perform. If unspecified, Vault will revoke " +
"the token and all of the token's children. If \"orphan\", Vault will " +
"revoke only the token, leaving the children as orphans. If \"path\", " +
"tokens created from the given authentication path prefix are deleted " +
"along with their children.",
})
return set
}
func (c *TokenRevokeCommand) AutocompleteArgs() complete.Predictor {
return nil
}
func (c *TokenRevokeCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *TokenRevokeCommand) Run(args []string) int {
var mode string
var accessor bool
var self bool
var token string
flags := c.Meta.FlagSet("token-revoke", meta.FlagSetDefault)
flags.BoolVar(&accessor, "accessor", false, "")
flags.BoolVar(&self, "self", false, "")
flags.StringVar(&mode, "mode", "", "")
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = flags.Args()
switch {
case len(args) == 1 && !self:
token = args[0]
case len(args) != 0 && self:
flags.Usage()
c.Ui.Error(fmt.Sprintf(
"\ntoken-revoke expects no arguments when revoking self"))
args = f.Args()
token := ""
if len(args) > 0 {
token = strings.TrimSpace(args[0])
}
switch c.flagMode {
case "", "orphan", "path":
default:
c.UI.Error(fmt.Sprintf("Invalid mode: %s", c.flagMode))
return 1
case len(args) != 1 && !self:
flags.Usage()
c.Ui.Error(fmt.Sprintf(
"\ntoken-revoke expects one argument or the 'self' flag"))
}
switch {
case c.flagSelf && len(args) > 0:
c.UI.Error(fmt.Sprintf("Too many arguments with -self (expected 0, got %d)", len(args)))
return 1
case !c.flagSelf && len(args) > 1:
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1 or -self, got %d)", len(args)))
return 1
case !c.flagSelf && len(args) < 1:
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1 or -self, got %d)", len(args)))
return 1
case c.flagSelf && c.flagAccessor:
c.UI.Error("Cannot use -self with -accessor!")
return 1
case c.flagSelf && c.flagMode != "":
c.UI.Error("Cannot use -self with -mode!")
return 1
case c.flagAccessor && c.flagMode == "orphan":
c.UI.Error("Cannot use -accessor with -mode=orphan!")
return 1
case c.flagAccessor && c.flagMode == "path":
c.UI.Error("Cannot use -accessor with -mode=path!")
return 1
}
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing client: %s", err))
c.UI.Error(err.Error())
return 2
}
var fn func(string) error
var revokeFn func(string) error
// Handle all 6 possible combinations
switch {
case !accessor && self && mode == "":
fn = client.Auth().Token().RevokeSelf
case !accessor && !self && mode == "":
fn = client.Auth().Token().RevokeTree
case !accessor && !self && mode == "orphan":
fn = client.Auth().Token().RevokeOrphan
case !accessor && !self && mode == "path":
fn = client.Sys().RevokePrefix
case accessor && !self && mode == "":
fn = client.Auth().Token().RevokeAccessor
case accessor && self:
c.Ui.Error("token-revoke cannot be run on self when 'accessor' flag is set")
return 1
case self && mode != "":
c.Ui.Error("token-revoke cannot be run on self when 'mode' flag is set")
return 1
case accessor && mode == "orphan":
c.Ui.Error("token-revoke cannot be run for 'orphan' mode when 'accessor' flag is set")
return 1
case accessor && mode == "path":
c.Ui.Error("token-revoke cannot be run for 'path' mode when 'accessor' flag is set")
return 1
case !c.flagAccessor && c.flagSelf && c.flagMode == "":
revokeFn = client.Auth().Token().RevokeSelf
case !c.flagAccessor && !c.flagSelf && c.flagMode == "":
revokeFn = client.Auth().Token().RevokeTree
case !c.flagAccessor && !c.flagSelf && c.flagMode == "orphan":
revokeFn = client.Auth().Token().RevokeOrphan
case !c.flagAccessor && !c.flagSelf && c.flagMode == "path":
revokeFn = client.Sys().RevokePrefix
case c.flagAccessor && !c.flagSelf && c.flagMode == "":
revokeFn = client.Auth().Token().RevokeAccessor
}
if err := fn(token); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error revoking token: %s", err))
if err := revokeFn(token); err != nil {
c.UI.Error(fmt.Sprintf("Error revoking token: %s", err))
return 2
}
c.Ui.Output("Success! Token revoked if it existed.")
c.UI.Output("Success! Revoked token (if it existed)")
return 0
}
func (c *TokenRevokeCommand) Synopsis() string {
return "Revoke one or more auth tokens"
}
func (c *TokenRevokeCommand) Help() string {
helpText := `
Usage: vault token-revoke [options] [token|accessor]
Revoke one or more auth tokens.
This command revokes auth tokens. Use the "revoke" command for
revoking secrets.
Depending on the flags used, auth tokens can be revoked in multiple ways
depending on the "-mode" flag:
* Without any value, the token specified and all of its children
will be revoked.
* With the "orphan" value, only the specific token will be revoked.
All of its children will be orphaned.
* With the "path" value, tokens created from the given auth path
prefix will be deleted, along with all their children. In this case
the "token" arg above is actually a "path". This mode does *not*
work with token values or parts of token values.
Token can be revoked using the token accessor. This can be done by
setting the '-accessor' flag. Note that when '-accessor' flag is set,
'-mode' should not be set for 'orphan' or 'path'. This is because,
a token accessor always revokes the token along with its child tokens.
General Options:
` + meta.GeneralOptionsUsage() + `
Token Options:
-accessor A boolean flag, if set, treats the argument as an accessor of the token.
Note that accessor can also be used for looking up the token properties
via '/auth/token/lookup-accessor/<accessor>' endpoint.
Accessor is used when there is no access to token ID.
-self A boolean flag, if set, the operation is performed on the currently
authenticated token i.e. lookup-self.
-mode=value The type of revocation to do. See the documentation
above for more information.
`
return strings.TrimSpace(helpText)
}

View File

@ -1,102 +1,222 @@
package command
import (
"strings"
"testing"
"github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/meta"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli"
)
func TestTokenRevokeAccessor(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
func testTokenRevokeCommand(tb testing.TB) (*cli.MockUi, *TokenRevokeCommand) {
tb.Helper()
ui := new(cli.MockUi)
c := &TokenRevokeCommand{
Meta: meta.Meta{
ClientToken: token,
Ui: ui,
ui := cli.NewMockUi()
return ui, &TokenRevokeCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
args := []string{
"-address", addr,
}
// Run it once for client
c.Run(args)
// Create a token
client, err := c.Client()
if err != nil {
t.Fatalf("err: %s", err)
}
resp, err := client.Auth().Token().Create(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
// Treat the argument as accessor
args = append(args, "-accessor")
if code := c.Run(args); code == 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Verify it worked with proper accessor
args1 := append(args, resp.Auth.Accessor)
if code := c.Run(args1); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Fail if mode is set to 'orphan' when accessor is set
args2 := append(args, "-mode=\"orphan\"")
if code := c.Run(args2); code == 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Fail if mode is set to 'path' when accessor is set
args3 := append(args, "-mode=\"path\"")
if code := c.Run(args3); code == 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
func TestTokenRevoke(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
func TestTokenRevokeCommand_Run(t *testing.T) {
t.Parallel()
ui := new(cli.MockUi)
c := &TokenRevokeCommand{
Meta: meta.Meta{
ClientToken: token,
Ui: ui,
validations := []struct {
name string
args []string
out string
code int
}{
{
"bad_mode",
[]string{"-mode=banana"},
"Invalid mode",
1,
},
{
"empty",
nil,
"Not enough arguments",
1,
},
{
"args_with_self",
[]string{"-self", "abcd1234"},
"Too many arguments",
1,
},
{
"too_many_args",
[]string{"abcd1234", "efgh5678"},
"Too many arguments",
1,
},
{
"self_and_accessor",
[]string{"-self", "-accessor"},
"Cannot use -self with -accessor",
1,
},
{
"self_and_mode",
[]string{"-self", "-mode=orphan"},
"Cannot use -self with -mode",
1,
},
{
"accessor_and_mode_orphan",
[]string{"-accessor", "-mode=orphan", "abcd1234"},
"Cannot use -accessor with -mode=orphan",
1,
},
{
"accessor_and_mode_path",
[]string{"-accessor", "-mode=path", "abcd1234"},
"Cannot use -accessor with -mode=path",
1,
},
}
args := []string{
"-address", addr,
}
t.Run("validations", func(t *testing.T) {
t.Parallel()
// Run it once for client
c.Run(args)
for _, tc := range validations {
tc := tc
// Create a token
client, err := c.Client()
if err != nil {
t.Fatalf("err: %s", err)
}
resp, err := client.Auth().Token().Create(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Verify it worked
args = append(args, resp.Auth.ClientToken)
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
ui, cmd := testTokenRevokeCommand(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("token", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
token, _ := testTokenAndAccessor(t, client)
ui, cmd := testTokenRevokeCommand(t)
cmd.client = client
code := cmd.Run([]string{
token,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Revoked token"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
secret, err := client.Auth().Token().Lookup(token)
if secret != nil || err == nil {
t.Errorf("expected token to be revoked: %#v", secret)
}
})
t.Run("self", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testTokenRevokeCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-self",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Revoked token"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
secret, err := client.Auth().Token().LookupSelf()
if secret != nil || err == nil {
t.Errorf("expected token to be revoked: %#v", secret)
}
})
t.Run("accessor", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
token, accessor := testTokenAndAccessor(t, client)
ui, cmd := testTokenRevokeCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-accessor",
accessor,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Revoked token"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
secret, err := client.Auth().Token().Lookup(token)
if secret != nil || err == nil {
t.Errorf("expected token to be revoked: %#v", secret)
}
})
t.Run("communication_failure", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testTokenRevokeCommand(t)
cmd.client = client
code := cmd.Run([]string{
"abcd1234",
})
if exp := 2; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error revoking token: "
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 := testTokenRevokeCommand(t)
assertNoTabs(t, cmd)
})
}