open-vault/command/operator_generate_root_test.go
ncabatoff 1c98152fa0
Shamir seals now come in two varieties: legacy and new-style. (#7694)
Shamir seals now come in two varieties: legacy and new-style. Legacy
Shamir is automatically converted to new-style when a rekey operation
is performed. All new Vault initializations using Shamir are new-style.

New-style Shamir writes an encrypted master key to storage, just like
AutoUnseal. The stored master key is encrypted using the shared key that
is split via Shamir's algorithm. Thus when unsealing, we take the key
fragments given, combine them into a Key-Encryption-Key, and use that
to decrypt the master key on disk. Then the master key is used to read
the keyring that decrypts the barrier.
2019-10-18 14:46:00 -04:00

469 lines
9.9 KiB
Go

// +build !race
package command
import (
"encoding/base64"
"io"
"os"
"regexp"
"strings"
"testing"
"github.com/hashicorp/vault/helper/xor"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli"
)
func testOperatorGenerateRootCommand(tb testing.TB) (*cli.MockUi, *OperatorGenerateRootCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &OperatorGenerateRootCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
out string
code int
}{
{
"init_invalid_otp",
[]string{
"-init",
"-otp", "not-a-valid-otp",
},
"OTP string is wrong length",
2,
},
{
"init_pgp_multi",
[]string{
"-init",
"-pgp-key", "keybase:hashicorp",
"-pgp-key", "keybase:jefferai",
},
"can only be specified once",
1,
},
{
"init_pgp_multi_inline",
[]string{
"-init",
"-pgp-key", "keybase:hashicorp,keybase:jefferai",
},
"can only specify one pgp key",
1,
},
{
"init_pgp_otp",
[]string{
"-init",
"-pgp-key", "keybase:hashicorp",
"-otp", "abcd1234",
},
"cannot specify both -otp and -pgp-key",
1,
},
}
t.Run("validations", func(t *testing.T) {
t.Parallel()
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run(tc.args)
if code != tc.code {
t.Errorf("%s: expected %d to be %d", tc.name, code, tc.code)
}
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, tc.out) {
t.Errorf("%s: expected %q to contain %q", tc.name, combined, tc.out)
}
})
}
})
t.Run("generate_otp", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
_, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-generate-otp",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
})
t.Run("decode", func(t *testing.T) {
t.Parallel()
encoded := "Bxg9JQQqOCNKBRICNwMIRzo2J3cWCBRi"
otp := "3JhHkONiyiaNYj14nnD9xZQS"
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
// Simulate piped output to print raw output
old := os.Stdout
_, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
os.Stdout = w
code := cmd.Run([]string{
"-decode", encoded,
"-otp", otp,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
w.Close()
os.Stdout = old
expected := "4RUmoevJ3lsLni9sTXcNnRE1"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if combined != expected {
t.Errorf("expected %q to be %q", combined, expected)
}
})
t.Run("cancel", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
// Initialize a generation
if _, err := client.Sys().GenerateRootInit("", ""); err != nil {
t.Fatal(err)
}
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-cancel",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Root token generation canceled"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
status, err := client.Sys().GenerateRootStatus()
if err != nil {
t.Fatal(err)
}
if status.Started {
t.Errorf("expected status to be canceled: %#v", status)
}
})
t.Run("init_otp", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-init",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Nonce"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
status, err := client.Sys().GenerateRootStatus()
if err != nil {
t.Fatal(err)
}
if !status.Started {
t.Errorf("expected status to be started: %#v", status)
}
})
t.Run("init_pgp", func(t *testing.T) {
t.Parallel()
pgpKey := "keybase:hashicorp"
pgpFingerprint := "91a6e7f85d05c65630bef18951852d87348ffc4c"
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-init",
"-pgp-key", pgpKey,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Nonce"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
status, err := client.Sys().GenerateRootStatus()
if err != nil {
t.Fatal(err)
}
if !status.Started {
t.Errorf("expected status to be started: %#v", status)
}
if status.PGPFingerprint != pgpFingerprint {
t.Errorf("expected %q to be %q", status.PGPFingerprint, pgpFingerprint)
}
})
t.Run("status", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-status",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Nonce"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
})
t.Run("provide_arg", func(t *testing.T) {
t.Parallel()
client, keys, closer := testVaultServerUnseal(t)
defer closer()
// Initialize a generation
status, err := client.Sys().GenerateRootInit("", "")
if err != nil {
t.Fatal(err)
}
nonce := status.Nonce
otp := status.OTP
// Supply the first n-1 unseal keys
for _, key := range keys[:len(keys)-1] {
_, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-nonce", nonce,
key,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
}
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-nonce", nonce,
keys[len(keys)-1], // the last unseal key
})
if exp := 0; code != exp {
t.Fatalf("expected %d to be %d, out=%q, err=%q", code, exp, ui.OutputWriter, ui.ErrorWriter)
}
reToken := regexp.MustCompile(`Encoded Token\s+(.+)`)
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
match := reToken.FindAllStringSubmatch(combined, -1)
if len(match) < 1 || len(match[0]) < 2 {
t.Fatalf("no match: %#v", match)
}
tokenBytes, err := base64.RawStdEncoding.DecodeString(match[0][1])
if err != nil {
t.Fatal(err)
}
token, err := xor.XORBytes(tokenBytes, []byte(otp))
if err != nil {
t.Fatal(err)
}
if l, exp := len(token), vault.TokenLength+2; l != exp {
t.Errorf("expected %d to be %d: %s", l, exp, token)
}
})
t.Run("provide_stdin", func(t *testing.T) {
t.Parallel()
client, keys, closer := testVaultServerUnseal(t)
defer closer()
// Initialize a generation
status, err := client.Sys().GenerateRootInit("", "")
if err != nil {
t.Fatal(err)
}
nonce := status.Nonce
otp := status.OTP
// Supply the first n-1 unseal keys
for _, key := range keys[:len(keys)-1] {
stdinR, stdinW := io.Pipe()
go func() {
stdinW.Write([]byte(key))
stdinW.Close()
}()
_, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
cmd.testStdin = stdinR
code := cmd.Run([]string{
"-nonce", nonce,
"-",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
}
stdinR, stdinW := io.Pipe()
go func() {
stdinW.Write([]byte(keys[len(keys)-1])) // the last unseal key
stdinW.Close()
}()
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
cmd.testStdin = stdinR
code := cmd.Run([]string{
"-nonce", nonce,
"-",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
reToken := regexp.MustCompile(`Encoded Token\s+(.+)`)
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
match := reToken.FindAllStringSubmatch(combined, -1)
if len(match) < 1 || len(match[0]) < 2 {
t.Fatalf("no match: %#v", match)
}
// encodedOTP := base64.RawStdEncoding.EncodeToString([]byte(otp))
// tokenBytes, err := xor.XORBase64(match[0][1], encodedOTP)
// if err != nil {
// t.Fatal(err)
// }
// token, err := uuid.FormatUUID(tokenBytes)
// if err != nil {
// t.Fatal(err)
// }
tokenBytes, err := base64.RawStdEncoding.DecodeString(match[0][1])
if err != nil {
t.Fatal(err)
}
token, err := xor.XORBytes(tokenBytes, []byte(otp))
if err != nil {
t.Fatal(err)
}
if l, exp := len(token), vault.TokenLength+2; l != exp {
t.Errorf("expected %d to be %d: %s", l, exp, token)
}
})
t.Run("communication_failure", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testOperatorGenerateRootCommand(t)
cmd.client = client
code := cmd.Run([]string{
"secret/foo",
})
if exp := 2; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error getting root generation status: "
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 := testOperatorGenerateRootCommand(t)
assertNoTabs(t, cmd)
})
}