core: re-encrypt barrier and recovery keys if the unseal key is updated (#7493)
Seal keys can be rotated. When this happens, the barrier and recovery keys should be re-encrypted with the new seal key. This change automatically re-encrypts the barrier and recovery keys with the latest seal key on the active node during the 'postUnseal' phase.
This commit is contained in:
parent
49f7e290ff
commit
6b1e1909e9
|
@ -1654,7 +1654,7 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c
|
|||
return nil
|
||||
}
|
||||
|
||||
// postUnseal is invoked after the barrier is unsealed, but before
|
||||
// postUnseal is invoked on the active node after the barrier is unsealed, but before
|
||||
// allowing any user operations. This allows us to setup any state that
|
||||
// requires the Vault to be unsealed such as mount tables, logical backends,
|
||||
// credential stores, etc.
|
||||
|
@ -1692,6 +1692,18 @@ func (c *Core) postUnseal(ctx context.Context, ctxCancelFunc context.CancelFunc,
|
|||
return err
|
||||
}
|
||||
|
||||
// Automatically re-encrypt the keys used for auto unsealing when the
|
||||
// seal's encryption key changes. The regular rotation of cryptographic
|
||||
// keys is a NIST recommendation. Access to prior keys for decryption
|
||||
// is normally supported for a configurable time period. Re-encrypting
|
||||
// the keys used for auto unsealing ensures Vault and its data will
|
||||
// continue to be accessible even after prior seal keys are destroyed.
|
||||
if seal, ok := c.seal.(*autoSeal); ok {
|
||||
if err := seal.UpgradeKeys(c.activeContext); err != nil {
|
||||
c.logger.Warn("post-unseal upgrade seal keys failed", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.metricsCh = make(chan struct{})
|
||||
go c.emitMetrics(c.metricsCh)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
type TestSeal struct {
|
||||
Type string
|
||||
secret []byte
|
||||
keyId string
|
||||
}
|
||||
|
||||
var _ Access = (*TestSeal)(nil)
|
||||
|
@ -18,6 +19,7 @@ func NewTestSeal(secret []byte) *TestSeal {
|
|||
return &TestSeal{
|
||||
Type: Test,
|
||||
secret: secret,
|
||||
keyId: "static-key",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,7 +36,11 @@ func (t *TestSeal) SealType() string {
|
|||
}
|
||||
|
||||
func (t *TestSeal) KeyID() string {
|
||||
return "static-key"
|
||||
return t.keyId
|
||||
}
|
||||
|
||||
func (t *TestSeal) SetKeyID(k string) {
|
||||
t.keyId = k
|
||||
}
|
||||
|
||||
func (t *TestSeal) Encrypt(_ context.Context, plaintext []byte) (*physical.EncryptedBlobInfo, error) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
"github.com/hashicorp/errwrap"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
|
@ -26,12 +27,13 @@ type autoSeal struct {
|
|||
barrierConfig atomic.Value
|
||||
recoveryConfig atomic.Value
|
||||
core *Core
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// Ensure we are implementing the Seal interface
|
||||
var _ Seal = (*autoSeal)(nil)
|
||||
|
||||
func NewAutoSeal(lowLevel seal.Access) Seal {
|
||||
func NewAutoSeal(lowLevel seal.Access) *autoSeal {
|
||||
ret := &autoSeal{
|
||||
Access: lowLevel,
|
||||
}
|
||||
|
@ -53,6 +55,10 @@ func (d *autoSeal) checkCore() error {
|
|||
|
||||
func (d *autoSeal) SetCore(core *Core) {
|
||||
d.core = core
|
||||
if d.logger == nil {
|
||||
d.logger = d.core.Logger().Named("autoseal")
|
||||
d.core.AddLogger(d.logger)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *autoSeal) Init(ctx context.Context) error {
|
||||
|
@ -147,6 +153,61 @@ func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) {
|
|||
return keys, nil
|
||||
}
|
||||
|
||||
func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error {
|
||||
pe, err := d.core.physical.Get(ctx, StoredBarrierKeysPath)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to fetch stored keys: {{err}}", err)
|
||||
}
|
||||
if pe == nil {
|
||||
return fmt.Errorf("no stored keys found")
|
||||
}
|
||||
|
||||
blobInfo := &physical.EncryptedBlobInfo{}
|
||||
if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
|
||||
return errwrap.Wrapf("failed to proto decode stored keys: {{err}}", err)
|
||||
}
|
||||
|
||||
if blobInfo.KeyInfo != nil && blobInfo.KeyInfo.KeyID != d.Access.KeyID() {
|
||||
d.logger.Info("upgrading stored keys")
|
||||
|
||||
pt, err := d.Decrypt(ctx, blobInfo)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to decrypt encrypted stored keys: {{err}}", err)
|
||||
}
|
||||
|
||||
// Decode the barrier entry
|
||||
var keys [][]byte
|
||||
if err := json.Unmarshal(pt, &keys); err != nil {
|
||||
return errwrap.Wrapf("failed to decode stored keys: {{err}}", err)
|
||||
}
|
||||
|
||||
if err := d.SetStoredKeys(ctx, keys); err != nil {
|
||||
return errwrap.Wrapf("failed to save upgraded stored keys: {{err}}", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpgradeKeys re-encrypts and saves the stored keys and the recovery key
|
||||
// with the current key if the current KeyID is different from the KeyID
|
||||
// the stored keys and the recovery key are encrypted with. The provided
|
||||
// Context must be non-nil.
|
||||
func (d *autoSeal) UpgradeKeys(ctx context.Context) error {
|
||||
// Many of the seals update their keys to the latest KeyID when Encrypt
|
||||
// is called.
|
||||
if _, err := d.Encrypt(ctx, []byte("a")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.upgradeRecoveryKey(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.upgradeStoredKeys(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *autoSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
||||
if d.barrierConfig.Load().(*SealConfig) != nil {
|
||||
return d.barrierConfig.Load().(*SealConfig).Clone(), nil
|
||||
|
@ -160,14 +221,14 @@ func (d *autoSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
|||
|
||||
entry, err := d.core.physical.Get(ctx, barrierSealConfigPath)
|
||||
if err != nil {
|
||||
d.core.logger.Error("autoseal: failed to read seal configuration", "seal_type", sealType, "error", err)
|
||||
d.logger.Error("failed to read seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to read %q seal configuration: {{err}}", sealType), err)
|
||||
}
|
||||
|
||||
// If the seal configuration is missing, we are not initialized
|
||||
if entry == nil {
|
||||
if d.core.logger.IsInfo() {
|
||||
d.core.logger.Info("autoseal: seal configuration missing, not initialized", "seal_type", sealType)
|
||||
if d.logger.IsInfo() {
|
||||
d.logger.Info("seal configuration missing, not initialized", "seal_type", sealType)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -175,20 +236,20 @@ func (d *autoSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
|||
conf := &SealConfig{}
|
||||
err = json.Unmarshal(entry.Value, conf)
|
||||
if err != nil {
|
||||
d.core.logger.Error("autoseal: failed to decode seal configuration", "seal_type", sealType, "error", err)
|
||||
d.logger.Error("failed to decode seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to decode %q seal configuration: {{err}}", sealType), err)
|
||||
}
|
||||
|
||||
// Check for a valid seal configuration
|
||||
if err := conf.Validate(); err != nil {
|
||||
d.core.logger.Error("autoseal: invalid seal configuration", "seal_type", sealType, "error", err)
|
||||
d.logger.Error("invalid seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("%q seal validation failed: {{err}}", sealType), err)
|
||||
}
|
||||
|
||||
barrierTypeUpgradeCheck(d.BarrierType(), conf)
|
||||
|
||||
if conf.Type != d.BarrierType() {
|
||||
d.core.logger.Error("autoseal: barrier seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.BarrierType())
|
||||
d.logger.Error("barrier seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.BarrierType())
|
||||
return nil, fmt.Errorf("barrier seal type of %q does not match loaded type of %q", conf.Type, d.BarrierType())
|
||||
}
|
||||
|
||||
|
@ -221,7 +282,7 @@ func (d *autoSeal) SetBarrierConfig(ctx context.Context, conf *SealConfig) error
|
|||
}
|
||||
|
||||
if err := d.core.physical.Put(ctx, pe); err != nil {
|
||||
d.core.logger.Error("autoseal: failed to write barrier seal configuration", "error", err)
|
||||
d.logger.Error("failed to write barrier seal configuration", "error", err)
|
||||
return errwrap.Wrapf("failed to write barrier seal configuration: {{err}}", err)
|
||||
}
|
||||
|
||||
|
@ -254,13 +315,13 @@ func (d *autoSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
|
|||
var err error
|
||||
entry, err = d.core.physical.Get(ctx, recoverySealConfigPlaintextPath)
|
||||
if err != nil {
|
||||
d.core.logger.Error("autoseal: failed to read seal configuration", "seal_type", sealType, "error", err)
|
||||
d.logger.Error("failed to read seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to read %q seal configuration: {{err}}", sealType), err)
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
if d.core.Sealed() {
|
||||
d.core.logger.Info("autoseal: seal configuration missing, but cannot check old path as core is sealed", "seal_type", sealType)
|
||||
d.logger.Info("seal configuration missing, but cannot check old path as core is sealed", "seal_type", sealType)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -273,8 +334,8 @@ func (d *autoSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
|
|||
|
||||
// If the seal configuration is missing, then we are not initialized.
|
||||
if be == nil {
|
||||
if d.core.logger.IsInfo() {
|
||||
d.core.logger.Info("autoseal: seal configuration missing, not initialized", "seal_type", sealType)
|
||||
if d.logger.IsInfo() {
|
||||
d.logger.Info("seal configuration missing, not initialized", "seal_type", sealType)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -288,18 +349,18 @@ func (d *autoSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
|
|||
|
||||
conf := &SealConfig{}
|
||||
if err := json.Unmarshal(entry.Value, conf); err != nil {
|
||||
d.core.logger.Error("autoseal: failed to decode seal configuration", "seal_type", sealType, "error", err)
|
||||
d.logger.Error("failed to decode seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to decode %q seal configuration: {{err}}", sealType), err)
|
||||
}
|
||||
|
||||
// Check for a valid seal configuration
|
||||
if err := conf.Validate(); err != nil {
|
||||
d.core.logger.Error("autoseal: invalid seal configuration", "seal_type", sealType, "error", err)
|
||||
d.logger.Error("invalid seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("%q seal validation failed: {{err}}", sealType), err)
|
||||
}
|
||||
|
||||
if conf.Type != d.RecoveryType() {
|
||||
d.core.logger.Error("autoseal: recovery seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.RecoveryType())
|
||||
d.logger.Error("recovery seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.RecoveryType())
|
||||
return nil, fmt.Errorf("recovery seal type of %q does not match loaded type of %q", conf.Type, d.RecoveryType())
|
||||
}
|
||||
|
||||
|
@ -339,7 +400,7 @@ func (d *autoSeal) SetRecoveryConfig(ctx context.Context, conf *SealConfig) erro
|
|||
}
|
||||
|
||||
if err := d.core.physical.Put(ctx, pe); err != nil {
|
||||
d.core.logger.Error("autoseal: failed to write recovery seal configuration", "error", err)
|
||||
d.logger.Error("failed to write recovery seal configuration", "error", err)
|
||||
return errwrap.Wrapf("failed to write recovery seal configuration: {{err}}", err)
|
||||
}
|
||||
|
||||
|
@ -395,7 +456,7 @@ func (d *autoSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
|
|||
}
|
||||
|
||||
if err := d.core.physical.Put(ctx, be); err != nil {
|
||||
d.core.logger.Error("autoseal: failed to write recovery key", "error", err)
|
||||
d.logger.Error("failed to write recovery key", "error", err)
|
||||
return errwrap.Wrapf("failed to write recovery key: {{err}}", err)
|
||||
}
|
||||
|
||||
|
@ -409,11 +470,11 @@ func (d *autoSeal) RecoveryKey(ctx context.Context) ([]byte, error) {
|
|||
func (d *autoSeal) getRecoveryKeyInternal(ctx context.Context) ([]byte, error) {
|
||||
pe, err := d.core.physical.Get(ctx, recoveryKeyPath)
|
||||
if err != nil {
|
||||
d.core.logger.Error("autoseal: failed to read recovery key", "error", err)
|
||||
d.logger.Error("failed to read recovery key", "error", err)
|
||||
return nil, errwrap.Wrapf("failed to read recovery key: {{err}}", err)
|
||||
}
|
||||
if pe == nil {
|
||||
d.core.logger.Warn("autoseal: no recovery key found")
|
||||
d.logger.Warn("no recovery key found")
|
||||
return nil, fmt.Errorf("no recovery key found")
|
||||
}
|
||||
|
||||
|
@ -430,6 +491,35 @@ func (d *autoSeal) getRecoveryKeyInternal(ctx context.Context) ([]byte, error) {
|
|||
return pt, nil
|
||||
}
|
||||
|
||||
func (d *autoSeal) upgradeRecoveryKey(ctx context.Context) error {
|
||||
pe, err := d.core.physical.Get(ctx, recoveryKeyPath)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to fetch recovery key: {{err}}", err)
|
||||
}
|
||||
if pe == nil {
|
||||
return fmt.Errorf("no recovery key found")
|
||||
}
|
||||
|
||||
blobInfo := &physical.EncryptedBlobInfo{}
|
||||
if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
|
||||
return errwrap.Wrapf("failed to proto decode recovery key: {{err}}", err)
|
||||
}
|
||||
|
||||
if blobInfo.KeyInfo != nil && blobInfo.KeyInfo.KeyID != d.Access.KeyID() {
|
||||
d.logger.Info("upgrading recovery key")
|
||||
|
||||
pt, err := d.Decrypt(ctx, blobInfo)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to decrypt encrypted recovery key: {{err}}", err)
|
||||
|
||||
}
|
||||
if err := d.SetRecoveryKey(ctx, pt); err != nil {
|
||||
return errwrap.Wrapf("failed to save upgraded recovery key: {{err}}", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateRecoveryConfig is a helper func to migrate the recovery config to
|
||||
// live outside the barrier. This is called from SetRecoveryConfig which is
|
||||
// always called with the stateLock.
|
||||
|
@ -446,8 +536,8 @@ func (d *autoSeal) migrateRecoveryConfig(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// Only log if we are performing the migration
|
||||
d.core.logger.Debug("migrating recovery seal configuration")
|
||||
defer d.core.logger.Debug("done migrating recovery seal configuration")
|
||||
d.logger.Debug("migrating recovery seal configuration")
|
||||
defer d.logger.Debug("done migrating recovery seal configuration")
|
||||
|
||||
// Perform migration
|
||||
pe := &physical.Entry{
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
|
||||
// phy implements physical.Backend. It maps keys to a slice of entries.
|
||||
// Each call to Put appends the entry to the slice of entries for that
|
||||
// key. No deduplication is done. This allows the test for UpgradeKeys to
|
||||
// verify entries are only being updated when the underlying encryption key
|
||||
// has been updated.
|
||||
type phy struct {
|
||||
t *testing.T
|
||||
entries map[string][]*physical.Entry
|
||||
}
|
||||
|
||||
var _ physical.Backend = (*phy)(nil)
|
||||
|
||||
func newTestBackend(t *testing.T) *phy {
|
||||
return &phy{
|
||||
t: t,
|
||||
entries: make(map[string][]*physical.Entry),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *phy) Put(_ context.Context, entry *physical.Entry) error {
|
||||
p.entries[entry.Key] = append(p.entries[entry.Key], entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *phy) Get(_ context.Context, key string) (*physical.Entry, error) {
|
||||
entries := p.entries[key]
|
||||
if entries == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return entries[len(entries)-1], nil
|
||||
}
|
||||
|
||||
func (p *phy) Delete(_ context.Context, key string) error {
|
||||
p.t.Errorf("Delete called on phy: key: %v", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *phy) List(_ context.Context, prefix string) ([]string, error) {
|
||||
p.t.Errorf("List called on phy: prefix: %v", prefix)
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (p *phy) Len() int {
|
||||
return len(p.entries)
|
||||
}
|
||||
|
||||
func TestAutoSeal_UpgradeKeys(t *testing.T) {
|
||||
core, _, _ := TestCoreUnsealed(t)
|
||||
testSeal := seal.NewTestSeal(nil)
|
||||
|
||||
var encKeys []string
|
||||
changeKey := func(key string) {
|
||||
encKeys = append(encKeys, key)
|
||||
testSeal.SetKeyID(key)
|
||||
}
|
||||
|
||||
// Set initial encryption key.
|
||||
changeKey("kaz")
|
||||
|
||||
autoSeal := NewAutoSeal(testSeal)
|
||||
autoSeal.SetCore(core)
|
||||
pBackend := newTestBackend(t)
|
||||
core.physical = pBackend
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
inkeys := [][]byte{[]byte("grist"), []byte("house")}
|
||||
if err := autoSeal.SetStoredKeys(ctx, inkeys); err != nil {
|
||||
t.Fatalf("SetStoredKeys: want no error, got %v", err)
|
||||
}
|
||||
|
||||
inRecoveryKey := []byte("falernum")
|
||||
if err := autoSeal.SetRecoveryKey(ctx, inRecoveryKey); err != nil {
|
||||
t.Fatalf("SetRecoveryKey: want no error, got %v", err)
|
||||
}
|
||||
|
||||
check := func() {
|
||||
// The values of the stored keys should never change.
|
||||
outkeys, err := autoSeal.GetStoredKeys(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetStoredKeys: want no error, got %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(inkeys, outkeys) {
|
||||
t.Errorf("incorrect stored keys: want %v, got %v", inkeys, outkeys)
|
||||
}
|
||||
|
||||
// The value of the recovery key should also never change.
|
||||
outRecoveryKey, err := autoSeal.RecoveryKey(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("RecoveryKey: want no error, got %v", err)
|
||||
}
|
||||
if !bytes.Equal(inRecoveryKey, outRecoveryKey) {
|
||||
t.Errorf("incorrect recovery key: want %q, got %q", inRecoveryKey, outRecoveryKey)
|
||||
}
|
||||
|
||||
// There should only be 2 entries in the physical backend. One for
|
||||
// the stored keys and one for the recovery key.
|
||||
if want, got := 2, pBackend.Len(); want != got {
|
||||
t.Errorf("backend unexpected Len: want %d, got %d", want, got)
|
||||
}
|
||||
|
||||
for phyKey, phyEntries := range pBackend.entries {
|
||||
// Calling UpgradeKeys should only add an entry if the key has
|
||||
// changed.
|
||||
if keyCount, entryCount := len(encKeys), len(phyEntries); keyCount != entryCount {
|
||||
t.Errorf("phyKey = %s: encryption key count not equal to entry count: keys=%d, entries=%d", phyKey, keyCount, entryCount)
|
||||
}
|
||||
|
||||
// Each phyEntry should correspond to a key at the same index
|
||||
// in encKeys. Iterate over each phyEntry and verify it was
|
||||
// encrypted with its corresponding key in encKeys.
|
||||
for i, phyEntry := range phyEntries {
|
||||
blobInfo := &physical.EncryptedBlobInfo{}
|
||||
if err := proto.Unmarshal(phyEntry.Value, blobInfo); err != nil {
|
||||
t.Errorf("phyKey = %s: failed to proto decode stored keys: %s", phyKey, err)
|
||||
}
|
||||
if blobInfo.KeyInfo == nil {
|
||||
t.Errorf("phyKey = %s: KeyInfo missing: %+v", phyKey, blobInfo)
|
||||
}
|
||||
if want, got := encKeys[i], blobInfo.KeyInfo.KeyID; want != got {
|
||||
t.Errorf("phyKey = %s: Incorrect encryption key: want %s, got %s", phyKey, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the current state is correct before calling UpgradeKeys.
|
||||
check()
|
||||
|
||||
// Call UpgradeKeys before changing the encryption key and verify
|
||||
// nothing has changed.
|
||||
if err := autoSeal.UpgradeKeys(ctx); err != nil {
|
||||
t.Fatalf("UpgradeKeys: want no error, got %v", err)
|
||||
}
|
||||
check()
|
||||
|
||||
// Change the encryption key, call UpgradeKeys, then verify the stored
|
||||
// keys and recovery key has been re-encrypted with the new encryption
|
||||
// key.
|
||||
changeKey("primanti")
|
||||
if err := autoSeal.UpgradeKeys(ctx); err != nil {
|
||||
t.Fatalf("UpgradeKeys: want no error, got %v", err)
|
||||
}
|
||||
check()
|
||||
}
|
Loading…
Reference in New Issue