update unseal command

This commit is contained in:
Seth Vargo 2017-09-05 00:05:27 -04:00
parent 2f0eb0a216
commit 0c0d90913a
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
2 changed files with 251 additions and 151 deletions

View File

@ -2,98 +2,27 @@ package command
import (
"fmt"
"io"
"os"
"strings"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/password"
"github.com/hashicorp/vault/meta"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*UnsealCommand)(nil)
var _ cli.CommandAutocomplete = (*UnsealCommand)(nil)
// UnsealCommand is a Command that unseals the vault.
type UnsealCommand struct {
meta.Meta
*BaseCommand
// Key can be used to pre-seed the key. If it is set, it will not
// be asked with the `password` helper.
Key string
}
flagReset bool
func (c *UnsealCommand) Run(args []string) int {
var reset bool
flags := c.Meta.FlagSet("unseal", meta.FlagSetDefault)
flags.BoolVar(&reset, "reset", false, "")
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
}
sealStatus, err := client.Sys().SealStatus()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error checking seal status: %s", err))
return 2
}
if !sealStatus.Sealed {
c.Ui.Output("Vault is already unsealed.")
return 0
}
args = flags.Args()
if reset {
sealStatus, err = client.Sys().ResetUnsealProcess()
} else {
value := c.Key
if len(args) > 0 {
value = args[0]
}
if value == "" {
fmt.Printf("Key (will be hidden): ")
value, err = password.Read(os.Stdin)
fmt.Printf("\n")
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error attempting to ask for password. The raw error message\n"+
"is shown below, but the most common reason for this error is\n"+
"that you attempted to pipe a value into unseal or you're\n"+
"executing `vault unseal` from outside of a terminal.\n\n"+
"You should use `vault unseal` from a terminal for maximum\n"+
"security. If this isn't an option, the unseal key can be passed\n"+
"in using the first parameter.\n\n"+
"Raw error: %s", err))
return 1
}
}
sealStatus, err = client.Sys().Unseal(strings.TrimSpace(value))
}
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error: %s", err))
return 1
}
c.Ui.Output(fmt.Sprintf(
"Sealed: %v\n"+
"Key Shares: %d\n"+
"Key Threshold: %d\n"+
"Unseal Progress: %d\n"+
"Unseal Nonce: %v",
sealStatus.Sealed,
sealStatus.N,
sealStatus.T,
sealStatus.Progress,
sealStatus.Nonce,
))
return 0
testOutput io.Writer // for tests
}
func (c *UnsealCommand) Synopsis() string {
@ -102,27 +31,132 @@ func (c *UnsealCommand) Synopsis() string {
func (c *UnsealCommand) Help() string {
helpText := `
Usage: vault unseal [options] [key]
Usage: vault unseal [options] [KEY]
Unseal the vault by entering a portion of the master key. Once all
portions are entered, the vault will be unsealed.
Provide a portion of the master key to unseal a Vault server. Vault starts
in a sealed state. It cannot perform operations until it is unsealed. This
command accepts a portion of the master key (an "unseal key").
Every Vault server initially starts as sealed. It cannot perform any
operation except unsealing until it is sealed. Secrets cannot be accessed
in any way until the vault is unsealed. This command allows you to enter
a portion of the master key to unseal the vault.
The unseal key can be supplied as an argument to the command, but this is
not recommended as the unseal key will be available in your history:
The unseal key can be specified via the command line, but this is
not recommended. The key may then live in your terminal history. This
only exists to assist in scripting.
$ vault unseal IXyR0OJnSFobekZMMCKCoVEpT7wI6l+USMzE3IcyDyo=
General Options:
` + meta.GeneralOptionsUsage() + `
Unseal Options:
Instead, run the command with no arguments and it will prompt for the key:
-reset Reset the unsealing process by throwing away
prior keys in process to unseal the vault.
$ vault unseal
Key (will be hidden): IXyR0OJnSFobekZMMCKCoVEpT7wI6l+USMzE3IcyDyo=
For a full list of examples, please see the documentation.
` + c.Flags().Help()
`
return strings.TrimSpace(helpText)
}
func (c *UnsealCommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP)
f := set.NewFlagSet("Command Options")
f.BoolVar(&BoolVar{
Name: "reset",
Aliases: []string{},
Target: &c.flagReset,
Default: false,
EnvVar: "",
Completion: complete.PredictNothing,
Usage: "Discard any previously entered keys to the unseal process.",
})
return set
}
func (c *UnsealCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultFiles()
}
func (c *UnsealCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *UnsealCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
unsealKey := ""
args = f.Args()
switch len(args) {
case 0:
// We will prompt for the unsealKey later
case 1:
unsealKey = strings.TrimSpace(args[0])
default:
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
}
if c.flagReset {
status, err := client.Sys().ResetUnsealProcess()
if err != nil {
c.UI.Error(fmt.Sprintf("Error resetting unseal process: %s", err))
return 2
}
c.prettySealStatus(status)
return 0
}
if unsealKey == "" {
// Override the output
writer := (io.Writer)(os.Stdout)
if c.testOutput != nil {
writer = c.testOutput
}
fmt.Fprintf(writer, "Key (will be hidden): ")
value, err := password.Read(os.Stdin)
fmt.Fprintf(writer, "\n")
if err != nil {
c.UI.Error(wrapAtLength(fmt.Sprintf("An error occurred attempting to "+
"ask for an unseal key. The raw error message is shown below, but "+
"usually this is because you attempted to pipe a value into the "+
"unseal command or you are executing outside of a terminal (tty). "+
"You should run the unseal command from a terminal for maximum "+
"security. If this is not an option, the unseal can be provided as "+
"the first argument to the unseal command. The raw error "+
"was:\n\n%s", err)))
return 1
}
unsealKey = strings.TrimSpace(value)
}
status, err := client.Sys().Unseal(unsealKey)
if err != nil {
c.UI.Error(fmt.Sprintf("Error unsealing: %s", err))
return 2
}
c.prettySealStatus(status)
return 0
}
func (c *UnsealCommand) prettySealStatus(status *api.SealStatusResponse) {
c.UI.Output(fmt.Sprintf("Sealed: %t", status.Sealed))
c.UI.Output(fmt.Sprintf("Key Shares: %d", status.N))
c.UI.Output(fmt.Sprintf("Key Threshold: %d", status.T))
c.UI.Output(fmt.Sprintf("Unseal Progress: %d", status.Progress))
if status.Nonce != "" {
c.UI.Output(fmt.Sprintf("Unseal Nonce: %s", status.Nonce))
}
}

View File

@ -1,72 +1,138 @@
package command
import (
"encoding/hex"
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/meta"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli"
)
func TestUnseal(t *testing.T) {
core := vault.TestCore(t)
keys, _ := vault.TestCoreInit(t, core)
ln, addr := http.TestServer(t, core)
defer ln.Close()
func testUnsealCommand(tb testing.TB) (*cli.MockUi, *UnsealCommand) {
tb.Helper()
ui := new(cli.MockUi)
for _, key := range keys {
c := &UnsealCommand{
Key: hex.EncodeToString(key),
Meta: meta.Meta{
Ui: ui,
},
}
args := []string{"-address", addr}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
sealed, err := core.Sealed()
if err != nil {
t.Fatalf("err: %s", err)
}
if sealed {
t.Fatal("should not be sealed")
ui := cli.NewMockUi()
return ui, &UnsealCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestUnseal_arg(t *testing.T) {
core := vault.TestCore(t)
keys, _ := vault.TestCoreInit(t, core)
ln, addr := http.TestServer(t, core)
defer ln.Close()
func TestUnsealCommand_Run(t *testing.T) {
t.Parallel()
ui := new(cli.MockUi)
t.Run("error_non_terminal", func(t *testing.T) {
t.Parallel()
for _, key := range keys {
c := &UnsealCommand{
Meta: meta.Meta{
Ui: ui,
},
ui, cmd := testUnsealCommand(t)
cmd.testOutput = ioutil.Discard
code := cmd.Run(nil)
if exp := 1; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
args := []string{"-address", addr, hex.EncodeToString(key)}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
expected := "is not a terminal"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
}
})
sealed, err := core.Sealed()
if err != nil {
t.Fatalf("err: %s", err)
}
if sealed {
t.Fatal("should not be sealed")
}
t.Run("reset", func(t *testing.T) {
t.Parallel()
client, keys, closer := testVaultServerUnseal(t)
defer closer()
// Seal so we can unseal
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Enter an unseal key
if _, err := client.Sys().Unseal(keys[0]); err != nil {
t.Fatal(err)
}
ui, cmd := testUnsealCommand(t)
cmd.client = client
cmd.testOutput = ioutil.Discard
// Reset and check output
code := cmd.Run([]string{
"-reset",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Unseal Progress: 0"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
})
t.Run("full", func(t *testing.T) {
t.Parallel()
client, keys, closer := testVaultServerUnseal(t)
defer closer()
// Seal so we can unseal
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
for i, key := range keys {
ui, cmd := testUnsealCommand(t)
cmd.client = client
cmd.testOutput = ioutil.Discard
// Reset and check output
code := cmd.Run([]string{
key,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := fmt.Sprintf("Unseal Progress: %d", (i+1)%3) // 1, 2, 0
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
}
})
t.Run("communication_failure", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testUnsealCommand(t)
cmd.client = client
code := cmd.Run([]string{
"abcd",
})
if exp := 2; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error unsealing: "
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 := testUnsealCommand(t)
assertNoTabs(t, cmd)
})
}