diff --git a/command/mount_tune.go b/command/mount_tune.go deleted file mode 100644 index 962ebb5f5f..0000000000 --- a/command/mount_tune.go +++ /dev/null @@ -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 -} diff --git a/command/secrets.go b/command/secrets.go new file mode 100644 index 0000000000..06e63bec28 --- /dev/null +++ b/command/secrets.go @@ -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 [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 +} diff --git a/command/secrets_disable.go b/command/secrets_disable.go new file mode 100644 index 0000000000..7002874409 --- /dev/null +++ b/command/secrets_disable.go @@ -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 +} diff --git a/command/unmount_test.go b/command/secrets_disable_test.go similarity index 72% rename from command/unmount_test.go rename to command/secrets_disable_test.go index 47057458f5..567c8956d6 100644 --- a/command/unmount_test.go +++ b/command/secrets_disable_test.go @@ -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) }) } diff --git a/command/mount.go b/command/secrets_enable.go similarity index 51% rename from command/mount.go rename to command/secrets_enable.go index c89115d782..8e5f178fb0 100644 --- a/command/mount.go +++ b/command/secrets_enable.go @@ -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 } diff --git a/command/mount_test.go b/command/secrets_enable_test.go similarity index 83% rename from command/mount_test.go rename to command/secrets_enable_test.go index dd7659127d..e241edfa65 100644 --- a/command/mount_test.go +++ b/command/secrets_enable_test.go @@ -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) }) } diff --git a/command/mounts.go b/command/secrets_list.go similarity index 66% rename from command/mounts.go rename to command/secrets_list.go index b25bfe4dcb..9bff70ff29 100644 --- a/command/mounts.go +++ b/command/secrets_list.go @@ -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) diff --git a/command/mounts_test.go b/command/secrets_list_test.go similarity index 82% rename from command/mounts_test.go rename to command/secrets_list_test.go index 8f1c8b9c85..9edb628202 100644 --- a/command/mounts_test.go +++ b/command/secrets_list_test.go @@ -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) }) } diff --git a/command/secrets_move.go b/command/secrets_move.go new file mode 100644 index 0000000000..542b14d3d2 --- /dev/null +++ b/command/secrets_move.go @@ -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 +} diff --git a/command/remount_test.go b/command/secrets_move_test.go similarity index 80% rename from command/remount_test.go rename to command/secrets_move_test.go index 6a6815e520..0936a7dd30 100644 --- a/command/remount_test.go +++ b/command/secrets_move_test.go @@ -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) }) } diff --git a/command/secrets_tune.go b/command/secrets_tune.go new file mode 100644 index 0000000000..b2029b7507 --- /dev/null +++ b/command/secrets_tune.go @@ -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 +} diff --git a/command/mount_tune_test.go b/command/secrets_tune_test.go similarity index 83% rename from command/mount_tune_test.go rename to command/secrets_tune_test.go index b60c532f72..11d90263d3 100644 --- a/command/mount_tune_test.go +++ b/command/secrets_tune_test.go @@ -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) }) } diff --git a/command/unmount.go b/command/unmount.go deleted file mode 100644 index cb3bc6266a..0000000000 --- a/command/unmount.go +++ /dev/null @@ -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 -}