diff --git a/command/seal.go b/command/seal.go index 033c164587..97f102a58e 100644 --- a/command/seal.go +++ b/command/seal.go @@ -4,35 +4,17 @@ 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 = (*SealCommand)(nil) +var _ cli.CommandAutocomplete = (*SealCommand)(nil) + // SealCommand is a Command that seals the vault. type SealCommand struct { - meta.Meta -} - -func (c *SealCommand) Run(args []string) int { - flags := c.Meta.FlagSet("seal", meta.FlagSetDefault) - flags.Usage = func() { c.Ui.Error(c.Help()) } - if err := flags.Parse(args); err != nil { - return 1 - } - - client, err := c.Client() - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error initializing client: %s", err)) - return 2 - } - - if err := client.Sys().Seal(); err != nil { - c.Ui.Error(fmt.Sprintf("Error sealing: %s", err)) - return 1 - } - - c.Ui.Output("Vault is now sealed.") - return 0 + *BaseCommand } func (c *SealCommand) Synopsis() string { @@ -43,21 +25,65 @@ func (c *SealCommand) Help() string { helpText := ` Usage: vault seal [options] - Seal the vault. + Seals the Vault server. Sealing tells the Vault server to stop responding + to any operations until it is unsealed. When sealed, the Vault server + discards its in-memory master key to unlock the data, so it is physically + blocked from responding to operations unsealed. - Sealing a vault tells the Vault server to stop responding to any - access operations until it is unsealed again. A sealed vault throws away - its master key to unlock the data, so it is physically blocked from - responding to operations again until the vault is unsealed with - the "unseal" command or via the API. + If an unseal is in progress, sealing the Vault will reset the unsealing + process. Users will have to re-enter their portions of the master key again. - This command is idempotent, if the vault is already sealed it does nothing. + This command does nothing if the Vault server is already sealed. - If an unseal has started, sealing the vault will reset the unsealing - process. You'll have to re-enter every portion of the master key again. - This is the same as running "vault unseal -reset". + Seal the Vault server: + + $ vault seal + + For a full list of examples and why you might want to seal the Vault, please + see the documentation. + +` + c.Flags().Help() -General Options: -` + meta.GeneralOptionsUsage() return strings.TrimSpace(helpText) } + +func (c *SealCommand) Flags() *FlagSets { + return c.flagSet(FlagSetHTTP) +} + +func (c *SealCommand) AutocompleteArgs() complete.Predictor { + return nil +} + +func (c *SealCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *SealCommand) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + args = f.Args() + if len(args) > 0 { + c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", len(args))) + return 1 + } + + client, err := c.Client() + if err != nil { + c.UI.Error(err.Error()) + return 2 + } + + if err := client.Sys().Seal(); err != nil { + c.UI.Error(fmt.Sprintf("Error sealing: %s", err)) + return 2 + } + + c.UI.Output("Success! Vault is sealed.") + return 0 +} diff --git a/command/seal_test.go b/command/seal_test.go index c224aee31e..a0cf373325 100644 --- a/command/seal_test.go +++ b/command/seal_test.go @@ -1,37 +1,122 @@ 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 Test_Seal(t *testing.T) { - core, _, token := vault.TestCoreUnsealed(t) - ln, addr := http.TestServer(t, core) - defer ln.Close() +func testSealCommand(tb testing.TB) (*cli.MockUi, *SealCommand) { + tb.Helper() - ui := new(cli.MockUi) - c := &SealCommand{ - Meta: meta.Meta{ - ClientToken: token, - Ui: ui, + ui := cli.NewMockUi() + return ui, &SealCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + +func TestSealCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "args", + []string{"foo"}, + "Too many arguments", + 1, }, } - args := []string{"-address", addr} - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } + t.Run("validations", func(t *testing.T) { + t.Parallel() - sealed, err := core.Sealed() - if err != nil { - t.Fatalf("err: %s", err) - } - if !sealed { - t.Fatal("should be sealed") - } + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testSealCommand(t) + cmd.client = client + + 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("integration", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testSealCommand(t) + cmd.client = client + + code := cmd.Run([]string{}) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Success! Vault is sealed." + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + + sealStatus, err := client.Sys().SealStatus() + if err != nil { + t.Fatal(err) + } + if !sealStatus.Sealed { + t.Errorf("expected to be sealed") + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testSealCommand(t) + cmd.client = client + + code := cmd.Run([]string{}) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error sealing: " + 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 := testSealCommand(t) + assertNoTabs(t, cmd) + }) }