Update token-revoke command
This commit is contained in:
parent
5dcd51302e
commit
c63e1a7b96
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue