open-vault/command/seal_migration_test.go

751 lines
19 KiB
Go

package command
import (
"context"
"encoding/base64"
"testing"
wrapping "github.com/hashicorp/go-kms-wrapping"
aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/testhelpers"
sealhelper "github.com/hashicorp/vault/helper/testhelpers/seal"
"github.com/hashicorp/vault/helper/testhelpers/teststorage"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/vault"
vaultseal "github.com/hashicorp/vault/vault/seal"
)
func verifyBarrierConfig(t *testing.T, cfg *vault.SealConfig, sealType string, shares, threshold, stored int) {
t.Helper()
if cfg.Type != sealType {
t.Fatalf("bad seal config: %#v, expected type=%q", cfg, sealType)
}
if cfg.SecretShares != shares {
t.Fatalf("bad seal config: %#v, expected SecretShares=%d", cfg, shares)
}
if cfg.SecretThreshold != threshold {
t.Fatalf("bad seal config: %#v, expected SecretThreshold=%d", cfg, threshold)
}
if cfg.StoredShares != stored {
t.Fatalf("bad seal config: %#v, expected StoredShares=%d", cfg, stored)
}
}
func TestSealMigration_ShamirToTransit(t *testing.T) {
t.Parallel()
t.Run("inmem", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTransit(t, teststorage.InmemBackendSetup)
})
t.Run("file", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTransit(t, teststorage.FileBackendSetup)
})
t.Run("consul", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTransit(t, teststorage.ConsulBackendSetup)
})
t.Run("raft", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTransit(t, teststorage.RaftBackendSetup)
})
}
func testSealMigrationShamirToTransit(t *testing.T, setup teststorage.ClusterSetupMutator) {
// Create a cluster that uses shamir.
conf, opts := teststorage.ClusterSetup(&vault.CoreConfig{
DisableSealWrap: true,
}, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
SkipInit: true,
NumCores: 3,
},
setup,
)
opts.SetupFunc = nil
cluster := vault.NewTestCluster(t, conf, opts)
cluster.Start()
defer cluster.Cleanup()
// Initialize the cluster, and unseal it using the shamir keys.
client := cluster.Cores[0].Client
initResp, err := client.Sys().Init(&api.InitRequest{
SecretShares: 5,
SecretThreshold: 3,
})
if err != nil {
t.Fatal(err)
}
var resp *api.SealStatusResponse
for _, key := range initResp.KeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
rootToken := initResp.RootToken
client.SetToken(rootToken)
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Create the transit server.
tcluster := sealhelper.NewTransitSealServer(t)
defer tcluster.Cleanup()
tcluster.MakeKey(t, "key1")
transitSeal := tcluster.MakeSeal(t, "key1")
// Transition to transit seal.
if err := adjustCoreForSealMigration(cluster.Logger, cluster.Cores[0].Core, transitSeal, nil); err != nil {
t.Fatal(err)
}
// Unseal and migrate to transit.
for _, key := range initResp.KeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err == nil {
t.Fatal("expected error due to lack of migrate parameter")
}
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key, Migrate: true})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
// Seal the cluster.
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Seal the transit cluster; we expect the unseal of our main cluster
// to fail as a result.
tcluster.EnsureCoresSealed(t)
// Verify that we cannot unseal. Now the barrier unseal keys are actually
// the recovery keys.
for _, key := range initResp.KeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
break
}
if resp == nil || !resp.Sealed {
break
}
}
if err == nil || resp != nil {
t.Fatalf("expected sealed state; got %#v", resp)
}
// Unseal the transit server; we expect the unseal to work now on our main
// cluster.
tcluster.UnsealCores(t)
// Verify that we can unseal.
for _, key := range initResp.KeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
// Make sure the seal configs were updated correctly.
b, r, err := cluster.Cores[0].Core.PhysicalSealConfigs(context.Background())
if err != nil {
t.Fatal(err)
}
verifyBarrierConfig(t, b, wrapping.Transit, 1, 1, 1)
verifyBarrierConfig(t, r, wrapping.Shamir, 5, 3, 0)
}
func TestSealMigration_ShamirToTestSeal(t *testing.T) {
t.Parallel()
t.Run("inmem", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTestSeal(t, teststorage.InmemBackendSetup)
})
t.Run("file", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTestSeal(t, teststorage.FileBackendSetup)
})
t.Run("consul", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTestSeal(t, teststorage.ConsulBackendSetup)
})
t.Run("raft", func(t *testing.T) {
t.Parallel()
testSealMigrationShamirToTestSeal(t, teststorage.RaftBackendSetup)
})
}
func testSealMigrationShamirToTestSeal(t *testing.T, setup teststorage.ClusterSetupMutator) {
// Create a cluster that uses shamir.
conf, opts := teststorage.ClusterSetup(&vault.CoreConfig{
DisableSealWrap: true,
}, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
SkipInit: true,
NumCores: 3,
},
setup,
)
opts.SetupFunc = nil
cluster := vault.NewTestCluster(t, conf, opts)
cluster.Start()
defer cluster.Cleanup()
// Initialize the cluster, and unseal it using the shamir keys.
client := cluster.Cores[0].Client
initResp, err := client.Sys().Init(&api.InitRequest{
SecretShares: 5,
SecretThreshold: 3,
})
if err != nil {
t.Fatal(err)
}
var resp *api.SealStatusResponse
for _, key := range initResp.KeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
rootToken := initResp.RootToken
client.SetToken(rootToken)
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Create a test seal
testSeal := vault.NewAutoSeal(vaultseal.NewTestSeal(&vaultseal.TestSealOpts{}))
// Transition to test seal.
if err := adjustCoreForSealMigration(cluster.Logger, cluster.Cores[0].Core, testSeal, nil); err != nil {
t.Fatal(err)
}
// Unseal and migrate to test seal.
for _, key := range initResp.KeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err == nil {
t.Fatal("expected error due to lack of migrate parameter")
}
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key, Migrate: true})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
// Seal the cluster.
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Verify that we can unseal.
for _, key := range initResp.KeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
// Make sure the seal configs were updated correctly.
b, r, err := cluster.Cores[0].Core.PhysicalSealConfigs(context.Background())
if err != nil {
t.Fatal(err)
}
verifyBarrierConfig(t, b, wrapping.Test, 1, 1, 1)
verifyBarrierConfig(t, r, wrapping.Shamir, 5, 3, 0)
}
func TestSealMigration_TransitToTestSeal(t *testing.T) {
t.Parallel()
t.Run("inmem", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTestSeal(t, teststorage.InmemBackendSetup)
})
t.Run("file", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTestSeal(t, teststorage.FileBackendSetup)
})
t.Run("consul", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTestSeal(t, teststorage.ConsulBackendSetup)
})
t.Run("raft", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTestSeal(t, teststorage.RaftBackendSetup)
})
}
func testSealMigrationTransitToTestSeal(t *testing.T, setup teststorage.ClusterSetupMutator) {
// Create the transit server.
tcluster := sealhelper.NewTransitSealServer(t)
defer func() {
if tcluster != nil {
tcluster.Cleanup()
}
}()
tcluster.MakeKey(t, "key1")
var transitSeal vault.Seal
// Create a cluster that uses transit.
conf, opts := teststorage.ClusterSetup(&vault.CoreConfig{
DisableSealWrap: true,
}, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
SkipInit: true,
NumCores: 3,
SealFunc: func() vault.Seal {
transitSeal = tcluster.MakeSeal(t, "key1")
return transitSeal
},
},
setup,
)
opts.SetupFunc = nil
cluster := vault.NewTestCluster(t, conf, opts)
cluster.Start()
defer cluster.Cleanup()
// Initialize the cluster, and fetch the recovery keys.
client := cluster.Cores[0].Client
initResp, err := client.Sys().Init(&api.InitRequest{
RecoveryShares: 5,
RecoveryThreshold: 3,
})
if err != nil {
t.Fatal(err)
}
for _, k := range initResp.RecoveryKeysB64 {
b, _ := base64.RawStdEncoding.DecodeString(k)
cluster.RecoveryKeys = append(cluster.RecoveryKeys, b)
}
testhelpers.WaitForActiveNode(t, cluster)
rootToken := initResp.RootToken
client.SetToken(rootToken)
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Create a test seal
testSeal := vault.NewAutoSeal(vaultseal.NewTestSeal(&vaultseal.TestSealOpts{}))
// Transition to test seal.
if err := adjustCoreForSealMigration(cluster.Logger, cluster.Cores[0].Core, testSeal, transitSeal); err != nil {
t.Fatal(err)
}
// Unseal and migrate to Test Seal.
// Although we're unsealing using the recovery keys, this is still an
// autounseal; if we stopped the transit cluster this would fail.
var resp *api.SealStatusResponse
for _, key := range initResp.RecoveryKeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err == nil {
t.Fatal("expected error due to lack of migrate parameter")
}
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key, Migrate: true})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
// Seal the cluster.
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Unseal the cluster. Now the recovery keys are actually the barrier
// unseal keys.
for _, key := range initResp.RecoveryKeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
// Make sure the seal configs were updated correctly.
b, r, err := cluster.Cores[0].Core.PhysicalSealConfigs(context.Background())
if err != nil {
t.Fatal(err)
}
verifyBarrierConfig(t, b, wrapping.Test, 1, 1, 1)
verifyBarrierConfig(t, r, wrapping.Shamir, 5, 3, 0)
// Now that migration is done, we can stop the transit cluster, since we
// can seal/unseal without it.
tcluster.Cleanup()
tcluster = nil
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
for _, key := range initResp.RecoveryKeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
}
func TestSealMigration_TransitToShamir(t *testing.T) {
t.Parallel()
t.Run("inmem", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToShamir(t, teststorage.InmemBackendSetup)
})
t.Run("file", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToShamir(t, teststorage.FileBackendSetup)
})
t.Run("consul", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToShamir(t, teststorage.ConsulBackendSetup)
})
t.Run("raft", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToShamir(t, teststorage.RaftBackendSetup)
})
}
func testSealMigrationTransitToShamir(t *testing.T, setup teststorage.ClusterSetupMutator) {
// Create the transit server.
tcluster := sealhelper.NewTransitSealServer(t)
defer func() {
if tcluster != nil {
tcluster.Cleanup()
}
}()
tcluster.MakeKey(t, "key1")
var transitSeal vault.Seal
// Create a cluster that uses transit.
conf, opts := teststorage.ClusterSetup(&vault.CoreConfig{
DisableSealWrap: true,
}, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
SkipInit: true,
NumCores: 3,
SealFunc: func() vault.Seal {
transitSeal = tcluster.MakeSeal(t, "key1")
return transitSeal
},
},
setup,
)
opts.SetupFunc = nil
cluster := vault.NewTestCluster(t, conf, opts)
cluster.Start()
defer cluster.Cleanup()
// Initialize the cluster, and fetch the recovery keys.
client := cluster.Cores[0].Client
initResp, err := client.Sys().Init(&api.InitRequest{
RecoveryShares: 5,
RecoveryThreshold: 3,
})
if err != nil {
t.Fatal(err)
}
for _, k := range initResp.RecoveryKeysB64 {
b, _ := base64.RawStdEncoding.DecodeString(k)
cluster.RecoveryKeys = append(cluster.RecoveryKeys, b)
}
testhelpers.WaitForActiveNode(t, cluster)
rootToken := initResp.RootToken
client.SetToken(rootToken)
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Create a Shamir seal.
logger := cluster.Logger.Named("shamir")
shamirSeal := vault.NewDefaultSeal(&vaultseal.Access{
Wrapper: aeadwrapper.NewWrapper(&wrapping.WrapperOptions{
Logger: logger,
}),
})
// Transition to Shamir seal.
if err := adjustCoreForSealMigration(logger, cluster.Cores[0].Core, shamirSeal, transitSeal); err != nil {
t.Fatal(err)
}
// Unseal and migrate to Shamir.
// Although we're unsealing using the recovery keys, this is still an
// autounseal; if we stopped the transit cluster this would fail.
var resp *api.SealStatusResponse
for _, key := range initResp.RecoveryKeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err == nil {
t.Fatal("expected error due to lack of migrate parameter")
}
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key, Migrate: true})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
// Seal the cluster.
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Unseal the cluster. Now the recovery keys are actually the barrier
// unseal keys.
for _, key := range initResp.RecoveryKeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
// Make sure the seal configs were updated correctly.
b, r, err := cluster.Cores[0].Core.PhysicalSealConfigs(context.Background())
if err != nil {
t.Fatal(err)
}
verifyBarrierConfig(t, b, wrapping.Shamir, 5, 3, 1)
if r != nil {
t.Fatalf("expected nil recovery config, got: %#v", r)
}
// Now that migration is done, we can stop the transit cluster, since we
// can seal/unseal without it.
tcluster.Cleanup()
tcluster = nil
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
for _, key := range initResp.RecoveryKeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
}
/*
func TestSealMigration_TransitToTransit(t *testing.T) {
t.Parallel()
t.Run("inmem", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTransit(t, teststorage.InmemBackendSetup)
})
t.Run("file", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTransit(t, teststorage.FileBackendSetup)
})
t.Run("consul", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTransit(t, teststorage.ConsulBackendSetup)
})
t.Run("raft", func(t *testing.T) {
t.Parallel()
testSealMigrationTransitToTransit(t, teststorage.RaftBackendSetup)
})
}
func testSealMigrationTransitToTransit(t *testing.T, setup teststorage.ClusterSetupMutator) {
tcluster := sealhelper.NewTransitSealServer(t)
defer tcluster.Cleanup()
tcluster.MakeKey(t, "key1")
tcluster.MakeKey(t, "key2")
var seals []vault.Seal
conf, opts := teststorage.ClusterSetup(&vault.CoreConfig{
DisableSealWrap: true,
}, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
SkipInit: true,
NumCores: 3,
SealFunc: func() vault.Seal {
tseal := tcluster.MakeSeal(t, "key1")
seals = append(seals, tseal)
return tseal
},
},
setup,
)
opts.SetupFunc = nil
cluster := vault.NewTestCluster(t, conf, opts)
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
initResp, err := client.Sys().Init(&api.InitRequest{
RecoveryShares: 5,
RecoveryThreshold: 3,
})
if err != nil {
t.Fatal(err)
}
rootToken := initResp.RootToken
client.SetToken(rootToken)
for _, k := range initResp.RecoveryKeysB64 {
b, _ := base64.RawStdEncoding.DecodeString(k)
cluster.RecoveryKeys = append(cluster.RecoveryKeys, b)
}
testhelpers.WaitForActiveNode(t, cluster)
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
logger := cluster.Logger.Named("shamir")
autoSeal2 := tcluster.MakeSeal(t, "key2")
if err := adjustCoreForSealMigration(logger, cluster.Cores[0].Core, autoSeal2, seals[0]); err != nil {
t.Fatal(err)
}
// Although we're unsealing using the recovery keys, this is still an
// autounseal; if we stopped the transit cluster this would fail.
var resp *api.SealStatusResponse
for _, key := range initResp.RecoveryKeysB64 {
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key})
if err == nil {
t.Fatal("expected error due to lack of migrate parameter")
}
resp, err = client.Sys().UnsealWithOptions(&api.UnsealOpts{Key: key, Migrate: true})
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.Sealed {
break
}
}
if resp == nil || resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", resp)
}
testhelpers.WaitForActiveNode(t, cluster)
// Seal and unseal again to verify that things are working fine
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
// Delete the original seal's transit key.
_, err = tcluster.Cores[0].Client.Logical().Delete(path.Join("transit", "keys", "key1"))
if err != nil {
t.Fatal(err)
}
err = cluster.Cores[0].Core.UnsealWithStoredKeys(context.Background())
if err != nil {
t.Fatal(err)
}
}
*/