541 lines
12 KiB
Go
541 lines
12 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package vault
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
|
"github.com/hashicorp/vault/sdk/physical"
|
|
"github.com/hashicorp/vault/sdk/physical/inmem"
|
|
"github.com/hashicorp/vault/vault/seal"
|
|
)
|
|
|
|
func TestCore_Rekey_Lifecycle(t *testing.T) {
|
|
bc := &SealConfig{
|
|
SecretShares: 1,
|
|
SecretThreshold: 1,
|
|
StoredShares: 1,
|
|
}
|
|
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, false)
|
|
}
|
|
|
|
func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
|
|
min, _ := c.barrier.KeyLength()
|
|
// Verify update not allowed
|
|
_, err := c.RekeyUpdate(context.Background(), make([]byte, min), "", recovery)
|
|
expected := "no barrier rekey in progress"
|
|
if recovery {
|
|
expected = "no recovery rekey in progress"
|
|
}
|
|
if err == nil || !strings.Contains(err.Error(), expected) {
|
|
t.Fatalf("no rekey should be in progress, err: %v", err)
|
|
}
|
|
|
|
// 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) {
|
|
t.Run("barrier-rekey-init", func(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,
|
|
}
|
|
|
|
// If recovery key is supported, set newConf
|
|
// to be a recovery seal config
|
|
if c.seal.RecoveryKeySupported() {
|
|
newConf.Type = c.seal.RecoveryType()
|
|
}
|
|
|
|
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 := &SealConfig{
|
|
SecretShares: 1,
|
|
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().String()
|
|
}
|
|
|
|
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 {
|
|
t.Fatal("nil result after update")
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// At this point bail if we are rekeying the barrier key with recovery
|
|
// keys, since a new rekey should still be using the same set of recovery
|
|
// keys and we haven't been returned key shares in this mode.
|
|
if !recovery && c.seal.RecoveryKeySupported() {
|
|
return
|
|
}
|
|
|
|
// 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!
|
|
|
|
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_Legacy(t *testing.T) {
|
|
bc := &SealConfig{
|
|
SecretShares: 1,
|
|
SecretThreshold: 1,
|
|
}
|
|
c, masterKeys, _, root := TestCoreUnsealedWithConfigSealOpts(t, bc, nil,
|
|
&seal.TestSealOpts{StoredKeys: seal.StoredKeysNotSupported})
|
|
testCore_Rekey_Update_Common(t, c, masterKeys, root, false)
|
|
}
|
|
|
|
func TestCore_Rekey_Invalid(t *testing.T) {
|
|
bc := &SealConfig{
|
|
SecretShares: 5,
|
|
SecretThreshold: 3,
|
|
}
|
|
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)
|
|
}
|
|
|
|
// Check progress has been reset
|
|
_, num, err := c.RekeyProgress(recovery, false)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if num != 0 {
|
|
t.Fatalf("rekey progress should be 0, got: %d", num)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
defer core.Shutdown()
|
|
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)
|
|
}
|
|
defer core2.Shutdown()
|
|
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")
|
|
}
|
|
}
|
|
|
|
// verifies that if we are using recovery keys to force a
|
|
// rekey of a stored-shares barrier that verification is not allowed since
|
|
// the keys aren't returned
|
|
func TestSysRekey_Verification_Invalid(t *testing.T) {
|
|
core, _, _, _ := TestCoreUnsealedWithConfigSealOpts(t,
|
|
&SealConfig{StoredShares: 1, SecretShares: 1, SecretThreshold: 1},
|
|
&SealConfig{StoredShares: 1, SecretShares: 1, SecretThreshold: 1},
|
|
&seal.TestSealOpts{StoredKeys: seal.StoredKeysSupportedGeneric})
|
|
|
|
err := core.BarrierRekeyInit(&SealConfig{
|
|
VerificationRequired: true,
|
|
StoredShares: 1,
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if !strings.Contains(err.Error(), "requiring verification not supported") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|