open-vault/vault/rekey_test.go
Jeff Mitchell 9687ccc8fa Tackle #4929 a different way (#4932)
* Tackle #4929 a different way

This turns c.sealed into an atomic, which allows us to call sealInternal
without a lock. By doing so we can better control lock grabbing when a
condition causing the standby loop to get out of active happens. This
encapsulates that logic into two distinct pieces (although they could
be combined into one), and makes lock guarding more understandable.

* Re-add context canceling to the non-HA version of sealInternal

* Return explicitly after stopCh triggered
2018-07-24 13:57:25 -07:00

481 lines
11 KiB
Go

package vault
import (
"context"
"fmt"
"reflect"
"testing"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/helper/logging"
"github.com/hashicorp/vault/physical"
"github.com/hashicorp/vault/physical/inmem"
)
func TestCore_Rekey_Lifecycle(t *testing.T) {
bc, _ := TestSealDefConfigs()
bc.SecretShares = 1
bc.SecretThreshold = 1
bc.StoredShares = 0
c, masterKeys, _, _ := TestCoreUnsealedWithConfigs(t, bc, nil)
if len(masterKeys) != 1 {
t.Fatalf("expected %d keys, got %d", bc.SecretShares-bc.StoredShares, len(masterKeys))
}
testCore_Rekey_Lifecycle_Common(t, c, masterKeys, false)
bc, _ = TestSealDefConfigs()
bc.SecretShares = 3
bc.SecretThreshold = 3
c, masterKeys, _, _ = TestCoreUnsealedWithConfigs(t, bc, nil)
if len(masterKeys) != 3 {
t.Fatalf("expected %d keys, got %d", bc.SecretShares-bc.StoredShares, len(masterKeys))
}
testCore_Rekey_Lifecycle_Common(t, c, masterKeys, false)
}
func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, masterKeys [][]byte, recovery bool) {
// Verify update not allowed
if _, err := c.RekeyUpdate(context.Background(), masterKeys[0], "", recovery); err == nil {
t.Fatalf("no rekey should be in progress")
}
// Should be no progress
if _, _, err := c.RekeyProgress(recovery, false); err == nil {
t.Fatal("expected error from RekeyProgress")
}
// Should be no config
conf, err := c.RekeyConfig(recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
if conf != nil {
t.Fatalf("bad: %v", conf)
}
// Cancel should be idempotent
err = c.RekeyCancel(false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Start a rekey
newConf := &SealConfig{
SecretThreshold: 3,
SecretShares: 5,
}
err = c.RekeyInit(newConf, recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should get config
conf, err = c.RekeyConfig(recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
newConf.Nonce = conf.Nonce
if !reflect.DeepEqual(conf, newConf) {
t.Fatalf("bad: %v", conf)
}
// Cancel should be clear
err = c.RekeyCancel(recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should be no config
conf, err = c.RekeyConfig(recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
if conf != nil {
t.Fatalf("bad: %v", conf)
}
}
func TestCore_Rekey_Init(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
testCore_Rekey_Init_Common(t, c, false)
}
func testCore_Rekey_Init_Common(t *testing.T, c *Core, recovery bool) {
// Try an invalid config
badConf := &SealConfig{
SecretThreshold: 5,
SecretShares: 1,
}
err := c.RekeyInit(badConf, recovery)
if err == nil {
t.Fatalf("should fail")
}
// Start a rekey
newConf := &SealConfig{
SecretThreshold: 3,
SecretShares: 5,
}
err = c.RekeyInit(newConf, recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
// Second should fail
err = c.RekeyInit(newConf, recovery)
if err == nil {
t.Fatalf("should fail")
}
}
func TestCore_Rekey_Update(t *testing.T) {
bc, _ := TestSealDefConfigs()
bc.SecretShares = 1
bc.SecretThreshold = 1
c, masterKeys, _, root := TestCoreUnsealedWithConfigs(t, bc, nil)
testCore_Rekey_Update_Common(t, c, masterKeys, root, false)
}
func testCore_Rekey_Update_Common(t *testing.T, c *Core, keys [][]byte, root string, recovery bool) {
var err error
// Start a rekey
var expType string
if recovery {
expType = c.seal.RecoveryType()
} else {
expType = c.seal.BarrierType()
}
newConf := &SealConfig{
Type: expType,
SecretThreshold: 3,
SecretShares: 5,
}
hErr := c.RekeyInit(newConf, recovery)
if hErr != nil {
t.Fatalf("err: %v", hErr)
}
// Fetch new config with generated nonce
rkconf, hErr := c.RekeyConfig(recovery)
if hErr != nil {
t.Fatalf("err: %v", hErr)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
// Provide the master/recovery keys
var result *RekeyResult
for _, key := range keys {
result, err = c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
if result != nil {
break
}
}
if result == nil || len(result.SecretShares) != newConf.SecretShares {
t.Fatalf("rekey update error: %#v", result)
}
// Should be no progress
if _, _, err := c.RekeyProgress(recovery, false); err == nil {
t.Fatal("expected error from RekeyProgress")
}
// Should be no config
conf, hErr := c.RekeyConfig(recovery)
if hErr != nil {
t.Fatalf("rekey config error: %v", hErr)
}
if conf != nil {
t.Fatalf("rekey config should be nil, got: %v", conf)
}
// SealConfig should update
var sealConf *SealConfig
if recovery {
sealConf, err = c.seal.RecoveryConfig(context.Background())
} else {
sealConf, err = c.seal.BarrierConfig(context.Background())
}
if err != nil {
t.Fatalf("seal config retrieval error: %v", err)
}
if sealConf == nil {
t.Fatal("seal configuration is nil")
}
newConf.Nonce = rkconf.Nonce
if !reflect.DeepEqual(sealConf, newConf) {
t.Fatalf("\nexpected: %#v\nactual: %#v\nexpType: %s\nrecovery: %t", newConf, sealConf, expType, recovery)
}
// Attempt unseal if this was not recovery mode
if !recovery {
err = c.Seal(root)
if err != nil {
t.Fatalf("err: %v", err)
}
for i := 0; i < newConf.SecretThreshold; i++ {
_, err = TestCoreUnseal(c, TestKeyCopy(result.SecretShares[i]))
if err != nil {
t.Fatalf("err: %v", err)
}
}
if c.Sealed() {
t.Fatalf("should be unsealed")
}
}
// Start another rekey, this time we require a quorum!
// Skip this step if we are rekeying the barrier key with
// recovery keys, since a new rekey should still be using
// the same set of recovery keys.
if !recovery && c.seal.RecoveryKeySupported() {
return
}
newConf = &SealConfig{
Type: expType,
SecretThreshold: 1,
SecretShares: 1,
}
err = c.RekeyInit(newConf, recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err = c.RekeyConfig(recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
// Provide the parts master
oldResult := result
for i := 0; i < 3; i++ {
result, err = c.RekeyUpdate(context.Background(), TestKeyCopy(oldResult.SecretShares[i]), rkconf.Nonce, recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should be progress
if i < 2 {
_, num, err := c.RekeyProgress(recovery, false)
if err != nil {
t.Fatalf("err: %v", err)
}
if num != i+1 {
t.Fatalf("bad: %d", num)
}
}
}
if result == nil || len(result.SecretShares) != 1 {
t.Fatalf("Bad: %#v", result)
}
// Attempt unseal if this was not recovery mode
if !recovery {
err = c.Seal(root)
if err != nil {
t.Fatalf("err: %v", err)
}
unseal, err := TestCoreUnseal(c, result.SecretShares[0])
if err != nil {
t.Fatalf("err: %v", err)
}
if !unseal {
t.Fatalf("should be unsealed")
}
}
// SealConfig should update
if recovery {
sealConf, err = c.seal.RecoveryConfig(context.Background())
} else {
sealConf, err = c.seal.BarrierConfig(context.Background())
}
if err != nil {
t.Fatalf("err: %v", err)
}
newConf.Nonce = rkconf.Nonce
if !reflect.DeepEqual(sealConf, newConf) {
t.Fatalf("bad: %#v", sealConf)
}
}
func TestCore_Rekey_Invalid(t *testing.T) {
bc, _ := TestSealDefConfigs()
bc.StoredShares = 0
bc.SecretShares = 1
bc.SecretThreshold = 1
c, masterKeys, _, _ := TestCoreUnsealedWithConfigs(t, bc, nil)
testCore_Rekey_Invalid_Common(t, c, masterKeys, false)
}
func testCore_Rekey_Invalid_Common(t *testing.T, c *Core, keys [][]byte, recovery bool) {
// Start a rekey
newConf := &SealConfig{
SecretThreshold: 3,
SecretShares: 5,
}
err := c.RekeyInit(newConf, recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err := c.RekeyConfig(recovery)
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
// Provide the nonce (invalid)
_, err = c.RekeyUpdate(context.Background(), keys[0], "abcd", recovery)
if err == nil {
t.Fatalf("expected error")
}
// Provide the key (invalid)
key := keys[0]
oldkeystr := fmt.Sprintf("%#v", key)
key[0]++
newkeystr := fmt.Sprintf("%#v", key)
ret, err := c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery)
if err == nil {
t.Fatalf("expected error, ret is %#v\noldkeystr: %s\nnewkeystr: %s", *ret, oldkeystr, newkeystr)
}
}
func TestCore_Rekey_Standby(t *testing.T) {
// Create the first core and initialize it
logger := logging.NewVaultLogger(log.Trace)
inm, err := inmem.NewInmemHA(nil, logger)
if err != nil {
t.Fatal(err)
}
inmha, err := inmem.NewInmemHA(nil, logger)
if err != nil {
t.Fatal(err)
}
redirectOriginal := "http://127.0.0.1:8200"
core, err := NewCore(&CoreConfig{
Physical: inm,
HAPhysical: inmha.(physical.HABackend),
RedirectAddr: redirectOriginal,
DisableMlock: true,
DisableCache: true,
})
if err != nil {
t.Fatalf("err: %v", err)
}
keys, root := TestCoreInit(t, core)
for _, key := range keys {
if _, err := TestCoreUnseal(core, TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
}
// Wait for core to become active
TestWaitActive(t, core)
// Create a second core, attached to same in-memory store
redirectOriginal2 := "http://127.0.0.1:8500"
core2, err := NewCore(&CoreConfig{
Physical: inm,
HAPhysical: inmha.(physical.HABackend),
RedirectAddr: redirectOriginal2,
DisableMlock: true,
DisableCache: true,
})
if err != nil {
t.Fatalf("err: %v", err)
}
for _, key := range keys {
if _, err := TestCoreUnseal(core2, TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
}
// Rekey the master key
newConf := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
}
err = core.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err := core.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
var rekeyResult *RekeyResult
for _, key := range keys {
rekeyResult, err = core.RekeyUpdate(context.Background(), key, rkconf.Nonce, false)
if err != nil {
t.Fatalf("err: %v", err)
}
}
if rekeyResult == nil {
t.Fatalf("rekey failed")
}
// Seal the first core, should step down
err = core.Seal(root)
if err != nil {
t.Fatalf("err: %v", err)
}
// Wait for core2 to become active
TestWaitActive(t, core2)
// Rekey the master key again
err = core2.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err = core2.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
var rekeyResult2 *RekeyResult
for _, key := range rekeyResult.SecretShares {
rekeyResult2, err = core2.RekeyUpdate(context.Background(), key, rkconf.Nonce, false)
if err != nil {
t.Fatalf("err: %v", err)
}
}
if rekeyResult2 == nil {
t.Fatalf("rekey failed")
}
if err != nil {
t.Fatalf("err: %v", err)
}
if rekeyResult2 == nil {
t.Fatalf("rekey failed")
}
}