1
0

Rename mounts to secrets engines and add the subcommand

This commit is contained in:
Seth Vargo 2017-09-07 22:01:31 -04:00
parent d4b68970f3
commit 387cce957e
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
13 changed files with 476 additions and 371 deletions

View File

@ -1,128 +0,0 @@
package command
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/api"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*MountTuneCommand)(nil)
var _ cli.CommandAutocomplete = (*MountTuneCommand)(nil)
// MountTuneCommand is a Command that remounts a mounted secret backend
// to a new endpoint.
type MountTuneCommand struct {
*BaseCommand
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
}
func (c *MountTuneCommand) Synopsis() string {
return "Tunes an existing mount's configuration"
}
func (c *MountTuneCommand) Help() string {
helpText := `
Usage: vault mount-tune [options] PATH
Tune the configuration options for a mounted secret backend at the given
path. The argument corresponds to the PATH of the mount, not the TYPE!
Tune the default lease for the PKI secret backend:
$ vault mount-tune -default-lease-ttl=72h pki/
For a full list of examples and paths, please see the documentation that
corresponds to the secret backend in use.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *MountTuneCommand) 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 backend. If unspecified, this " +
"defaults to the Vault server's globally configured default lease TTL, " +
"or a previously configured value for the backend.",
})
f.DurationVar(&DurationVar{
Name: "max-lease-ttl",
Target: &c.flagMaxLeaseTTL,
Default: 0,
EnvVar: "",
Completion: complete.PredictAnything,
Usage: "The maximum lease TTL for this backend. If unspecified, this " +
"defaults to the Vault server's globally configured maximum lease TTL, " +
"or a previously configured value for the backend.",
})
return set
}
func (c *MountTuneCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultMounts()
}
func (c *MountTuneCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *MountTuneCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
mountPath, remaining, err := extractPath(args)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if len(remaining) > 0 {
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 a trailing slash to indicate it's a path in output
mountPath = ensureTrailingSlash(mountPath)
mountConfig := api.MountConfigInput{
DefaultLeaseTTL: c.flagDefaultLeaseTTL.String(),
MaxLeaseTTL: c.flagMaxLeaseTTL.String(),
}
if err := client.Sys().TuneMount(mountPath, mountConfig); err != nil {
c.UI.Error(fmt.Sprintf("Error tuning mount %s: %s", mountPath, err))
return 2
}
c.UI.Output(fmt.Sprintf("Success! Tuned the mount at: %s", mountPath))
return 0
}

43
command/secrets.go Normal file
View File

@ -0,0 +1,43 @@
package command
import (
"strings"
"github.com/mitchellh/cli"
)
var _ cli.Command = (*SecretsCommand)(nil)
type SecretsCommand struct {
*BaseCommand
}
func (c *SecretsCommand) Synopsis() string {
return "Interact with secrets engines"
}
func (c *SecretsCommand) Help() string {
helpText := `
Usage: vault secrets <subcommand> [options] [args]
This command groups subcommands for interacting with Vault's secrets engines.
Each secret engine behaves differently. Please see the documentation for
more information.
List all enabled secrets engines:
$ vault secrets list
Enable a new secrets engine:
$ vault secrets enable database
Please see the individual subcommand help for detailed usage information.
`
return strings.TrimSpace(helpText)
}
func (c *SecretsCommand) Run(args []string) int {
return cli.RunResultHelp
}

View File

@ -0,0 +1,84 @@
package command
import (
"fmt"
"strings"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var _ cli.Command = (*SecretsDisableCommand)(nil)
var _ cli.CommandAutocomplete = (*SecretsDisableCommand)(nil)
type SecretsDisableCommand struct {
*BaseCommand
}
func (c *SecretsDisableCommand) Synopsis() string {
return "Disable a secret engine"
}
func (c *SecretsDisableCommand) Help() string {
helpText := `
Usage: vault secrets disable [options] PATH
Disables a secrets engine at the given PATH. The argument corresponds to
the enabled PATH of the engine, not the TYPE! All secrets created by this
engine are revoked and its Vault data is removed.
Disable the secrets engine enabled at aws/:
$ vault secrets disable aws/
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *SecretsDisableCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *SecretsDisableCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultMounts()
}
func (c *SecretsDisableCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *SecretsDisableCommand) 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
}
path := ensureTrailingSlash(sanitizePath(args[0]))
if err := client.Sys().Unmount(path); err != nil {
c.UI.Error(fmt.Sprintf("Error disabling secrets engine at %s: %s", path, err))
return 2
}
c.UI.Output(fmt.Sprintf("Success! Disabled the secrets engine (if it existed) at: %s", path))
return 0
}

View File

@ -8,18 +8,18 @@ import (
"github.com/mitchellh/cli"
)
func testUnmountCommand(tb testing.TB) (*cli.MockUi, *UnmountCommand) {
func testSecretsDisableCommand(tb testing.TB) (*cli.MockUi, *SecretsDisableCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &UnmountCommand{
return ui, &SecretsDisableCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestUnmountCommand_Run(t *testing.T) {
func TestSecretsDisableCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
@ -29,27 +29,27 @@ func TestUnmountCommand_Run(t *testing.T) {
code int
}{
{
"empty",
nil,
"Missing PATH!",
"not_enough_args",
[]string{},
"Not enough arguments",
1,
},
{
"slash",
[]string{"/"},
"Missing PATH!",
"too_many_args",
[]string{"foo", "bar"},
"Too many arguments",
1,
},
{
"not_real",
[]string{"not_real"},
"Success! Unmounted the secret backend (if it existed) at: not_real/",
"Success! Disabled the secrets engine (if it existed) at: not_real/",
0,
},
{
"default",
[]string{"secret"},
"Success! Unmounted the secret backend (if it existed) at: secret/",
"Success! Disabled the secrets engine (if it existed) at: secret/",
0,
},
}
@ -66,7 +66,7 @@ func TestUnmountCommand_Run(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testUnmountCommand(t)
ui, cmd := testSecretsDisableCommand(t)
cmd.client = client
code := cmd.Run(tc.args)
@ -88,23 +88,23 @@ func TestUnmountCommand_Run(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
if err := client.Sys().Mount("integration_unmount/", &api.MountInput{
if err := client.Sys().Mount("my-secret/", &api.MountInput{
Type: "generic",
}); err != nil {
t.Fatal(err)
}
ui, cmd := testUnmountCommand(t)
ui, cmd := testSecretsDisableCommand(t)
cmd.client = client
code := cmd.Run([]string{
"integration_unmount/",
"my-secret/",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Unmounted the secret backend (if it existed) at: integration_unmount/"
expected := "Success! Disabled the secrets engine (if it existed) at: my-secret/"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -126,7 +126,7 @@ func TestUnmountCommand_Run(t *testing.T) {
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testUnmountCommand(t)
ui, cmd := testSecretsDisableCommand(t)
cmd.client = client
code := cmd.Run([]string{
@ -136,7 +136,7 @@ func TestUnmountCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error unmounting pki/: "
expected := "Error disabling secrets engine at pki/: "
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -146,7 +146,7 @@ func TestUnmountCommand_Run(t *testing.T) {
t.Run("no_tabs", func(t *testing.T) {
t.Parallel()
_, cmd := testUnmountCommand(t)
_, cmd := testSecretsDisableCommand(t)
assertNoTabs(t, cmd)
})
}

View File

@ -10,12 +10,10 @@ import (
"github.com/posener/complete"
)
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*MountCommand)(nil)
var _ cli.CommandAutocomplete = (*MountCommand)(nil)
var _ cli.Command = (*SecretsEnableCommand)(nil)
var _ cli.CommandAutocomplete = (*SecretsEnableCommand)(nil)
// MountCommand is a Command that mounts a new mount.
type MountCommand struct {
type SecretsEnableCommand struct {
*BaseCommand
flagDescription string
@ -27,45 +25,45 @@ type MountCommand struct {
flagLocal bool
}
func (c *MountCommand) Synopsis() string {
return "Mounts a secret backend at a path"
func (c *SecretsEnableCommand) Synopsis() string {
return "Enable a secrets engine"
}
func (c *MountCommand) Help() string {
func (c *SecretsEnableCommand) Help() string {
helpText := `
Usage: vault mount [options] TYPE
Usage: vault secrets enable [options] TYPE
Mount a secret backend at a particular path. By default, secret backends are
mounted at the path corresponding to their "type", but users can customize
the mount point using the -path option.
Enables a secrets engine. By default, secrets engines are enabled at the path
corresponding to their TYPE, but users can customize the path using the
-path option.
Once mounted at a path, Vault will route all requests which begin with the
path to the secret backend.
Once enabled, Vault will route all requests which begin with the path to the
secrets engine.
Mount the AWS backend at aws/:
Enable the AWS secrets engine at aws/:
$ vault mount aws
$ vault secrets enable aws
Mount the SSH backend at ssh-prod/:
Enable the SSH secrets engine at ssh-prod/:
$ vault mount -path=ssh-prod ssh
$ vault secrets enable -path=ssh-prod ssh
Mount the database backend with an explicit maximum TTL of 30m:
Enable the database secrets engine with an explicit maximum TTL of 30m:
$ vault mount -max-lease-ttl=30m database
$ vault secrets enable -max-lease-ttl=30m database
Mount a custom plugin (after it is registered in the plugin registry):
Enable a custom plugin (after it is registered in the plugin registry):
$ vault mount -path=my-secrets -plugin-name=my-custom-plugin plugin
$ vault secrets enable -path=my-secrets -plugin-name=my-plugin plugin
For a full list of secret backends and examples, please see the documentation.
For a full list of secrets engines and examples, please see the documentation.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *MountCommand) Flags() *FlagSets {
func (c *SecretsEnableCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
f := set.NewFlagSet("Command Options")
@ -74,7 +72,7 @@ func (c *MountCommand) Flags() *FlagSets {
Name: "description",
Target: &c.flagDescription,
Completion: complete.PredictAnything,
Usage: "Human-friendly description for the purpose of this mount.",
Usage: "Human-friendly description for the purpose of this engine.",
})
f.StringVar(&StringVar{
@ -82,31 +80,34 @@ func (c *MountCommand) Flags() *FlagSets {
Target: &c.flagPath,
Default: "", // The default is complex, so we have to manually document
Completion: complete.PredictAnything,
Usage: "Place where the mount will be accessible. This must be " +
"unique across all mounts. This defaults to the \"type\" of the mount.",
Usage: "Place where the secrets engine will be accessible. This must be " +
"unique cross all secrets engines. This defaults to the \"type\" of the " +
"secrets engine.",
})
f.DurationVar(&DurationVar{
Name: "default-lease-ttl",
Target: &c.flagDefaultLeaseTTL,
Completion: complete.PredictAnything,
Usage: "The default lease TTL for this backend. If unspecified, this " +
"defaults to the Vault server's globally configured default lease TTL.",
Usage: "The default lease TTL for this secrets engine. If unspecified, " +
"this defaults to the Vault server's globally configured default lease " +
"TTL.",
})
f.DurationVar(&DurationVar{
Name: "max-lease-ttl",
Target: &c.flagMaxLeaseTTL,
Completion: complete.PredictAnything,
Usage: "The maximum lease TTL for this backend. If unspecified, this " +
"defaults to the Vault server's globally configured maximum lease TTL.",
Usage: "The maximum lease TTL for this secrets engine. If unspecified, " +
"this defaults to the Vault server's globally configured maximum lease " +
"TTL.",
})
f.BoolVar(&BoolVar{
Name: "force-no-cache",
Target: &c.flagForceNoCache,
Default: false,
Usage: "Force the backend to disable caching. If unspecified, this " +
Usage: "Force the secrets engine to disable caching. If unspecified, this " +
"defaults to the Vault server's globally configured cache settings. " +
"This does not affect caching of the underlying encrypted data storage.",
})
@ -115,30 +116,30 @@ func (c *MountCommand) Flags() *FlagSets {
Name: "plugin-name",
Target: &c.flagPluginName,
Completion: complete.PredictAnything,
Usage: "Name of the plugin to mount. This plugin name must already exist " +
"in the Vault server's plugin catalog.",
Usage: "Name of the secrets engine plugin. This plugin name must already " +
"exist in Vault's plugin catalog.",
})
f.BoolVar(&BoolVar{
Name: "local",
Target: &c.flagLocal,
Default: false,
Usage: "Mark the mount as a local-only mount. Local mounts are not " +
"replicated nor removed by replication.",
Usage: "Mark the secrets engine as local-only. Local engines are not " +
"replicated or removed by replication.",
})
return set
}
func (c *MountCommand) AutocompleteArgs() complete.Predictor {
func (c *SecretsEnableCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultAvailableMounts()
}
func (c *MountCommand) AutocompleteFlags() complete.Flags {
func (c *SecretsEnableCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *MountCommand) Run(args []string) int {
func (c *SecretsEnableCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
@ -147,13 +148,11 @@ func (c *MountCommand) Run(args []string) int {
}
args = f.Args()
switch len(args) {
case 0:
c.UI.Error("Missing TYPE!")
switch {
case len(args) < 1:
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
return 1
case 1:
// OK
default:
case len(args) > 1:
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
return 1
}
@ -164,17 +163,17 @@ func (c *MountCommand) Run(args []string) int {
return 2
}
// Get the mount type (first arg)
mountType := strings.TrimSpace(args[0])
// Get the engine type type (first arg)
engineType := strings.TrimSpace(args[0])
// If no path is specified, we default the path to the backend type
// or use the plugin name if it's a plugin backend
mountPath := c.flagPath
if mountPath == "" {
if mountType == "plugin" {
if engineType == "plugin" {
mountPath = c.flagPluginName
} else {
mountPath = mountType
mountPath = engineType
}
}
@ -183,7 +182,7 @@ func (c *MountCommand) Run(args []string) int {
// Build mount input
mountInput := &api.MountInput{
Type: mountType,
Type: engineType,
Description: c.flagDescription,
Local: c.flagLocal,
Config: api.MountConfigInput{
@ -195,15 +194,15 @@ func (c *MountCommand) Run(args []string) int {
}
if err := client.Sys().Mount(mountPath, mountInput); err != nil {
c.UI.Error(fmt.Sprintf("Error mounting: %s", err))
c.UI.Error(fmt.Sprintf("Error enabling: %s", err))
return 2
}
mountThing := mountType + " secret backend"
if mountType == "plugin" {
mountThing = c.flagPluginName + " plugin"
thing := engineType + " secrets engine"
if engineType == "plugin" {
thing = c.flagPluginName + " plugin"
}
c.UI.Output(fmt.Sprintf("Success! Mounted the %s at: %s", mountThing, mountPath))
c.UI.Output(fmt.Sprintf("Success! Enabled the %s at: %s", thing, mountPath))
return 0
}

View File

@ -7,17 +7,18 @@ import (
"github.com/mitchellh/cli"
)
func testMountCommand(tb testing.TB) (*cli.MockUi, *MountCommand) {
func testSecretsEnableCommand(tb testing.TB) (*cli.MockUi, *SecretsEnableCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &MountCommand{
return ui, &SecretsEnableCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestMountCommand_Run(t *testing.T) {
func TestSecretsEnableCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
@ -27,9 +28,9 @@ func TestMountCommand_Run(t *testing.T) {
code int
}{
{
"empty",
nil,
"Missing TYPE!",
"not_enough_args",
[]string{},
"Not enough arguments",
1,
},
{
@ -47,7 +48,7 @@ func TestMountCommand_Run(t *testing.T) {
{
"mount",
[]string{"transit"},
"Success! Mounted the transit secret backend at: transit/",
"Success! Enabled the transit secrets engine at: transit/",
0,
},
{
@ -56,7 +57,7 @@ func TestMountCommand_Run(t *testing.T) {
"-path", "transit_mount_point",
"transit",
},
"Success! Mounted the transit secret backend at: transit_mount_point/",
"Success! Enabled the transit secrets engine at: transit_mount_point/",
0,
},
}
@ -70,7 +71,7 @@ func TestMountCommand_Run(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testMountCommand(t)
ui, cmd := testSecretsEnableCommand(t)
cmd.client = client
code := cmd.Run(tc.args)
@ -91,7 +92,7 @@ func TestMountCommand_Run(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testMountCommand(t)
ui, cmd := testSecretsEnableCommand(t)
cmd.client = client
code := cmd.Run([]string{
@ -106,7 +107,7 @@ func TestMountCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Mounted the pki secret backend at: mount_integration/"
expected := "Success! Enabled the pki secrets engine at: mount_integration/"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -144,7 +145,7 @@ func TestMountCommand_Run(t *testing.T) {
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testMountCommand(t)
ui, cmd := testSecretsEnableCommand(t)
cmd.client = client
code := cmd.Run([]string{
@ -154,7 +155,7 @@ func TestMountCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error mounting: "
expected := "Error enabling: "
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -164,7 +165,7 @@ func TestMountCommand_Run(t *testing.T) {
t.Run("no_tabs", func(t *testing.T) {
t.Parallel()
_, cmd := testMountCommand(t)
_, cmd := testSecretsEnableCommand(t)
assertNoTabs(t, cmd)
})
}

View File

@ -11,44 +11,42 @@ import (
"github.com/posener/complete"
)
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*MountsCommand)(nil)
var _ cli.CommandAutocomplete = (*MountsCommand)(nil)
var _ cli.Command = (*SecretsListCommand)(nil)
var _ cli.CommandAutocomplete = (*SecretsListCommand)(nil)
// MountsCommand is a Command that lists the mounts.
type MountsCommand struct {
type SecretsListCommand struct {
*BaseCommand
flagDetailed bool
}
func (c *MountsCommand) Synopsis() string {
return "Lists mounted secret backends"
func (c *SecretsListCommand) Synopsis() string {
return "List enabled secrets engines"
}
func (c *MountsCommand) Help() string {
func (c *SecretsListCommand) Help() string {
helpText := `
Usage: vault mounts [options]
Usage: vault secrets list [options]
Lists the mounted secret backends on the Vault server. This command also
outputs information about the mount point including configured TTLs and
Lists the enabled secret engines on the Vault server. This command also
outputs information about the enabled path including configured TTLs and
human-friendly descriptions. A TTL of "system" indicates that the system
default is in use.
List all mounts:
List all enabled secrets engines:
$ vault mounts
$ vault secrets list
List all mounts with detailed output:
List all enabled secrets engines with detailed output:
$ vault mounts -detailed
$ vault secrets list -detailed
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *MountsCommand) Flags() *FlagSets {
func (c *SecretsListCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
f := set.NewFlagSet("Command Options")
@ -58,21 +56,21 @@ func (c *MountsCommand) Flags() *FlagSets {
Target: &c.flagDetailed,
Default: false,
Usage: "Print detailed information such as TTLs and replication status " +
"about each mount.",
"about each secrets engine.",
})
return set
}
func (c *MountsCommand) AutocompleteArgs() complete.Predictor {
func (c *SecretsListCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultFiles()
}
func (c *MountsCommand) AutocompleteFlags() complete.Flags {
func (c *SecretsListCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *MountsCommand) Run(args []string) int {
func (c *SecretsListCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
@ -94,20 +92,20 @@ func (c *MountsCommand) Run(args []string) int {
mounts, err := client.Sys().ListMounts()
if err != nil {
c.UI.Error(fmt.Sprintf("Error listing mounts: %s", err))
c.UI.Error(fmt.Sprintf("Error listing secrets engines: %s", err))
return 2
}
if c.flagDetailed {
c.UI.Output(tableOutput(c.detailedMounts(mounts)))
c.UI.Output(tableOutput(c.detailedMounts(mounts), nil))
return 0
}
c.UI.Output(tableOutput(c.simpleMounts(mounts)))
c.UI.Output(tableOutput(c.simpleMounts(mounts), nil))
return 0
}
func (c *MountsCommand) simpleMounts(mounts map[string]*api.MountOutput) []string {
func (c *SecretsListCommand) simpleMounts(mounts map[string]*api.MountOutput) []string {
paths := make([]string, 0, len(mounts))
for path := range mounts {
paths = append(paths, path)
@ -123,7 +121,7 @@ func (c *MountsCommand) simpleMounts(mounts map[string]*api.MountOutput) []strin
return out
}
func (c *MountsCommand) detailedMounts(mounts map[string]*api.MountOutput) []string {
func (c *SecretsListCommand) detailedMounts(mounts map[string]*api.MountOutput) []string {
paths := make([]string, 0, len(mounts))
for path := range mounts {
paths = append(paths, path)

View File

@ -7,18 +7,18 @@ import (
"github.com/mitchellh/cli"
)
func testMountsCommand(tb testing.TB) (*cli.MockUi, *MountsCommand) {
func testSecretsListCommand(tb testing.TB) (*cli.MockUi, *SecretsListCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &MountsCommand{
return ui, &SecretsListCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestMountsCommand_Run(t *testing.T) {
func TestSecretsListCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
@ -59,7 +59,7 @@ func TestMountsCommand_Run(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testMountsCommand(t)
ui, cmd := testSecretsListCommand(t)
cmd.client = client
code := cmd.Run(tc.args)
@ -81,7 +81,7 @@ func TestMountsCommand_Run(t *testing.T) {
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testMountsCommand(t)
ui, cmd := testSecretsListCommand(t)
cmd.client = client
code := cmd.Run([]string{})
@ -89,7 +89,7 @@ func TestMountsCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error listing mounts: "
expected := "Error listing secrets engines: "
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -99,7 +99,7 @@ func TestMountsCommand_Run(t *testing.T) {
t.Run("no_tabs", func(t *testing.T) {
t.Parallel()
_, cmd := testMountsCommand(t)
_, cmd := testSecretsListCommand(t)
assertNoTabs(t, cmd)
})
}

89
command/secrets_move.go Normal file
View File

@ -0,0 +1,89 @@
package command
import (
"fmt"
"strings"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var _ cli.Command = (*SecretsMoveCommand)(nil)
var _ cli.CommandAutocomplete = (*SecretsMoveCommand)(nil)
type SecretsMoveCommand struct {
*BaseCommand
}
func (c *SecretsMoveCommand) Synopsis() string {
return "Move a secrets engine to a new path"
}
func (c *SecretsMoveCommand) Help() string {
helpText := `
Usage: vault secrets move [options] SOURCE DESTINATION
Moves an existing secrets engine to a new path. Any leases from the old
secrets engine are revoked, but all configuration associated with the engine
is preserved.
WARNING! Moving an existing secrets engine will revoke any leases from the
old engine.
Move the existing secrets engine at secret/ to generic/:
$ vault secrets move secret/ generic/
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *SecretsMoveCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *SecretsMoveCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultMounts()
}
func (c *SecretsMoveCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *SecretsMoveCommand) 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) < 2:
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 2, got %d)", len(args)))
return 1
case len(args) > 2:
c.UI.Error(fmt.Sprintf("Too many arguments (expected 2, got %d)", len(args)))
return 1
}
// Grab the source and destination
source := ensureTrailingSlash(args[0])
destination := ensureTrailingSlash(args[1])
client, err := c.Client()
if err != nil {
c.UI.Error(err.Error())
return 2
}
if err := client.Sys().Remount(source, destination); err != nil {
c.UI.Error(fmt.Sprintf("Error moving secrets engine %s to %s: %s", source, destination, err))
return 2
}
c.UI.Output(fmt.Sprintf("Success! Moved secrets engine %s to: %s", source, destination))
return 0
}

View File

@ -7,18 +7,18 @@ import (
"github.com/mitchellh/cli"
)
func testRemountCommand(tb testing.TB) (*cli.MockUi, *RemountCommand) {
func testSecretsMoveCommand(tb testing.TB) (*cli.MockUi, *SecretsMoveCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &RemountCommand{
return ui, &SecretsMoveCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestRemountCommand_Run(t *testing.T) {
func TestSecretsMoveCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
@ -29,7 +29,7 @@ func TestRemountCommand_Run(t *testing.T) {
}{
{
"not_enough_args",
nil,
[]string{},
"Not enough arguments",
1,
},
@ -42,7 +42,7 @@ func TestRemountCommand_Run(t *testing.T) {
{
"non_existent",
[]string{"not_real", "over_here"},
"Error remounting not_real/ to over_here/",
"Error moving secrets engine not_real/ to over_here/",
2,
},
}
@ -56,7 +56,7 @@ func TestRemountCommand_Run(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ui, cmd := testRemountCommand(t)
ui, cmd := testSecretsMoveCommand(t)
code := cmd.Run(tc.args)
if code != tc.code {
@ -77,7 +77,7 @@ func TestRemountCommand_Run(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testRemountCommand(t)
ui, cmd := testSecretsMoveCommand(t)
cmd.client = client
code := cmd.Run([]string{
@ -87,7 +87,7 @@ func TestRemountCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Remounted secret/ to: generic/"
expected := "Success! Moved secrets engine secret/ to: generic/"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -109,7 +109,7 @@ func TestRemountCommand_Run(t *testing.T) {
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testRemountCommand(t)
ui, cmd := testSecretsMoveCommand(t)
cmd.client = client
code := cmd.Run([]string{
@ -119,7 +119,7 @@ func TestRemountCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error remounting secret/ to generic/: "
expected := "Error moving secrets engine secret/ to generic/:"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -129,7 +129,7 @@ func TestRemountCommand_Run(t *testing.T) {
t.Run("no_tabs", func(t *testing.T) {
t.Parallel()
_, cmd := testRemountCommand(t)
_, cmd := testSecretsMoveCommand(t)
assertNoTabs(t, cmd)
})
}

119
command/secrets_tune.go Normal file
View File

@ -0,0 +1,119 @@
package command
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/api"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var _ cli.Command = (*SecretsTuneCommand)(nil)
var _ cli.CommandAutocomplete = (*SecretsTuneCommand)(nil)
type SecretsTuneCommand struct {
*BaseCommand
flagDefaultLeaseTTL time.Duration
flagMaxLeaseTTL time.Duration
}
func (c *SecretsTuneCommand) Synopsis() string {
return "Tune a secrets engine configuration"
}
func (c *SecretsTuneCommand) Help() string {
helpText := `
Usage: vault secrets tune [options] PATH
Tunes the configuration options for the secrets engine at the given PATH.
The argument corresponds to the PATH where the secrets engine is enabled,
not the TYPE!
Tune the default lease for the PKI secrets engine:
$ vault secrets tune -default-lease-ttl=72h pki/
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *SecretsTuneCommand) 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 secrets engine. If unspecified, " +
"this defaults to the Vault server's globally configured default lease " +
"TTL, or a previously configured value for the secrets engine.",
})
f.DurationVar(&DurationVar{
Name: "max-lease-ttl",
Target: &c.flagMaxLeaseTTL,
Default: 0,
EnvVar: "",
Completion: complete.PredictAnything,
Usage: "The maximum lease TTL for this secrets engine. If unspecified, " +
"this defaults to the Vault server's globally configured maximum lease " +
"TTL, or a previously configured value for the secrets engine.",
})
return set
}
func (c *SecretsTuneCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultMounts()
}
func (c *SecretsTuneCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *SecretsTuneCommand) 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 a trailing slash to indicate it's a path in output
mountPath := ensureTrailingSlash(sanitizePath(args[0]))
if err := client.Sys().TuneMount(mountPath, api.MountConfigInput{
DefaultLeaseTTL: ttlToAPI(c.flagDefaultLeaseTTL),
MaxLeaseTTL: ttlToAPI(c.flagMaxLeaseTTL),
}); err != nil {
c.UI.Error(fmt.Sprintf("Error tuning secrets engine %s: %s", mountPath, err))
return 2
}
c.UI.Output(fmt.Sprintf("Success! Tuned the secrets engine at: %s", mountPath))
return 0
}

View File

@ -8,18 +8,18 @@ import (
"github.com/mitchellh/cli"
)
func testMountTuneCommand(tb testing.TB) (*cli.MockUi, *MountTuneCommand) {
func testSecretsTuneCommand(tb testing.TB) (*cli.MockUi, *SecretsTuneCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &MountTuneCommand{
return ui, &SecretsTuneCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestMountTuneCommand_Run(t *testing.T) {
func TestSecretsTuneCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
@ -29,15 +29,9 @@ func TestMountTuneCommand_Run(t *testing.T) {
code int
}{
{
"empty",
nil,
"Missing PATH!",
1,
},
{
"slash",
[]string{"/"},
"Missing PATH!",
"not_enough_args",
[]string{},
"Not enough arguments",
1,
},
{
@ -57,7 +51,7 @@ func TestMountTuneCommand_Run(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ui, cmd := testMountTuneCommand(t)
ui, cmd := testSecretsTuneCommand(t)
code := cmd.Run(tc.args)
if code != tc.code {
@ -78,7 +72,7 @@ func TestMountTuneCommand_Run(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testMountTuneCommand(t)
ui, cmd := testSecretsTuneCommand(t)
cmd.client = client
// Mount
@ -97,7 +91,7 @@ func TestMountTuneCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Tuned the mount at: mount_tune_integration/"
expected := "Success! Tuned the secrets engine at: mount_tune_integration/"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -129,7 +123,7 @@ func TestMountTuneCommand_Run(t *testing.T) {
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testMountTuneCommand(t)
ui, cmd := testSecretsTuneCommand(t)
cmd.client = client
code := cmd.Run([]string{
@ -139,7 +133,7 @@ func TestMountTuneCommand_Run(t *testing.T) {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error tuning mount pki/: "
expected := "Error tuning secrets engine pki/: "
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
@ -149,7 +143,7 @@ func TestMountTuneCommand_Run(t *testing.T) {
t.Run("no_tabs", func(t *testing.T) {
t.Parallel()
_, cmd := testMountTuneCommand(t)
_, cmd := testSecretsTuneCommand(t)
assertNoTabs(t, cmd)
})
}

View File

@ -1,94 +0,0 @@
package command
import (
"fmt"
"strings"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*UnmountCommand)(nil)
var _ cli.CommandAutocomplete = (*UnmountCommand)(nil)
// UnmountCommand is a Command that mounts a new mount.
type UnmountCommand struct {
*BaseCommand
}
func (c *UnmountCommand) Synopsis() string {
return "Unmounts a secret backend"
}
func (c *UnmountCommand) Help() string {
helpText := `
Usage: vault unmount [options] PATH
Unmounts a secret backend at the given PATH. The argument corresponds to
the PATH of the mount, not the TYPE! All secrets created by this backend
are revoked and its Vault data is removed.
If no mount exists at the given path, the command will still return as
successful because unmounting is an idempotent operation.
Unmount the secret backend mounted at aws/:
$ vault unmount aws/
For a full list of examples, please see the documentation.
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *UnmountCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *UnmountCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultMounts()
}
func (c *UnmountCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *UnmountCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
mountPath, remaining, err := extractPath(args)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if len(remaining) > 0 {
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 a trailing slash to indicate it's a path in output
mountPath = ensureTrailingSlash(mountPath)
if err := client.Sys().Unmount(mountPath); err != nil {
c.UI.Error(fmt.Sprintf("Error unmounting %s: %s", mountPath, err))
return 2
}
c.UI.Output(fmt.Sprintf("Success! Unmounted the secret backend (if it existed) at: %s", mountPath))
return 0
}