// 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) } }