Add lease subcommand

This commit is contained in:
Seth Vargo 2017-09-07 21:59:31 -04:00
parent 6b75e6e2bf
commit 9a80d9a8f8
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
5 changed files with 93 additions and 57 deletions

40
command/lease.go Normal file
View File

@ -0,0 +1,40 @@
package command
import (
"strings"
"github.com/mitchellh/cli"
)
var _ cli.Command = (*LeaseCommand)(nil)
type LeaseCommand struct {
*BaseCommand
}
func (c *LeaseCommand) Synopsis() string {
return "Interact with leases"
}
func (c *LeaseCommand) Help() string {
helpText := `
Usage: vault lease <subcommand> [options] [args]
This command groups subcommands for interacting with leases. Users can revoke
or renew leases.
Renew a lease:
$ vault lease renew database/creds/readonly/2f6a614c...
Revoke a lease:
$ vault lease revoke database/creds/readonly/2f6a614c...
`
return strings.TrimSpace(helpText)
}
func (c *LeaseCommand) Run(args []string) int {
return cli.RunResultHelp
}

View File

@ -9,24 +9,22 @@ import (
"github.com/posener/complete" "github.com/posener/complete"
) )
// Ensure we are implementing the right interfaces. var _ cli.Command = (*LeaseRenewCommand)(nil)
var _ cli.Command = (*RenewCommand)(nil) var _ cli.CommandAutocomplete = (*LeaseRenewCommand)(nil)
var _ cli.CommandAutocomplete = (*RenewCommand)(nil)
// RenewCommand is a Command that mounts a new mount. type LeaseRenewCommand struct {
type RenewCommand struct {
*BaseCommand *BaseCommand
flagIncrement time.Duration flagIncrement time.Duration
} }
func (c *RenewCommand) Synopsis() string { func (c *LeaseRenewCommand) Synopsis() string {
return "Renews the lease of a secret" return "Renews the lease of a secret"
} }
func (c *RenewCommand) Help() string { func (c *LeaseRenewCommand) Help() string {
helpText := ` helpText := `
Usage: vault renew [options] ID Usage: vault lease renew [options] ID
Renews the lease on a secret, extending the time that it can be used before Renews the lease on a secret, extending the time that it can be used before
it is revoked by Vault. it is revoked by Vault.
@ -38,7 +36,7 @@ Usage: vault renew [options] ID
Renew a secret: Renew a secret:
$ vault renew database/creds/readonly/2f6a614c-4aa2-7b19-24b9-ad944a8d4de6 $ vault lease renew database/creds/readonly/2f6a614c...
Lease renewal will fail if the secret is not renewable, the secret has already Lease renewal will fail if the secret is not renewable, the secret has already
been revoked, or if the secret has already reached its maximum TTL. been revoked, or if the secret has already reached its maximum TTL.
@ -50,7 +48,7 @@ Usage: vault renew [options] ID
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *RenewCommand) Flags() *FlagSets { func (c *LeaseRenewCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat) set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options") f := set.NewFlagSet("Command Options")
@ -67,15 +65,15 @@ func (c *RenewCommand) Flags() *FlagSets {
return set return set
} }
func (c *RenewCommand) AutocompleteArgs() complete.Predictor { func (c *LeaseRenewCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictAnything return complete.PredictAnything
} }
func (c *RenewCommand) AutocompleteFlags() complete.Flags { func (c *LeaseRenewCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions() return c.Flags().Completions()
} }
func (c *RenewCommand) Run(args []string) int { func (c *LeaseRenewCommand) Run(args []string) int {
f := c.Flags() f := c.Flags()
if err := f.Parse(args); err != nil { if err := f.Parse(args); err != nil {

View File

@ -8,20 +8,20 @@ import (
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func testRenewCommand(tb testing.TB) (*cli.MockUi, *RenewCommand) { func testLeaseRenewCommand(tb testing.TB) (*cli.MockUi, *LeaseRenewCommand) {
tb.Helper() tb.Helper()
ui := cli.NewMockUi() ui := cli.NewMockUi()
return ui, &RenewCommand{ return ui, &LeaseRenewCommand{
BaseCommand: &BaseCommand{ BaseCommand: &BaseCommand{
UI: ui, UI: ui,
}, },
} }
} }
// testRenewCommandMountAndLease mounts a leased secret backend and returns // testLeaseRenewCommandMountAndLease mounts a leased secret backend and returns
// the leaseID of an item. // the leaseID of an item.
func testRenewCommandMountAndLease(tb testing.TB, client *api.Client) string { func testLeaseRenewCommandMountAndLease(tb testing.TB, client *api.Client) string {
if err := client.Sys().Mount("testing", &api.MountInput{ if err := client.Sys().Mount("testing", &api.MountInput{
Type: "generic-leased", Type: "generic-leased",
}); err != nil { }); err != nil {
@ -47,7 +47,7 @@ func testRenewCommandMountAndLease(tb testing.TB, client *api.Client) string {
return secret.LeaseID return secret.LeaseID
} }
func TestRenewCommand_Run(t *testing.T) { func TestLeaseRenewCommand_Run(t *testing.T) {
t.Parallel() t.Parallel()
cases := []struct { cases := []struct {
@ -100,9 +100,9 @@ func TestRenewCommand_Run(t *testing.T) {
client, closer := testVaultServer(t) client, closer := testVaultServer(t)
defer closer() defer closer()
leaseID := testRenewCommandMountAndLease(t, client) leaseID := testLeaseRenewCommandMountAndLease(t, client)
ui, cmd := testRenewCommand(t) ui, cmd := testLeaseRenewCommand(t)
cmd.client = client cmd.client = client
if tc.args != nil { if tc.args != nil {
@ -127,9 +127,9 @@ func TestRenewCommand_Run(t *testing.T) {
client, closer := testVaultServer(t) client, closer := testVaultServer(t)
defer closer() defer closer()
leaseID := testRenewCommandMountAndLease(t, client) leaseID := testLeaseRenewCommandMountAndLease(t, client)
_, cmd := testRenewCommand(t) _, cmd := testLeaseRenewCommand(t)
cmd.client = client cmd.client = client
code := cmd.Run([]string{leaseID}) code := cmd.Run([]string{leaseID})
@ -144,7 +144,7 @@ func TestRenewCommand_Run(t *testing.T) {
client, closer := testVaultServerBad(t) client, closer := testVaultServerBad(t)
defer closer() defer closer()
ui, cmd := testRenewCommand(t) ui, cmd := testLeaseRenewCommand(t)
cmd.client = client cmd.client = client
code := cmd.Run([]string{ code := cmd.Run([]string{
@ -164,7 +164,7 @@ func TestRenewCommand_Run(t *testing.T) {
t.Run("no_tabs", func(t *testing.T) { t.Run("no_tabs", func(t *testing.T) {
t.Parallel() t.Parallel()
_, cmd := testRenewCommand(t) _, cmd := testLeaseRenewCommand(t)
assertNoTabs(t, cmd) assertNoTabs(t, cmd)
}) })
} }

View File

@ -8,50 +8,48 @@ import (
"github.com/posener/complete" "github.com/posener/complete"
) )
// Ensure we are implementing the right interfaces. var _ cli.Command = (*LeaseRevokeCommand)(nil)
var _ cli.Command = (*ReadCommand)(nil) var _ cli.CommandAutocomplete = (*LeaseRevokeCommand)(nil)
var _ cli.CommandAutocomplete = (*ReadCommand)(nil)
// RevokeCommand is a Command that mounts a new mount. type LeaseRevokeCommand struct {
type RevokeCommand struct {
*BaseCommand *BaseCommand
flagForce bool flagForce bool
flagPrefix bool flagPrefix bool
} }
func (c *RevokeCommand) Synopsis() string { func (c *LeaseRevokeCommand) Synopsis() string {
return "Revokes leases and secrets" return "Revokes leases and secrets"
} }
func (c *RevokeCommand) Help() string { func (c *LeaseRevokeCommand) Help() string {
helpText := ` helpText := `
Usage: vault revoke [options] ID Usage: vault lease revoke [options] ID
Revokes secrets by their lease ID. This command can revoke a single secret Revokes secrets by their lease ID. This command can revoke a single secret
or multiple secrets based on a path-matched prefix. or multiple secrets based on a path-matched prefix.
Revoke a single lease: Revoke a single lease:
$ vault revoke database/creds/readonly/2f6a614c... $ vault lease revoke database/creds/readonly/2f6a614c...
Revoke all leases for a role: Revoke all leases for a role:
$ vault revoke -prefix aws/creds/deploy $ vault lease revoke -prefix aws/creds/deploy
Force delete leases from Vault even if backend revocation fails: Force delete leases from Vault even if secret engine revocation fails:
$ vault revoke -force -prefix consul/creds $ vault lease revoke -force -prefix consul/creds
For a full list of examples and paths, please see the documentation that For a full list of examples and paths, please see the documentation that
corresponds to the secret backend in use. corresponds to the secret engine in use.
` + c.Flags().Help() ` + c.Flags().Help()
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *RevokeCommand) Flags() *FlagSets { func (c *LeaseRevokeCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP) set := c.flagSet(FlagSetHTTP)
f := set.NewFlagSet("Command Options") f := set.NewFlagSet("Command Options")
@ -60,10 +58,10 @@ func (c *RevokeCommand) Flags() *FlagSets {
Aliases: []string{"f"}, Aliases: []string{"f"},
Target: &c.flagForce, Target: &c.flagForce,
Default: false, Default: false,
Usage: "Delete the lease from Vault even if the backend revocation " + Usage: "Delete the lease from Vault even if the secret engine revocation " +
"fails. This is meant for recovery situations where the secret " + "fails. This is meant for recovery situations where the secret " +
"in the backend was manually removed. If this flag is specified, " + "in the target secret engine was manually removed. If this flag is " +
"-prefix is also required.", "specified, -prefix is also required.",
}) })
f.BoolVar(&BoolVar{ f.BoolVar(&BoolVar{
@ -77,15 +75,15 @@ func (c *RevokeCommand) Flags() *FlagSets {
return set return set
} }
func (c *RevokeCommand) AutocompleteArgs() complete.Predictor { func (c *LeaseRevokeCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultFiles() return c.PredictVaultFiles()
} }
func (c *RevokeCommand) AutocompleteFlags() complete.Flags { func (c *LeaseRevokeCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions() return c.Flags().Completions()
} }
func (c *RevokeCommand) Run(args []string) int { func (c *LeaseRevokeCommand) Run(args []string) int {
f := c.Flags() f := c.Flags()
if err := f.Parse(args); err != nil { if err := f.Parse(args); err != nil {
@ -94,13 +92,11 @@ func (c *RevokeCommand) Run(args []string) int {
} }
args = f.Args() args = f.Args()
leaseID, remaining, err := extractID(args) switch {
if err != nil { case len(args) < 1:
c.UI.Error(err.Error()) c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
return 1 return 1
} case len(args) > 1:
if len(remaining) > 0 {
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
return 1 return 1
} }
@ -116,10 +112,12 @@ func (c *RevokeCommand) Run(args []string) int {
return 2 return 2
} }
leaseID := strings.TrimSpace(args[0])
switch { switch {
case c.flagForce && c.flagPrefix: case c.flagForce && c.flagPrefix:
c.UI.Warn(wrapAtLength("Warning! Force-removing leases can cause Vault " + c.UI.Warn(wrapAtLength("Warning! Force-removing leases can cause Vault " +
"to become out of sync with credential backends!")) "to become out of sync with secret engines!"))
if err := client.Sys().RevokeForce(leaseID); err != nil { if err := client.Sys().RevokeForce(leaseID); err != nil {
c.UI.Error(fmt.Sprintf("Error force revoking leases with prefix %s: %s", leaseID, err)) c.UI.Error(fmt.Sprintf("Error force revoking leases with prefix %s: %s", leaseID, err))
return 2 return 2

View File

@ -8,18 +8,18 @@ import (
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func testRevokeCommand(tb testing.TB) (*cli.MockUi, *RevokeCommand) { func testLeaseRevokeCommand(tb testing.TB) (*cli.MockUi, *LeaseRevokeCommand) {
tb.Helper() tb.Helper()
ui := cli.NewMockUi() ui := cli.NewMockUi()
return ui, &RevokeCommand{ return ui, &LeaseRevokeCommand{
BaseCommand: &BaseCommand{ BaseCommand: &BaseCommand{
UI: ui, UI: ui,
}, },
} }
} }
func TestRevokeCommand_Run(t *testing.T) { func TestLeaseRevokeCommand_Run(t *testing.T) {
t.Parallel() t.Parallel()
cases := []struct { cases := []struct {
@ -85,7 +85,7 @@ func TestRevokeCommand_Run(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
ui, cmd := testRevokeCommand(t) ui, cmd := testLeaseRevokeCommand(t)
cmd.client = client cmd.client = client
tc.args = append(tc.args, secret.LeaseID) tc.args = append(tc.args, secret.LeaseID)
@ -108,7 +108,7 @@ func TestRevokeCommand_Run(t *testing.T) {
client, closer := testVaultServerBad(t) client, closer := testVaultServerBad(t)
defer closer() defer closer()
ui, cmd := testRevokeCommand(t) ui, cmd := testLeaseRevokeCommand(t)
cmd.client = client cmd.client = client
code := cmd.Run([]string{ code := cmd.Run([]string{
@ -128,7 +128,7 @@ func TestRevokeCommand_Run(t *testing.T) {
t.Run("no_tabs", func(t *testing.T) { t.Run("no_tabs", func(t *testing.T) {
t.Parallel() t.Parallel()
_, cmd := testRevokeCommand(t) _, cmd := testLeaseRevokeCommand(t)
assertNoTabs(t, cmd) assertNoTabs(t, cmd)
}) })
} }