open-vault/vault/rekey_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

535 lines
12 KiB
Go

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"
)
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()
}
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,
&TestSealOpts{StoredKeys: 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)
}
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")
}
}
// 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},
&TestSealOpts{StoredKeys: 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)
}
}