ed14061578
* Work on raft backend * Add logstore locally * Add encryptor and unsealable interfaces * Add clustering support to raft * Remove client and handler * Bootstrap raft on init * Cleanup raft logic a bit * More raft work * Work on TLS config * More work on bootstrapping * Fix build * More work on bootstrapping * More bootstrapping work * fix build * Remove consul dep * Fix build * merged oss/master into raft-storage * Work on bootstrapping * Get bootstrapping to work * Clean up FMS and node-id * Update local node ID logic * Cleanup node-id change * Work on snapshotting * Raft: Add remove peer API (#906) * Add remove peer API * Add some comments * Fix existing snapshotting (#909) * Raft get peers API (#912) * Read raft configuration * address review feedback * Use the Leadership Transfer API to step-down the active node (#918) * Raft join and unseal using Shamir keys (#917) * Raft join using shamir * Store AEAD instead of master key * Split the raft join process to answer the challenge after a successful unseal * get the follower to standby state * Make unseal work * minor changes * Some input checks * reuse the shamir seal access instead of new default seal access * refactor joinRaftSendAnswer function * Synchronously send answer in auto-unseal case * Address review feedback * Raft snapshots (#910) * Fix existing snapshotting * implement the noop snapshotting * Add comments and switch log libraries * add some snapshot tests * add snapshot test file * add TODO * More work on raft snapshotting * progress on the ConfigStore strategy * Don't use two buckets * Update the snapshot store logic to hide the file logic * Add more backend tests * Cleanup code a bit * [WIP] Raft recovery (#938) * Add recovery functionality * remove fmt.Printfs * Fix a few fsm bugs * Add max size value for raft backend (#942) * Add max size value for raft backend * Include physical.ErrValueTooLarge in the message * Raft snapshot Take/Restore API (#926) * Inital work on raft snapshot APIs * Always redirect snapshot install/download requests * More work on the snapshot APIs * Cleanup code a bit * On restore handle special cases * Use the seal to encrypt the sha sum file * Add sealer mechanism and fix some bugs * Call restore while state lock is held * Send restore cb trigger through raft log * Make error messages nicer * Add test helpers * Add snapshot test * Add shamir unseal test * Add more raft snapshot API tests * Fix locking * Change working to initalize * Add underlying raw object to test cluster core * Move leaderUUID to core * Add raft TLS rotation logic (#950) * Add TLS rotation logic * Cleanup logic a bit * Add/Remove from follower state on add/remove peer * add comments * Update more comments * Update request_forwarding_service.proto * Make sure we populate all nodes in the followerstate obj * Update times * Apply review feedback * Add more raft config setting (#947) * Add performance config setting * Add more config options and fix tests * Test Raft Recovery (#944) * Test raft recovery * Leave out a node during recovery * remove unused struct * Update physical/raft/snapshot_test.go * Update physical/raft/snapshot_test.go * fix vendoring * Switch to new raft interface * Remove unused files * Switch a gogo -> proto instance * Remove unneeded vault dep in go.sum * Update helper/testhelpers/testhelpers.go Co-Authored-By: Calvin Leung Huang <cleung2010@gmail.com> * Update vault/cluster/cluster.go * track active key within the keyring itself (#6915) * track active key within the keyring itself * lookup and store using the active key ID * update docstring * minor refactor * Small text fixes (#6912) * Update physical/raft/raft.go Co-Authored-By: Calvin Leung Huang <cleung2010@gmail.com> * review feedback * Move raft logical system into separate file * Update help text a bit * Enforce cluster addr is set and use it for raft bootstrapping * Fix tests * fix http test panic * Pull in latest raft-snapshot library * Add comment
469 lines
13 KiB
Go
469 lines
13 KiB
Go
package vault
|
|
|
|
import (
|
|
"context"
|
|
"crypto/subtle"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync/atomic"
|
|
|
|
proto "github.com/golang/protobuf/proto"
|
|
"github.com/hashicorp/errwrap"
|
|
"github.com/hashicorp/vault/sdk/physical"
|
|
"github.com/hashicorp/vault/vault/seal"
|
|
)
|
|
|
|
// barrierTypeUpgradeCheck checks for backwards compat on barrier type, not
|
|
// applicable in the OSS side
|
|
var barrierTypeUpgradeCheck = func(_ string, _ *SealConfig) {}
|
|
|
|
// autoSeal is a Seal implementation that contains logic for encrypting and
|
|
// decrypting stored keys via an underlying AutoSealAccess implementation, as
|
|
// well as logic related to recovery keys and barrier config.
|
|
type autoSeal struct {
|
|
seal.Access
|
|
|
|
barrierConfig atomic.Value
|
|
recoveryConfig atomic.Value
|
|
core *Core
|
|
}
|
|
|
|
// Ensure we are implementing the Seal interface
|
|
var _ Seal = (*autoSeal)(nil)
|
|
|
|
func NewAutoSeal(lowLevel seal.Access) Seal {
|
|
ret := &autoSeal{
|
|
Access: lowLevel,
|
|
}
|
|
ret.barrierConfig.Store((*SealConfig)(nil))
|
|
ret.recoveryConfig.Store((*SealConfig)(nil))
|
|
return ret
|
|
}
|
|
|
|
func (d *autoSeal) GetAccess() seal.Access {
|
|
return d.Access
|
|
}
|
|
|
|
func (d *autoSeal) checkCore() error {
|
|
if d.core == nil {
|
|
return fmt.Errorf("seal does not have a core set")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *autoSeal) SetCore(core *Core) {
|
|
d.core = core
|
|
}
|
|
|
|
func (d *autoSeal) Init(ctx context.Context) error {
|
|
return d.Access.Init(ctx)
|
|
}
|
|
|
|
func (d *autoSeal) Finalize(ctx context.Context) error {
|
|
return d.Access.Finalize(ctx)
|
|
}
|
|
|
|
func (d *autoSeal) BarrierType() string {
|
|
return d.SealType()
|
|
}
|
|
|
|
func (d *autoSeal) StoredKeysSupported() bool {
|
|
return true
|
|
}
|
|
|
|
func (d *autoSeal) RecoveryKeySupported() bool {
|
|
return true
|
|
}
|
|
|
|
// SetStoredKeys uses the autoSeal.Access.Encrypts method to wrap the keys. The stored entry
|
|
// does not need to be seal wrapped in this case.
|
|
func (d *autoSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error {
|
|
if keys == nil {
|
|
return fmt.Errorf("keys were nil")
|
|
}
|
|
if len(keys) == 0 {
|
|
return fmt.Errorf("no keys provided")
|
|
}
|
|
|
|
buf, err := json.Marshal(keys)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to encode keys for storage: {{err}}", err)
|
|
}
|
|
|
|
// Encrypt and marshal the keys
|
|
blobInfo, err := d.Encrypt(ctx, buf)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to encrypt keys for storage: {{err}}", err)
|
|
}
|
|
|
|
value, err := proto.Marshal(blobInfo)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to marshal value for storage: {{err}}", err)
|
|
}
|
|
|
|
// Store the seal configuration.
|
|
pe := &physical.Entry{
|
|
Key: StoredBarrierKeysPath,
|
|
Value: value,
|
|
}
|
|
|
|
if err := d.core.physical.Put(ctx, pe); err != nil {
|
|
return errwrap.Wrapf("failed to write keys to storage: {{err}}", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetStoredKeys retrieves the key shares by unwrapping the encrypted key using the
|
|
// autoseal.
|
|
func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) {
|
|
pe, err := d.core.physical.Get(ctx, StoredBarrierKeysPath)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("failed to fetch stored keys: {{err}}", err)
|
|
}
|
|
|
|
// This is not strictly an error; we may not have any stored keys, for
|
|
// instance, if we're not initialized
|
|
if pe == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
blobInfo := &physical.EncryptedBlobInfo{}
|
|
if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
|
|
return nil, errwrap.Wrapf("failed to proto decode stored keys: {{err}}", err)
|
|
}
|
|
|
|
pt, err := d.Decrypt(ctx, blobInfo)
|
|
if err != nil {
|
|
return nil, 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 nil, fmt.Errorf("failed to decode stored keys: %v", err)
|
|
}
|
|
|
|
return keys, nil
|
|
}
|
|
|
|
func (d *autoSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
|
if d.barrierConfig.Load().(*SealConfig) != nil {
|
|
return d.barrierConfig.Load().(*SealConfig).Clone(), nil
|
|
}
|
|
|
|
if err := d.checkCore(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sealType := "barrier"
|
|
|
|
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)
|
|
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)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
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)
|
|
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)
|
|
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())
|
|
return nil, fmt.Errorf("barrier seal type of %q does not match loaded type of %q", conf.Type, d.BarrierType())
|
|
}
|
|
|
|
d.barrierConfig.Store(conf)
|
|
return conf.Clone(), nil
|
|
}
|
|
|
|
func (d *autoSeal) SetBarrierConfig(ctx context.Context, conf *SealConfig) error {
|
|
if err := d.checkCore(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if conf == nil {
|
|
d.barrierConfig.Store((*SealConfig)(nil))
|
|
return nil
|
|
}
|
|
|
|
conf.Type = d.BarrierType()
|
|
|
|
// Encode the seal configuration
|
|
buf, err := json.Marshal(conf)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to encode barrier seal configuration: {{err}}", err)
|
|
}
|
|
|
|
// Store the seal configuration
|
|
pe := &physical.Entry{
|
|
Key: barrierSealConfigPath,
|
|
Value: buf,
|
|
}
|
|
|
|
if err := d.core.physical.Put(ctx, pe); err != nil {
|
|
d.core.logger.Error("autoseal: failed to write barrier seal configuration", "error", err)
|
|
return errwrap.Wrapf("failed to write barrier seal configuration: {{err}}", err)
|
|
}
|
|
|
|
d.barrierConfig.Store(conf.Clone())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *autoSeal) SetCachedBarrierConfig(config *SealConfig) {
|
|
d.barrierConfig.Store(config)
|
|
}
|
|
|
|
func (d *autoSeal) RecoveryType() string {
|
|
return RecoveryTypeShamir
|
|
}
|
|
|
|
// RecoveryConfig returns the recovery config on recoverySealConfigPlaintextPath.
|
|
func (d *autoSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
|
|
if d.recoveryConfig.Load().(*SealConfig) != nil {
|
|
return d.recoveryConfig.Load().(*SealConfig).Clone(), nil
|
|
}
|
|
|
|
if err := d.checkCore(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sealType := "recovery"
|
|
|
|
var entry *physical.Entry
|
|
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)
|
|
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)
|
|
return nil, nil
|
|
}
|
|
|
|
// Check the old recovery seal config path so an upgraded standby will
|
|
// return the correct seal config
|
|
be, err := d.core.barrier.Get(ctx, recoverySealConfigPath)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("failed to read old recovery seal configuration: {{err}}", err)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// Reconstruct the physical entry
|
|
entry = &physical.Entry{
|
|
Key: be.Key,
|
|
Value: be.Value,
|
|
}
|
|
}
|
|
|
|
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)
|
|
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)
|
|
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())
|
|
return nil, fmt.Errorf("recovery seal type of %q does not match loaded type of %q", conf.Type, d.RecoveryType())
|
|
}
|
|
|
|
d.recoveryConfig.Store(conf)
|
|
return conf.Clone(), nil
|
|
}
|
|
|
|
// SetRecoveryConfig writes the recovery configuration to the physical storage
|
|
// and sets it as the seal's recoveryConfig.
|
|
func (d *autoSeal) SetRecoveryConfig(ctx context.Context, conf *SealConfig) error {
|
|
if err := d.checkCore(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Perform migration if applicable
|
|
if err := d.migrateRecoveryConfig(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if conf == nil {
|
|
d.recoveryConfig.Store((*SealConfig)(nil))
|
|
return nil
|
|
}
|
|
|
|
conf.Type = d.RecoveryType()
|
|
|
|
// Encode the seal configuration
|
|
buf, err := json.Marshal(conf)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to encode recovery seal configuration: {{err}}", err)
|
|
}
|
|
|
|
// Store the seal configuration directly in the physical storage
|
|
pe := &physical.Entry{
|
|
Key: recoverySealConfigPlaintextPath,
|
|
Value: buf,
|
|
}
|
|
|
|
if err := d.core.physical.Put(ctx, pe); err != nil {
|
|
d.core.logger.Error("autoseal: failed to write recovery seal configuration", "error", err)
|
|
return errwrap.Wrapf("failed to write recovery seal configuration: {{err}}", err)
|
|
}
|
|
|
|
d.recoveryConfig.Store(conf.Clone())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *autoSeal) SetCachedRecoveryConfig(config *SealConfig) {
|
|
d.recoveryConfig.Store(config)
|
|
}
|
|
|
|
func (d *autoSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error {
|
|
if key == nil {
|
|
return fmt.Errorf("recovery key to verify is nil")
|
|
}
|
|
|
|
pt, err := d.getRecoveryKeyInternal(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if subtle.ConstantTimeCompare(key, pt) != 1 {
|
|
return fmt.Errorf("recovery key does not match submitted values")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *autoSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
|
|
if err := d.checkCore(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if key == nil {
|
|
return fmt.Errorf("recovery key to store is nil")
|
|
}
|
|
|
|
// Encrypt and marshal the keys
|
|
blobInfo, err := d.Encrypt(ctx, key)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to encrypt keys for storage: {{err}}", err)
|
|
}
|
|
|
|
value, err := proto.Marshal(blobInfo)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to marshal value for storage: {{err}}", err)
|
|
}
|
|
|
|
be := &physical.Entry{
|
|
Key: recoveryKeyPath,
|
|
Value: value,
|
|
}
|
|
|
|
if err := d.core.physical.Put(ctx, be); err != nil {
|
|
d.core.logger.Error("autoseal: failed to write recovery key", "error", err)
|
|
return errwrap.Wrapf("failed to write recovery key: {{err}}", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *autoSeal) RecoveryKey(ctx context.Context) ([]byte, error) {
|
|
return d.getRecoveryKeyInternal(ctx)
|
|
}
|
|
|
|
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)
|
|
return nil, errwrap.Wrapf("failed to read recovery key: {{err}}", err)
|
|
}
|
|
if pe == nil {
|
|
d.core.logger.Warn("autoseal: no recovery key found")
|
|
return nil, fmt.Errorf("no recovery key found")
|
|
}
|
|
|
|
blobInfo := &physical.EncryptedBlobInfo{}
|
|
if err := proto.Unmarshal(pe.Value, blobInfo); err != nil {
|
|
return nil, errwrap.Wrapf("failed to proto decode stored keys: {{err}}", err)
|
|
}
|
|
|
|
pt, err := d.Decrypt(ctx, blobInfo)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf("failed to decrypt encrypted stored keys: {{err}}", err)
|
|
}
|
|
|
|
return pt, 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.
|
|
func (d *autoSeal) migrateRecoveryConfig(ctx context.Context) error {
|
|
// Get config from the old recoverySealConfigPath path
|
|
be, err := d.core.barrier.Get(ctx, recoverySealConfigPath)
|
|
if err != nil {
|
|
return errwrap.Wrapf("failed to read old recovery seal configuration during migration: {{err}}", err)
|
|
}
|
|
|
|
// If this entry is nil, then skip migration
|
|
if be == nil {
|
|
return nil
|
|
}
|
|
|
|
// 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")
|
|
|
|
// Perform migration
|
|
pe := &physical.Entry{
|
|
Key: recoverySealConfigPlaintextPath,
|
|
Value: be.Value,
|
|
}
|
|
|
|
if err := d.core.physical.Put(ctx, pe); err != nil {
|
|
return errwrap.Wrapf("failed to write recovery seal configuration during migration: {{err}}", err)
|
|
}
|
|
|
|
// Perform deletion of the old entry
|
|
if err := d.core.barrier.Delete(ctx, recoverySealConfigPath); err != nil {
|
|
return errwrap.Wrapf("failed to delete old recovery seal configuration during migration: {{err}}", err)
|
|
}
|
|
|
|
return nil
|
|
}
|