mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Add the ability to unseal using recovery keys via an explicit seal option. (#18683)
* wip * wip * Got it 'working', but not happy about cleanliness yet * Switch to a dedicated defaultSeal with recovery keys This is simpler than trying to hijack SealAccess as before. Instead, if the operator has requested recovery unseal mode (via a flag in the seal stanza), we new up a shamir seal with the recovery unseal key path instead of the auto seal. Then everything proceeds as if you had a shamir seal to begin with. * Handle recovery rekeying * changelog * Revert go.mod redirect * revert multi-blob info * Dumb nil unmarshal target * More comments * Update vault/seal.go Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com> * Update changelog/18683.txt Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com> * pr feedback * Fix recovery rekey, which needs to fetch root keys and restore them under the new recovery split * Better comment on recovery seal during adjustSealMigration * Make it possible to migrate from an auto-seal in recovery mode to shamir * Fix sealMigrated to account for a recovery seal * comments * Update changelog/18683.txt Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com> * Address PR feedback * Refactor duplicated migration code into helpers, using UnsealRecoveryKey/RecoveryKey where appropriate * Don't shortcut the reast of seal migration * get rid of redundant transit server cleanup Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>
This commit is contained in:
		| @@ -51,14 +51,15 @@ func (c *Sys) InitWithContext(ctx context.Context, opts *InitRequest) (*InitResp | |||||||
| } | } | ||||||
|  |  | ||||||
| type InitRequest struct { | type InitRequest struct { | ||||||
| 	SecretShares      int      `json:"secret_shares"` | 	SecretShares           int      `json:"secret_shares"` | ||||||
| 	SecretThreshold   int      `json:"secret_threshold"` | 	SecretThreshold        int      `json:"secret_threshold"` | ||||||
| 	StoredShares      int      `json:"stored_shares"` | 	StoredShares           int      `json:"stored_shares"` | ||||||
| 	PGPKeys           []string `json:"pgp_keys"` | 	PGPKeys                []string `json:"pgp_keys"` | ||||||
| 	RecoveryShares    int      `json:"recovery_shares"` | 	RecoveryShares         int      `json:"recovery_shares"` | ||||||
| 	RecoveryThreshold int      `json:"recovery_threshold"` | 	RecoveryThreshold      int      `json:"recovery_threshold"` | ||||||
| 	RecoveryPGPKeys   []string `json:"recovery_pgp_keys"` | 	RecoveryPGPKeys        []string `json:"recovery_pgp_keys"` | ||||||
| 	RootTokenPGPKey   string   `json:"root_token_pgp_key"` | 	RootTokenPGPKey        string   `json:"root_token_pgp_key"` | ||||||
|  | 	UnsealRecoveryDisabled bool     `json:"disable_unseal_recovery"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type InitStatusResponse struct { | type InitStatusResponse struct { | ||||||
| @@ -66,9 +67,10 @@ type InitStatusResponse struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type InitResponse struct { | type InitResponse struct { | ||||||
| 	Keys            []string `json:"keys"` | 	Keys                    []string `json:"keys"` | ||||||
| 	KeysB64         []string `json:"keys_base64"` | 	KeysB64                 []string `json:"keys_base64"` | ||||||
| 	RecoveryKeys    []string `json:"recovery_keys"` | 	RecoveryKeys            []string `json:"recovery_keys"` | ||||||
| 	RecoveryKeysB64 []string `json:"recovery_keys_base64"` | 	RecoveryKeysB64         []string `json:"recovery_keys_base64"` | ||||||
| 	RootToken       string   `json:"root_token"` | 	RootToken               string   `json:"root_token"` | ||||||
|  | 	UnsealRecoveryAvailable bool     `json:"unseal_recovery_available"` | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								changelog/18683.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								changelog/18683.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | ```release-note:change | ||||||
|  | core: recovery keys may now be used to unseal an auto-sealed Vault via an | ||||||
|  | opt-out unseal recovery mode. | ||||||
|  | ``` | ||||||
| @@ -29,10 +29,11 @@ type OperatorInitCommand struct { | |||||||
| 	flagRootTokenPGPKey string | 	flagRootTokenPGPKey string | ||||||
|  |  | ||||||
| 	// Auto Unseal | 	// Auto Unseal | ||||||
| 	flagRecoveryShares    int | 	flagRecoveryShares        int | ||||||
| 	flagRecoveryThreshold int | 	flagRecoveryThreshold     int | ||||||
| 	flagRecoveryPGPKeys   []string | 	flagRecoveryPGPKeys       []string | ||||||
| 	flagStoredShares      int | 	flagStoredShares          int | ||||||
|  | 	flagDisableUnsealRecovery bool | ||||||
|  |  | ||||||
| 	// Consul | 	// Consul | ||||||
| 	flagConsulAuto    bool | 	flagConsulAuto    bool | ||||||
| @@ -149,6 +150,13 @@ func (c *OperatorInitCommand) Flags() *FlagSets { | |||||||
| 		Usage:   "DEPRECATED: This flag does nothing. It will be removed in Vault 1.3.", | 		Usage:   "DEPRECATED: This flag does nothing. It will be removed in Vault 1.3.", | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | 	f.BoolVar(&BoolVar{ | ||||||
|  | 		Name:    "disable-unseal-recovery", | ||||||
|  | 		Target:  &c.flagDisableUnsealRecovery, | ||||||
|  | 		Default: false, | ||||||
|  | 		Usage:   "If disabled, unsealing Vault using recovery keys is not possible.", | ||||||
|  | 	}) | ||||||
|  |  | ||||||
| 	// Consul Options | 	// Consul Options | ||||||
| 	f = set.NewFlagSet("Consul Options") | 	f = set.NewFlagSet("Consul Options") | ||||||
|  |  | ||||||
| @@ -280,9 +288,10 @@ func (c *OperatorInitCommand) Run(args []string) int { | |||||||
| 		PGPKeys:         c.flagPGPKeys, | 		PGPKeys:         c.flagPGPKeys, | ||||||
| 		RootTokenPGPKey: c.flagRootTokenPGPKey, | 		RootTokenPGPKey: c.flagRootTokenPGPKey, | ||||||
|  |  | ||||||
| 		RecoveryShares:    c.flagRecoveryShares, | 		RecoveryShares:         c.flagRecoveryShares, | ||||||
| 		RecoveryThreshold: c.flagRecoveryThreshold, | 		RecoveryThreshold:      c.flagRecoveryThreshold, | ||||||
| 		RecoveryPGPKeys:   c.flagRecoveryPGPKeys, | 		RecoveryPGPKeys:        c.flagRecoveryPGPKeys, | ||||||
|  | 		UnsealRecoveryDisabled: c.flagDisableUnsealRecovery, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Check auto mode | 	// Check auto mode | ||||||
|   | |||||||
| @@ -2378,7 +2378,7 @@ func setSeal(c *ServerCommand, config *server.Config, infoKeys []string, info ma | |||||||
| 			config.Seals = append(config.Seals, &configutil.KMS{Type: wrapping.WrapperTypeShamir.String()}) | 			config.Seals = append(config.Seals, &configutil.KMS{Type: wrapping.WrapperTypeShamir.String()}) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	var createdSeals []vault.Seal = make([]vault.Seal, len(config.Seals)) | 	createdSeals := make([]vault.Seal, len(config.Seals)) | ||||||
| 	for _, configSeal := range config.Seals { | 	for _, configSeal := range config.Seals { | ||||||
| 		sealType := wrapping.WrapperTypeShamir.String() | 		sealType := wrapping.WrapperTypeShamir.String() | ||||||
| 		if !configSeal.Disabled && os.Getenv("VAULT_SEAL_TYPE") != "" { | 		if !configSeal.Disabled && os.Getenv("VAULT_SEAL_TYPE") != "" { | ||||||
| @@ -2403,7 +2403,11 @@ func setSeal(c *ServerCommand, config *server.Config, infoKeys []string, info ma | |||||||
| 					"Error parsing Seal configuration: %s", sealConfigError) | 					"Error parsing Seal configuration: %s", sealConfigError) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if wrapper == nil { | 		if configSeal.Recover { | ||||||
|  | 			seal = vault.NewRecoverySeal(&vaultseal.Access{ | ||||||
|  | 				Wrapper: aeadwrapper.NewShamirWrapper(), | ||||||
|  | 			}) | ||||||
|  | 		} else if wrapper == nil { | ||||||
| 			seal = defaultSeal | 			seal = defaultSeal | ||||||
| 		} else { | 		} else { | ||||||
| 			var err error | 			var err error | ||||||
| @@ -2428,6 +2432,7 @@ func setSeal(c *ServerCommand, config *server.Config, infoKeys []string, info ma | |||||||
| 		} | 		} | ||||||
| 		createdSeals = append(createdSeals, seal) | 		createdSeals = append(createdSeals, seal) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return barrierSeal, barrierWrapper, unwrapSeal, createdSeals, sealConfigError, nil | 	return barrierSeal, barrierWrapper, unwrapSeal, createdSeals, sealConfigError, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -2617,9 +2622,7 @@ func runUnseal(c *ServerCommand, core *vault.Core, ctx context.Context) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func createCoreConfig(c *ServerCommand, config *server.Config, backend physical.Backend, configSR sr.ServiceRegistration, barrierSeal, unwrapSeal vault.Seal, | func createCoreConfig(c *ServerCommand, config *server.Config, backend physical.Backend, configSR sr.ServiceRegistration, barrierSeal, unwrapSeal vault.Seal, metricsHelper *metricsutil.MetricsHelper, metricSink *metricsutil.ClusterMetricSink, secureRandomReader io.Reader) vault.CoreConfig { | ||||||
| 	metricsHelper *metricsutil.MetricsHelper, metricSink *metricsutil.ClusterMetricSink, secureRandomReader io.Reader, |  | ||||||
| ) vault.CoreConfig { |  | ||||||
| 	coreConfig := &vault.CoreConfig{ | 	coreConfig := &vault.CoreConfig{ | ||||||
| 		RawConfig:                      config, | 		RawConfig:                      config, | ||||||
| 		Physical:                       backend, | 		Physical:                       backend, | ||||||
|   | |||||||
| @@ -482,12 +482,16 @@ func CheckConfig(c *Config, e error) (*Config, error) { | |||||||
| 		return c, e | 		return c, e | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(c.Seals) == 2 { | 	switch len(c.Seals) { | ||||||
|  | 	case 2: | ||||||
|  | 		// Two seals indicates a seal migration, but one and only one must be disabled | ||||||
| 		switch { | 		switch { | ||||||
| 		case c.Seals[0].Disabled && c.Seals[1].Disabled: | 		case c.Seals[0].Disabled && c.Seals[1].Disabled: | ||||||
| 			return nil, errors.New("seals: two seals provided but both are disabled") | 			return nil, errors.New("seals: two seals provided but both are disabled") | ||||||
| 		case !c.Seals[0].Disabled && !c.Seals[1].Disabled: | 		case !c.Seals[0].Disabled && !c.Seals[1].Disabled: | ||||||
| 			return nil, errors.New("seals: two seals provided but neither is disabled") | 			return nil, errors.New("seals: two seals provided but neither is disabled") | ||||||
|  | 		case (!c.Seals[0].Disabled && c.Seals[0].Recover) || (!c.Seals[1].Disabled && c.Seals[1].Recover): | ||||||
|  | 			return nil, errors.New("seals: migration target seal cannot be in recovery mode") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -61,9 +61,10 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	recoveryConfig := &vault.SealConfig{ | 	recoveryConfig := &vault.SealConfig{ | ||||||
| 		SecretShares:    req.RecoveryShares, | 		SecretShares:          req.RecoveryShares, | ||||||
| 		SecretThreshold: req.RecoveryThreshold, | 		SecretThreshold:       req.RecoveryThreshold, | ||||||
| 		PGPKeys:         req.RecoveryPGPKeys, | 		PGPKeys:               req.RecoveryPGPKeys, | ||||||
|  | 		DisableUnsealRecovery: req.DisableUnsealRecovery, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	initParams := &vault.InitParams{ | 	initParams := &vault.InitParams{ | ||||||
| @@ -115,14 +116,15 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) | |||||||
| } | } | ||||||
|  |  | ||||||
| type InitRequest struct { | type InitRequest struct { | ||||||
| 	SecretShares      int      `json:"secret_shares"` | 	SecretShares          int      `json:"secret_shares"` | ||||||
| 	SecretThreshold   int      `json:"secret_threshold"` | 	SecretThreshold       int      `json:"secret_threshold"` | ||||||
| 	StoredShares      int      `json:"stored_shares"` | 	StoredShares          int      `json:"stored_shares"` | ||||||
| 	PGPKeys           []string `json:"pgp_keys"` | 	PGPKeys               []string `json:"pgp_keys"` | ||||||
| 	RecoveryShares    int      `json:"recovery_shares"` | 	RecoveryShares        int      `json:"recovery_shares"` | ||||||
| 	RecoveryThreshold int      `json:"recovery_threshold"` | 	RecoveryThreshold     int      `json:"recovery_threshold"` | ||||||
| 	RecoveryPGPKeys   []string `json:"recovery_pgp_keys"` | 	RecoveryPGPKeys       []string `json:"recovery_pgp_keys"` | ||||||
| 	RootTokenPGPKey   string   `json:"root_token_pgp_key"` | 	RootTokenPGPKey       string   `json:"root_token_pgp_key"` | ||||||
|  | 	DisableUnsealRecovery bool     `json:"disable_unseal_recovery"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type InitResponse struct { | type InitResponse struct { | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ type KMS struct { | |||||||
| 	Purpose []string `hcl:"-"` | 	Purpose []string `hcl:"-"` | ||||||
|  |  | ||||||
| 	Disabled bool | 	Disabled bool | ||||||
|  | 	Recover  bool | ||||||
| 	Config   map[string]string | 	Config   map[string]string | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -99,6 +100,15 @@ func parseKMS(result *[]*KMS, list *ast.ObjectList, blockName string, maxKMS int | |||||||
| 			delete(m, "disabled") | 			delete(m, "disabled") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		var recover bool | ||||||
|  | 		if v, ok := m["recovery_keys_fallback"]; ok { | ||||||
|  | 			recover, err = parseutil.ParseBool(v) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key)) | ||||||
|  | 			} | ||||||
|  | 			delete(m, "recovery_keys_fallback") | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		strMap := make(map[string]string, len(m)) | 		strMap := make(map[string]string, len(m)) | ||||||
| 		for k, v := range m { | 		for k, v := range m { | ||||||
| 			s, err := parseutil.ParseString(v) | 			s, err := parseutil.ParseString(v) | ||||||
| @@ -112,6 +122,7 @@ func parseKMS(result *[]*KMS, list *ast.ObjectList, blockName string, maxKMS int | |||||||
| 			Type:     strings.ToLower(key), | 			Type:     strings.ToLower(key), | ||||||
| 			Purpose:  purpose, | 			Purpose:  purpose, | ||||||
| 			Disabled: disabled, | 			Disabled: disabled, | ||||||
|  | 			Recover:  recover, | ||||||
| 		} | 		} | ||||||
| 		if len(strMap) > 0 { | 		if len(strMap) > 0 { | ||||||
| 			seal.Config = strMap | 			seal.Config = strMap | ||||||
|   | |||||||
							
								
								
									
										123
									
								
								vault/core.go
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								vault/core.go
									
									
									
									
									
								
							| @@ -1696,7 +1696,11 @@ func (c *Core) sealMigrated(ctx context.Context) (bool, error) { | |||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if c.seal.BarrierType() != c.migrationInfo.seal.BarrierType() { | 	// If the types of the seals differ, e.g. auto->shamir or shamir->auto, we're done.  BUT, | ||||||
|  | 	// with an auto seal in recovery mode as the migration seal, they will match even though | ||||||
|  | 	// the migration seal was really an auto seal | ||||||
|  | 	if c.seal.BarrierType() != c.migrationInfo.seal.BarrierType() || | ||||||
|  | 		(isAutoSeal(c.migrationInfo.seal) && c.seal.BarrierType() == wrapping.WrapperTypeShamir) { | ||||||
| 		return true, nil | 		return true, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -1748,20 +1752,34 @@ func (c *Core) migrateSeal(ctx context.Context) error { | |||||||
| 			return fmt.Errorf("error getting recovery key to set on new seal: %w", err) | 			return fmt.Errorf("error getting recovery key to set on new seal: %w", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err := c.seal.SetRecoveryKey(ctx, recoveryKey); err != nil { | 		if err := c.migrateAutoToAuto(ctx, recoveryKey); err != nil { | ||||||
| 			return fmt.Errorf("error setting new recovery key information during migrate: %w", err) | 			return err | ||||||
| 		} | 		} | ||||||
|  | 	case isUnsealRecoverySeal(c.migrationInfo.seal) && c.seal.RecoveryKeySupported(): | ||||||
|  | 		c.logger.Info("migrating from one auto-unseal to another", "from", | ||||||
|  | 			c.migrationInfo.seal.BarrierType(), "to", c.seal.BarrierType()) | ||||||
|  |  | ||||||
| 		barrierKeys, err := c.migrationInfo.seal.GetStoredKeys(ctx) | 		recoveryKey, err := c.migrationInfo.seal.UnsealRecoveryKey(ctx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("error getting stored keys to set on new seal: %w", err) | 			return fmt.Errorf("error getting recovery key to set on new seal: %w", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err := c.seal.SetStoredKeys(ctx, barrierKeys); err != nil { | 		if err := c.migrateAutoToAuto(ctx, recoveryKey); err != nil { | ||||||
| 			return fmt.Errorf("error setting new barrier key information during migrate: %w", err) | 			return err | ||||||
|  | 		} | ||||||
|  | 	case isUnsealRecoverySeal(c.migrationInfo.seal): | ||||||
|  | 		c.logger.Info("migrating from one auto-unseal to shamir", "from", c.migrationInfo.seal.BarrierType()) | ||||||
|  | 		// Auto to Shamir, since recovery key isn't supported on new seal | ||||||
|  |  | ||||||
|  | 		recoveryKey, err := c.migrationInfo.seal.UnsealRecoveryKey(ctx) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error getting recovery key to set on new seal: %w", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	case c.migrationInfo.seal.RecoveryKeySupported(): | 		if err := c.migrateAutoToShamir(ctx, recoveryKey); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	case isAutoSeal(c.migrationInfo.seal): | ||||||
| 		c.logger.Info("migrating from one auto-unseal to shamir", "from", c.migrationInfo.seal.BarrierType()) | 		c.logger.Info("migrating from one auto-unseal to shamir", "from", c.migrationInfo.seal.BarrierType()) | ||||||
| 		// Auto to Shamir, since recovery key isn't supported on new seal | 		// Auto to Shamir, since recovery key isn't supported on new seal | ||||||
|  |  | ||||||
| @@ -1770,21 +1788,9 @@ func (c *Core) migrateSeal(ctx context.Context) error { | |||||||
| 			return fmt.Errorf("error getting recovery key to set on new seal: %w", err) | 			return fmt.Errorf("error getting recovery key to set on new seal: %w", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// We have recovery keys; we're going to use them as the new shamir KeK. | 		if err := c.migrateAutoToShamir(ctx, recoveryKey); err != nil { | ||||||
| 		err = c.seal.GetAccess().Wrapper.(*aeadwrapper.ShamirWrapper).SetAesGcmKeyBytes(recoveryKey) | 			return err | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("failed to set master key in seal: %w", err) |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		barrierKeys, err := c.migrationInfo.seal.GetStoredKeys(ctx) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("error getting stored keys to set on new seal: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if err := c.seal.SetStoredKeys(ctx, barrierKeys); err != nil { |  | ||||||
| 			return fmt.Errorf("error setting new barrier key information during migrate: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 	case c.seal.RecoveryKeySupported(): | 	case c.seal.RecoveryKeySupported(): | ||||||
| 		c.logger.Info("migrating from shamir to auto-unseal", "to", c.seal.BarrierType()) | 		c.logger.Info("migrating from shamir to auto-unseal", "to", c.seal.BarrierType()) | ||||||
| 		// Migration is happening from shamir -> auto. In this case use the shamir | 		// Migration is happening from shamir -> auto. In this case use the shamir | ||||||
| @@ -1810,6 +1816,16 @@ func (c *Core) migrateSeal(ctx context.Context) error { | |||||||
| 			return fmt.Errorf("error storing new master key: %w", err) | 			return fmt.Errorf("error storing new master key: %w", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// Store the unseal recovery key | ||||||
|  | 		wrapper := aeadwrapper.NewShamirWrapper() | ||||||
|  | 		wrapper.SetAesGcmKeyBytes(c.migrationInfo.unsealKey) | ||||||
|  | 		recoverySeal := NewRecoverySeal(&vaultseal.Access{ | ||||||
|  | 			Wrapper: wrapper, | ||||||
|  | 		}) | ||||||
|  | 		recoverySeal.SetCore(c) | ||||||
|  | 		if err := recoverySeal.SetStoredKeys(ctx, [][]byte{newMasterKey}); err != nil { | ||||||
|  | 			c.logger.Error("failed to store recovery unseal keys", "error", err) | ||||||
|  | 		} | ||||||
| 	default: | 	default: | ||||||
| 		return errors.New("unhandled migration case (shamir to shamir)") | 		return errors.New("unhandled migration case (shamir to shamir)") | ||||||
| 	} | 	} | ||||||
| @@ -1826,6 +1842,40 @@ func (c *Core) migrateSeal(ctx context.Context) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Core) migrateAutoToAuto(ctx context.Context, recoveryKey []byte) error { | ||||||
|  | 	if err := c.seal.SetRecoveryKey(ctx, recoveryKey); err != nil { | ||||||
|  | 		return fmt.Errorf("error setting new recovery key information during migrate: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	barrierKeys, err := c.migrationInfo.seal.GetStoredKeys(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error getting stored keys to set on new seal: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := c.seal.SetStoredKeys(ctx, barrierKeys); err != nil { | ||||||
|  | 		return fmt.Errorf("error setting new barrier key information during migrate: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Core) migrateAutoToShamir(ctx context.Context, recoveryKey []byte) error { | ||||||
|  | 	// We have recovery keys; we're going to use them as the new shamir KeK. | ||||||
|  | 	err := c.seal.GetAccess().Wrapper.(*aeadwrapper.ShamirWrapper).SetAesGcmKeyBytes(recoveryKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to set master key in seal: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	barrierKeys, err := c.migrationInfo.seal.GetStoredKeys(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error getting stored keys to set on new seal: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := c.seal.SetStoredKeys(ctx, barrierKeys); err != nil { | ||||||
|  | 		return fmt.Errorf("error setting new barrier key information during migrate: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // unsealInternal takes in the master key and attempts to unseal the barrier. | // unsealInternal takes in the master key and attempts to unseal the barrier. | ||||||
| // N.B.: This must be called with the state write lock held. | // N.B.: This must be called with the state write lock held. | ||||||
| func (c *Core) unsealInternal(ctx context.Context, masterKey []byte) error { | func (c *Core) unsealInternal(ctx context.Context, masterKey []byte) error { | ||||||
| @@ -2755,6 +2805,12 @@ func (c *Core) adjustForSealMigration(unwrapSeal Seal) error { | |||||||
| 			// We have the same barrier type and the unwrap seal is nil so we're not | 			// We have the same barrier type and the unwrap seal is nil so we're not | ||||||
| 			// migrating from same to same, IOW we assume it's not a migration. | 			// migrating from same to same, IOW we assume it's not a migration. | ||||||
| 			return nil | 			return nil | ||||||
|  | 		case c.seal.BarrierType() == wrapping.WrapperTypeShamir && isUnsealRecoverySeal(c.seal): | ||||||
|  | 			// The stored barrier config is not shamir, but we have a shamir seal anyway, because | ||||||
|  | 			// seal recovery mode has been requested.  Note that this isn't for migration, but this function | ||||||
|  | 			// is called regardless of whether migration is occurring or not, and this is a valid state | ||||||
|  | 			// for the seal to be in, thus we mustn't reject it. | ||||||
|  | 			return nil | ||||||
| 		case c.seal.BarrierType() == wrapping.WrapperTypeShamir: | 		case c.seal.BarrierType() == wrapping.WrapperTypeShamir: | ||||||
| 			// The stored barrier config is not shamir, there is no disabled seal | 			// The stored barrier config is not shamir, there is no disabled seal | ||||||
| 			// in config, and either no configured seal (which equates to Shamir) | 			// in config, and either no configured seal (which equates to Shamir) | ||||||
| @@ -2778,7 +2834,7 @@ func (c *Core) adjustForSealMigration(unwrapSeal Seal) error { | |||||||
| 		// If we're not coming from Shamir we expect the previous seal to be | 		// If we're not coming from Shamir we expect the previous seal to be | ||||||
| 		// in the config and disabled. | 		// in the config and disabled. | ||||||
|  |  | ||||||
| 		if unwrapSeal.BarrierType() == wrapping.WrapperTypeShamir { | 		if unwrapSeal.BarrierType() == wrapping.WrapperTypeShamir && !isUnsealRecoverySeal(unwrapSeal) { | ||||||
| 			return errors.New("Shamir seals cannot be set disabled (they should simply not be set)") | 			return errors.New("Shamir seals cannot be set disabled (they should simply not be set)") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -2814,6 +2870,21 @@ func (c *Core) adjustForSealMigration(unwrapSeal Seal) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // With the addition of unseal recovery mode, we need a more subtle check for whether a Seal is an auto | ||||||
|  | // seal, as it may be a Shamir seal in due to recovery mode but would otherwise have been an auto seal. | ||||||
|  | func isAutoSeal(seal Seal) bool { | ||||||
|  | 	return seal.RecoveryKeySupported() || isUnsealRecoverySeal(seal) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Returns whether a seal is a recovery seal, e.g. a shamir seal pointing at the root key encrypted by | ||||||
|  | // recovery keys rather than the seal wrapper. | ||||||
|  | func isUnsealRecoverySeal(seal Seal) bool { | ||||||
|  | 	if ds, ok := seal.(*defaultSeal); ok { | ||||||
|  | 		return ds.unsealKeyPath == recoveryUnsealKeyPath | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Core) migrateSealConfig(ctx context.Context) error { | func (c *Core) migrateSealConfig(ctx context.Context) error { | ||||||
| 	existBarrierSealConfig, existRecoverySealConfig, err := c.PhysicalSealConfigs(ctx) | 	existBarrierSealConfig, existRecoverySealConfig, err := c.PhysicalSealConfigs(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -2823,10 +2894,10 @@ func (c *Core) migrateSealConfig(ctx context.Context) error { | |||||||
| 	var bc, rc *SealConfig | 	var bc, rc *SealConfig | ||||||
|  |  | ||||||
| 	switch { | 	switch { | ||||||
| 	case c.migrationInfo.seal.RecoveryKeySupported() && c.seal.RecoveryKeySupported(): | 	case isAutoSeal(c.migrationInfo.seal) && c.seal.RecoveryKeySupported(): | ||||||
| 		// Migrating from auto->auto, copy the configs over | 		// Migrating from auto->auto, copy the configs over | ||||||
| 		bc, rc = existBarrierSealConfig, existRecoverySealConfig | 		bc, rc = existBarrierSealConfig, existRecoverySealConfig | ||||||
| 	case c.migrationInfo.seal.RecoveryKeySupported(): | 	case isAutoSeal(c.migrationInfo.seal): | ||||||
| 		// Migrating from auto->shamir, clone auto's recovery config and set | 		// Migrating from auto->shamir, clone auto's recovery config and set | ||||||
| 		// stored keys to 1. | 		// stored keys to 1. | ||||||
| 		bc = existRecoverySealConfig.Clone() | 		bc = existRecoverySealConfig.Clone() | ||||||
| @@ -2863,7 +2934,7 @@ func (c *Core) migrateSealConfig(ctx context.Context) error { | |||||||
|  |  | ||||||
| func (c *Core) adjustSealConfigDuringMigration(existBarrierSealConfig, existRecoverySealConfig *SealConfig) { | func (c *Core) adjustSealConfigDuringMigration(existBarrierSealConfig, existRecoverySealConfig *SealConfig) { | ||||||
| 	switch { | 	switch { | ||||||
| 	case c.migrationInfo.seal.RecoveryKeySupported() && existRecoverySealConfig != nil: | 	case isAutoSeal(c.migrationInfo.seal) && existRecoverySealConfig != nil: | ||||||
| 		// Migrating from auto->shamir, clone auto's recovery config and set | 		// Migrating from auto->shamir, clone auto's recovery config and set | ||||||
| 		// stored keys to 1.  Unless the recover config doesn't exist, in which | 		// stored keys to 1.  Unless the recover config doesn't exist, in which | ||||||
| 		// case the migration is assumed to already have been performed. | 		// case the migration is assumed to already have been performed. | ||||||
|   | |||||||
| @@ -59,6 +59,11 @@ func TestSealMigration_TransitToShamir_Post14(t *testing.T) { | |||||||
| 	testVariousBackends(t, ParamTestSealMigrationTransitToShamir_Post14, BasePort_TransitToShamir_Post14, true) | 	testVariousBackends(t, ParamTestSealMigrationTransitToShamir_Post14, BasePort_TransitToShamir_Post14, true) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestSealMigration_TransitToShamir_Recovery(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  | 	testVariousBackends(t, ParamTestSealMigrationTransitToShamir_Recovery, BasePort_TransitToShamir_Recovery, true) | ||||||
|  | } | ||||||
|  |  | ||||||
| // TestSealMigration_TransitToTransit tests transit-to-shamir seal | // TestSealMigration_TransitToTransit tests transit-to-shamir seal | ||||||
| // migration, using the post-1.4 method of bring individual nodes in the | // migration, using the post-1.4 method of bring individual nodes in the | ||||||
| // cluster to do the migration. | // cluster to do the migration. | ||||||
|   | |||||||
| @@ -7,6 +7,9 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2" | ||||||
|  | 	"github.com/hashicorp/vault/vault/seal" | ||||||
|  |  | ||||||
| 	"github.com/go-test/deep" | 	"github.com/go-test/deep" | ||||||
| 	"github.com/hashicorp/go-hclog" | 	"github.com/hashicorp/go-hclog" | ||||||
| 	wrapping "github.com/hashicorp/go-kms-wrapping/v2" | 	wrapping "github.com/hashicorp/go-kms-wrapping/v2" | ||||||
| @@ -25,11 +28,12 @@ const ( | |||||||
| 	keyShares    = 3 | 	keyShares    = 3 | ||||||
| 	keyThreshold = 3 | 	keyThreshold = 3 | ||||||
|  |  | ||||||
| 	BasePort_ShamirToTransit_Pre14  = 20000 | 	BasePort_ShamirToTransit_Pre14    = 20000 | ||||||
| 	BasePort_TransitToShamir_Pre14  = 21000 | 	BasePort_TransitToShamir_Pre14    = 21000 | ||||||
| 	BasePort_ShamirToTransit_Post14 = 22000 | 	BasePort_ShamirToTransit_Post14   = 22000 | ||||||
| 	BasePort_TransitToShamir_Post14 = 23000 | 	BasePort_TransitToShamir_Post14   = 23000 | ||||||
| 	BasePort_TransitToTransit       = 24000 | 	BasePort_TransitToTransit         = 24000 | ||||||
|  | 	BasePort_TransitToShamir_Recovery = 25000 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func ParamTestSealMigrationTransitToShamir_Pre14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) { | func ParamTestSealMigrationTransitToShamir_Pre14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) { | ||||||
| @@ -120,6 +124,55 @@ func ParamTestSealMigrationTransitToShamir_Post14(t *testing.T, logger hclog.Log | |||||||
| 	runShamir(t, logger, storage, basePort, rootToken, recoveryKeys) | 	runShamir(t, logger, storage, basePort, rootToken, recoveryKeys) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func ParamTestSealMigrationTransitToShamir_Recovery(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) { | ||||||
|  | 	// Create the transit server. | ||||||
|  | 	tss := sealhelper.NewTransitSealServer(t, 0) | ||||||
|  | 	defer func() { | ||||||
|  | 		if tss != nil { | ||||||
|  | 			tss.Cleanup() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	sealKeyName := "transit-seal-key-1" | ||||||
|  | 	tss.MakeKey(t, sealKeyName) | ||||||
|  |  | ||||||
|  | 	// Initialize the backend with transit. | ||||||
|  | 	cluster, opts := InitializeTransit(t, logger, storage, basePort, tss, sealKeyName) | ||||||
|  | 	rootToken, recoveryKeys := cluster.RootToken, cluster.RecoveryKeys | ||||||
|  |  | ||||||
|  | 	// Disable the transit seal, forcing recovery | ||||||
|  | 	tss.Cleanup() | ||||||
|  | 	tss = nil | ||||||
|  |  | ||||||
|  | 	// conf := cluster.Cores[0].GetCoreConfigInternal() | ||||||
|  | 	// conf.Seals[0].Recover = true | ||||||
|  |  | ||||||
|  | 	opts.UnwrapSealFunc = func() vault.Seal { | ||||||
|  | 		seal := vault.NewRecoverySeal(&seal.Access{ | ||||||
|  | 			Wrapper:     aead.NewShamirWrapper(), | ||||||
|  | 			WrapperType: wrapping.WrapperTypeShamir, | ||||||
|  | 		}) | ||||||
|  | 		seal.SetCachedBarrierConfig(&vault.SealConfig{ | ||||||
|  | 			SecretShares:    len(recoveryKeys), | ||||||
|  | 			SecretThreshold: len(recoveryKeys), | ||||||
|  | 			StoredShares:    len(recoveryKeys), | ||||||
|  | 		}) | ||||||
|  | 		return seal | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Migrate the backend from transit to shamir | ||||||
|  | 	// opts.UnwrapSealFunc = opts.SealFunc | ||||||
|  | 	opts.SealFunc = func() vault.Seal { return nil } | ||||||
|  | 	leaderIdx := migratePost14(t, storage, cluster, opts, cluster.RecoveryKeys) | ||||||
|  | 	validateMigration(t, storage, cluster, leaderIdx, verifySealConfigShamir) | ||||||
|  |  | ||||||
|  | 	cluster.Cleanup() | ||||||
|  | 	storage.Cleanup(t, cluster) | ||||||
|  |  | ||||||
|  | 	// Run the backend with shamir.  Note that the recovery keys are now the | ||||||
|  | 	// barrier keys. | ||||||
|  | 	runShamir(t, logger, storage, basePort, rootToken, recoveryKeys) | ||||||
|  | } | ||||||
|  |  | ||||||
| func ParamTestSealMigrationShamirToTransit_Post14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) { | func ParamTestSealMigrationShamirToTransit_Post14(t *testing.T, logger hclog.Logger, storage teststorage.ReusableStorage, basePort int) { | ||||||
| 	// Initialize the backend using shamir | 	// Initialize the backend using shamir | ||||||
| 	cluster, opts := initializeShamir(t, logger, storage, basePort) | 	cluster, opts := initializeShamir(t, logger, storage, basePort) | ||||||
|   | |||||||
| @@ -386,6 +386,18 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			results.RecoveryShares = recoveryUnsealKeys | 			results.RecoveryShares = recoveryUnsealKeys | ||||||
|  |  | ||||||
|  | 			if !initParams.RecoveryConfig.DisableUnsealRecovery { | ||||||
|  | 				wrapper := aeadwrapper.NewShamirWrapper() | ||||||
|  | 				wrapper.SetAesGcmKeyBytes(recoveryKey) | ||||||
|  | 				recoverySeal := NewRecoverySeal(&seal.Access{ | ||||||
|  | 					Wrapper: wrapper, | ||||||
|  | 				}) | ||||||
|  | 				recoverySeal.SetCore(c) | ||||||
|  | 				if err := recoverySeal.SetStoredKeys(ctx, [][]byte{barrierKey}); err != nil { | ||||||
|  | 					c.logger.Error("failed to store recovery unseal keys", "error", err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -252,7 +252,9 @@ func (c *Core) RecoveryRekeyInit(config *SealConfig) logical.HTTPCodedError { | |||||||
| 		c.logger.Error("invalid recovery configuration", "error", err) | 		c.logger.Error("invalid recovery configuration", "error", err) | ||||||
| 		return logical.CodedError(http.StatusInternalServerError, fmt.Errorf("invalid recovery configuration: %w", err).Error()) | 		return logical.CodedError(http.StatusInternalServerError, fmt.Errorf("invalid recovery configuration: %w", err).Error()) | ||||||
| 	} | 	} | ||||||
|  | 	if isUnsealRecoverySeal(c.seal) { | ||||||
|  | 		return logical.CodedError(http.StatusBadRequest, "cannot rekey while in unseal recovery mode") | ||||||
|  | 	} | ||||||
| 	if !c.seal.RecoveryKeySupported() { | 	if !c.seal.RecoveryKeySupported() { | ||||||
| 		return logical.CodedError(http.StatusBadRequest, "recovery keys not supported") | 		return logical.CodedError(http.StatusBadRequest, "recovery keys not supported") | ||||||
| 	} | 	} | ||||||
| @@ -757,6 +759,31 @@ func (c *Core) RecoveryRekeyUpdate(ctx context.Context, key []byte, nonce string | |||||||
| 		return nil, logical.CodedError(http.StatusInternalServerError, fmt.Errorf("failed to perform recovery rekey: %w", err).Error()) | 		return nil, logical.CodedError(http.StatusInternalServerError, fmt.Errorf("failed to perform recovery rekey: %w", err).Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Store the unseal recovery key | ||||||
|  | 	wrapper := aeadwrapper.NewShamirWrapper() | ||||||
|  | 	// TODO, just test if keys existed in the recovery path | ||||||
|  |  | ||||||
|  | 	keys, err := c.physical.Get(ctx, recoveryUnsealKeyPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, logical.CodedError(http.StatusInternalServerError, fmt.Errorf("failed to perform recovery rekey, failed testing for recovery unseal keys: %w", err).Error()) | ||||||
|  | 	} | ||||||
|  | 	if keys != nil { | ||||||
|  | 		rootKeys, err := c.seal.GetStoredKeys(ctx) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, logical.CodedError(http.StatusInternalServerError, fmt.Errorf("failed to perform recovery rekey, failed retrieving root keys: %w", err).Error()) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		wrapper.SetAesGcmKeyBytes(newRecoveryKey) | ||||||
|  | 		recoverySeal := NewRecoverySeal(&seal.Access{ | ||||||
|  | 			Wrapper: wrapper, | ||||||
|  | 		}) | ||||||
|  | 		recoverySeal.SetCore(c) | ||||||
|  |  | ||||||
|  | 		if err := recoverySeal.SetStoredKeys(ctx, rootKeys); err != nil { | ||||||
|  | 			c.logger.Error("failed to store recovery unseal keys", "error", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	c.recoveryRekeyConfig = nil | 	c.recoveryRekeyConfig = nil | ||||||
| 	return results, nil | 	return results, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,12 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2" | ||||||
|  |  | ||||||
|  | 	log "github.com/hashicorp/go-hclog" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/go-multierror" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/jsonutil" | 	"github.com/hashicorp/vault/sdk/helper/jsonutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/physical" | 	"github.com/hashicorp/vault/sdk/physical" | ||||||
|  |  | ||||||
| @@ -40,6 +46,12 @@ const ( | |||||||
| 	// recoveryKeyPath is the path to the recovery key | 	// recoveryKeyPath is the path to the recovery key | ||||||
| 	recoveryKeyPath = "core/recovery-key" | 	recoveryKeyPath = "core/recovery-key" | ||||||
|  |  | ||||||
|  | 	// recoveryUnsealKeyPath is the path to a copy of the root key, | ||||||
|  | 	// encrypted using the shamir-combined recovery keys, just like | ||||||
|  | 	// StoredBarrierKeysPath is the root key encrypted by the seal | ||||||
|  | 	// (which in the case of a shamir seal is the shamir-combined unseal keys.) | ||||||
|  | 	recoveryUnsealKeyPath = "core/recovery-unseal-key" | ||||||
|  |  | ||||||
| 	// StoredBarrierKeysPath is the path used for storing HSM-encrypted unseal keys | 	// StoredBarrierKeysPath is the path used for storing HSM-encrypted unseal keys | ||||||
| 	StoredBarrierKeysPath = "core/hsm/barrier-unseal-keys" | 	StoredBarrierKeysPath = "core/hsm/barrier-unseal-keys" | ||||||
|  |  | ||||||
| @@ -68,6 +80,7 @@ type Seal interface { | |||||||
| 	RecoveryType() string | 	RecoveryType() string | ||||||
| 	RecoveryConfig(context.Context) (*SealConfig, error) | 	RecoveryConfig(context.Context) (*SealConfig, error) | ||||||
| 	RecoveryKey(context.Context) ([]byte, error) | 	RecoveryKey(context.Context) ([]byte, error) | ||||||
|  | 	UnsealRecoveryKey(ctx context.Context) ([]byte, error) | ||||||
| 	SetRecoveryConfig(context.Context, *SealConfig) error | 	SetRecoveryConfig(context.Context, *SealConfig) error | ||||||
| 	SetCachedRecoveryConfig(*SealConfig) | 	SetCachedRecoveryConfig(*SealConfig) | ||||||
| 	SetRecoveryKey(context.Context, []byte) error | 	SetRecoveryKey(context.Context, []byte) error | ||||||
| @@ -76,14 +89,26 @@ type Seal interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| type defaultSeal struct { | type defaultSeal struct { | ||||||
| 	access *seal.Access | 	access        *seal.Access | ||||||
| 	config atomic.Value | 	config        atomic.Value | ||||||
| 	core   *Core | 	logger        log.Logger | ||||||
|  | 	core          *Core | ||||||
|  | 	unsealKeyPath string | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewDefaultSeal(lowLevel *seal.Access) Seal { | func NewDefaultSeal(lowLevel *seal.Access) Seal { | ||||||
| 	ret := &defaultSeal{ | 	ret := &defaultSeal{ | ||||||
| 		access: lowLevel, | 		access:        lowLevel, | ||||||
|  | 		unsealKeyPath: StoredBarrierKeysPath, | ||||||
|  | 	} | ||||||
|  | 	ret.config.Store((*SealConfig)(nil)) | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewRecoverySeal(lowLevel *seal.Access) Seal { | ||||||
|  | 	ret := &defaultSeal{ | ||||||
|  | 		access:        lowLevel, | ||||||
|  | 		unsealKeyPath: recoveryUnsealKeyPath, | ||||||
| 	} | 	} | ||||||
| 	ret.config.Store((*SealConfig)(nil)) | 	ret.config.Store((*SealConfig)(nil)) | ||||||
| 	return ret | 	return ret | ||||||
| @@ -110,6 +135,14 @@ func (d *defaultSeal) SetAccess(access *seal.Access) { | |||||||
|  |  | ||||||
| func (d *defaultSeal) SetCore(core *Core) { | func (d *defaultSeal) SetCore(core *Core) { | ||||||
| 	d.core = core | 	d.core = core | ||||||
|  | 	if d.logger == nil { | ||||||
|  | 		if isUnsealRecoverySeal(d) { | ||||||
|  | 			d.logger = d.core.Logger().Named("recoveryseal") | ||||||
|  | 		} else { | ||||||
|  | 			d.logger = d.core.Logger().Named("defaultseal") | ||||||
|  | 		} | ||||||
|  | 		d.core.AddLogger(d.logger) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *defaultSeal) Init(ctx context.Context) error { | func (d *defaultSeal) Init(ctx context.Context) error { | ||||||
| @@ -141,7 +174,7 @@ func (d *defaultSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error { | |||||||
| 	if d.LegacySeal() { | 	if d.LegacySeal() { | ||||||
| 		return fmt.Errorf("stored keys are not supported") | 		return fmt.Errorf("stored keys are not supported") | ||||||
| 	} | 	} | ||||||
| 	return writeStoredKeys(ctx, d.core.physical, d.access, keys) | 	return writeStoredKeys(ctx, d.core.physical, d.access, keys, d.unsealKeyPath) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *defaultSeal) LegacySeal() bool { | func (d *defaultSeal) LegacySeal() bool { | ||||||
| @@ -156,7 +189,7 @@ func (d *defaultSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) { | |||||||
| 	if d.LegacySeal() { | 	if d.LegacySeal() { | ||||||
| 		return nil, fmt.Errorf("stored keys are not supported") | 		return nil, fmt.Errorf("stored keys are not supported") | ||||||
| 	} | 	} | ||||||
| 	keys, err := readStoredKeys(ctx, d.core.physical, d.access) | 	keys, err := readStoredKeys(ctx, d.core.physical, d.access, d.unsealKeyPath) | ||||||
| 	return keys, err | 	return keys, err | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -197,8 +230,10 @@ func (d *defaultSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) { | |||||||
| 		conf.Type = d.BarrierType().String() | 		conf.Type = d.BarrierType().String() | ||||||
| 	case d.BarrierType().String(): | 	case d.BarrierType().String(): | ||||||
| 	default: | 	default: | ||||||
| 		d.core.logger.Error("barrier seal type does not match expected type", "barrier_seal_type", conf.Type, "loaded_seal_type", d.BarrierType()) | 		if conf.Type == wrapping.WrapperTypeShamir.String() || d.unsealKeyPath != recoveryUnsealKeyPath { | ||||||
| 		return nil, fmt.Errorf("barrier seal type of %q does not match expected type of %q", conf.Type, d.BarrierType()) | 			d.core.logger.Error("barrier seal type does not match expected type", "barrier_seal_type", conf.Type, "loaded_seal_type", d.BarrierType()) | ||||||
|  | 			return nil, fmt.Errorf("barrier seal type of %q does not match expected type of %q", conf.Type, d.BarrierType()) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Check for a valid seal configuration | 	// Check for a valid seal configuration | ||||||
| @@ -266,6 +301,10 @@ func (d *defaultSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) { | |||||||
| 	return nil, fmt.Errorf("recovery not supported") | 	return nil, fmt.Errorf("recovery not supported") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d *defaultSeal) UnsealRecoveryKey(ctx context.Context) ([]byte, error) { | ||||||
|  | 	return d.access.Wrapper.(*aead.ShamirWrapper).KeyBytes(ctx) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (d *defaultSeal) RecoveryKey(ctx context.Context) ([]byte, error) { | func (d *defaultSeal) RecoveryKey(ctx context.Context) ([]byte, error) { | ||||||
| 	return nil, fmt.Errorf("recovery not supported") | 	return nil, fmt.Errorf("recovery not supported") | ||||||
| } | } | ||||||
| @@ -281,6 +320,10 @@ func (d *defaultSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error { | |||||||
| 	return fmt.Errorf("recovery not supported") | 	return fmt.Errorf("recovery not supported") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d *defaultSeal) VerifyRecoveryUnsealKey(ctx context.Context, key []byte) error { | ||||||
|  | 	return fmt.Errorf("recovery unseal not supported") | ||||||
|  | } | ||||||
|  |  | ||||||
| func (d *defaultSeal) SetRecoveryKey(ctx context.Context, key []byte) error { | func (d *defaultSeal) SetRecoveryKey(ctx context.Context, key []byte) error { | ||||||
| 	return fmt.Errorf("recovery not supported") | 	return fmt.Errorf("recovery not supported") | ||||||
| } | } | ||||||
| @@ -316,6 +359,9 @@ type SealConfig struct { | |||||||
| 	// How many keys to store, for seals that support storage.  Always 0 or 1. | 	// How many keys to store, for seals that support storage.  Always 0 or 1. | ||||||
| 	StoredShares int `json:"stored_shares" mapstructure:"stored_shares"` | 	StoredShares int `json:"stored_shares" mapstructure:"stored_shares"` | ||||||
|  |  | ||||||
|  | 	// Whether unseal using recovery keys should be disabled | ||||||
|  | 	DisableUnsealRecovery bool `json:"disable_unseal_recovery" mapstructure:"disable_unseal_recovery"` | ||||||
|  |  | ||||||
| 	// Stores the progress of the rekey operation (key shares) | 	// Stores the progress of the rekey operation (key shares) | ||||||
| 	RekeyProgress [][]byte `json:"-"` | 	RekeyProgress [][]byte `json:"-"` | ||||||
|  |  | ||||||
| @@ -429,7 +475,7 @@ func (e *ErrDecrypt) Is(target error) bool { | |||||||
| 	return ok || errors.Is(e.Err, target) | 	return ok || errors.Is(e.Err, target) | ||||||
| } | } | ||||||
|  |  | ||||||
| func writeStoredKeys(ctx context.Context, storage physical.Backend, encryptor *seal.Access, keys [][]byte) error { | func writeStoredKeys(ctx context.Context, storage physical.Backend, encryptor *seal.Access, keys [][]byte, path string) error { | ||||||
| 	if keys == nil { | 	if keys == nil { | ||||||
| 		return fmt.Errorf("keys were nil") | 		return fmt.Errorf("keys were nil") | ||||||
| 	} | 	} | ||||||
| @@ -455,7 +501,7 @@ func writeStoredKeys(ctx context.Context, storage physical.Backend, encryptor *s | |||||||
|  |  | ||||||
| 	// Store the seal configuration. | 	// Store the seal configuration. | ||||||
| 	pe := &physical.Entry{ | 	pe := &physical.Entry{ | ||||||
| 		Key:   StoredBarrierKeysPath, | 		Key:   path, | ||||||
| 		Value: value, | 		Value: value, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -466,8 +512,8 @@ func writeStoredKeys(ctx context.Context, storage physical.Backend, encryptor *s | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func readStoredKeys(ctx context.Context, storage physical.Backend, encryptor *seal.Access) ([][]byte, error) { | func readStoredKeys(ctx context.Context, storage physical.Backend, encryptor *seal.Access, path string) ([][]byte, error) { | ||||||
| 	pe, err := storage.Get(ctx, StoredBarrierKeysPath) | 	pe, err := storage.Get(ctx, path) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to fetch stored keys: %w", err) | 		return nil, fmt.Errorf("failed to fetch stored keys: %w", err) | ||||||
| 	} | 	} | ||||||
| @@ -478,17 +524,22 @@ func readStoredKeys(ctx context.Context, storage physical.Backend, encryptor *se | |||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	blobInfo := &wrapping.BlobInfo{} | 	var blobInfo wrapping.BlobInfo | ||||||
| 	if err := proto.Unmarshal(pe.Value, blobInfo); err != nil { | 	// Read as a multi-blob first | ||||||
|  | 	if err := proto.Unmarshal(pe.Value, &blobInfo); err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to proto decode stored keys: %w", err) | 		return nil, fmt.Errorf("failed to proto decode stored keys: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pt, err := encryptor.Decrypt(ctx, blobInfo, nil) | 	pt, err := encryptor.Decrypt(ctx, &blobInfo, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if strings.Contains(err.Error(), "message authentication failed") { | 		if strings.Contains(err.Error(), "message authentication failed") { | ||||||
| 			return nil, &ErrInvalidKey{Reason: fmt.Sprintf("failed to decrypt keys from storage: %v", err)} | 			err = multierror.Append(err, &ErrInvalidKey{Reason: fmt.Sprintf("failed to decrypt keys from storage: %v", err)}) | ||||||
|  | 		} else { | ||||||
|  | 			err = multierror.Append(err, &ErrDecrypt{Err: fmt.Errorf("failed to decrypt keys from storage: %w", err)}) | ||||||
| 		} | 		} | ||||||
| 		return nil, &ErrDecrypt{Err: fmt.Errorf("failed to decrypt keys from storage: %w", err)} | 	} | ||||||
|  | 	if len(pt) == 0 { | ||||||
|  | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Decode the barrier entry | 	// Decode the barrier entry | ||||||
|   | |||||||
| @@ -108,13 +108,13 @@ func (d *autoSeal) RecoveryKeySupported() bool { | |||||||
| // SetStoredKeys uses the autoSeal.Access.Encrypts method to wrap the keys. The stored entry | // SetStoredKeys uses the autoSeal.Access.Encrypts method to wrap the keys. The stored entry | ||||||
| // does not need to be seal wrapped in this case. | // does not need to be seal wrapped in this case. | ||||||
| func (d *autoSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error { | func (d *autoSeal) SetStoredKeys(ctx context.Context, keys [][]byte) error { | ||||||
| 	return writeStoredKeys(ctx, d.core.physical, d.Access, keys) | 	return writeStoredKeys(ctx, d.core.physical, d.Access, keys, StoredBarrierKeysPath) | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetStoredKeys retrieves the key shares by unwrapping the encrypted key using the | // GetStoredKeys retrieves the key shares by unwrapping the encrypted key using the | ||||||
| // autoseal. | // autoseal. | ||||||
| func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) { | func (d *autoSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) { | ||||||
| 	return readStoredKeys(ctx, d.core.physical, d.Access) | 	return readStoredKeys(ctx, d.core.physical, d.Access, StoredBarrierKeysPath) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error { | func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error { | ||||||
| @@ -435,14 +435,22 @@ func (d *autoSeal) RecoveryKey(ctx context.Context) ([]byte, error) { | |||||||
| 	return d.getRecoveryKeyInternal(ctx) | 	return d.getRecoveryKeyInternal(ctx) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d *autoSeal) UnsealRecoveryKey(ctx context.Context) ([]byte, error) { | ||||||
|  | 	return nil, fmt.Errorf("unseal recovery not supported") | ||||||
|  | } | ||||||
|  |  | ||||||
| func (d *autoSeal) getRecoveryKeyInternal(ctx context.Context) ([]byte, error) { | func (d *autoSeal) getRecoveryKeyInternal(ctx context.Context) ([]byte, error) { | ||||||
| 	pe, err := d.core.physical.Get(ctx, recoveryKeyPath) | 	return getRecoveryKeyInternal(ctx, d.core.physical, d.logger, d.Access) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getRecoveryKeyInternal(ctx context.Context, storage physical.Backend, logger log.Logger, access *seal.Access) ([]byte, error) { | ||||||
|  | 	pe, err := storage.Get(ctx, recoveryKeyPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		d.logger.Error("failed to read recovery key", "error", err) | 		logger.Error("failed to read recovery key", "error", err) | ||||||
| 		return nil, fmt.Errorf("failed to read recovery key: %w", err) | 		return nil, fmt.Errorf("failed to read recovery key: %w", err) | ||||||
| 	} | 	} | ||||||
| 	if pe == nil { | 	if pe == nil { | ||||||
| 		d.logger.Warn("no recovery key found") | 		logger.Warn("no recovery key found") | ||||||
| 		return nil, fmt.Errorf("no recovery key found") | 		return nil, fmt.Errorf("no recovery key found") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -451,7 +459,7 @@ func (d *autoSeal) getRecoveryKeyInternal(ctx context.Context) ([]byte, error) { | |||||||
| 		return nil, fmt.Errorf("failed to proto decode stored keys: %w", err) | 		return nil, fmt.Errorf("failed to proto decode stored keys: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pt, err := d.Decrypt(ctx, blobInfo, nil) | 	pt, err := access.Decrypt(ctx, blobInfo, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to decrypt encrypted stored keys: %w", err) | 		return nil, fmt.Errorf("failed to decrypt encrypted stored keys: %w", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Scott Miller
					Scott Miller