diff --git a/api/sys_seal.go b/api/sys_seal.go index 07d236b77e..75b9ebd314 100644 --- a/api/sys_seal.go +++ b/api/sys_seal.go @@ -96,25 +96,26 @@ func sealStatusRequestWithContext(ctx context.Context, c *Sys, r *Request) (*Sea } type SealStatusResponse struct { - Type string `json:"type"` - Initialized bool `json:"initialized"` - Sealed bool `json:"sealed"` - T int `json:"t"` - N int `json:"n"` - Progress int `json:"progress"` - Nonce string `json:"nonce"` - Version string `json:"version"` - BuildDate string `json:"build_date"` - Migration bool `json:"migration"` - ClusterName string `json:"cluster_name,omitempty"` - ClusterID string `json:"cluster_id,omitempty"` - RecoverySeal bool `json:"recovery_seal"` - RecoverySealType string `json:"recovery_seal_type,omitempty"` - StorageType string `json:"storage_type,omitempty"` - HCPLinkStatus string `json:"hcp_link_status,omitempty"` - HCPLinkResourceID string `json:"hcp_link_resource_ID,omitempty"` - RemovedFromCluster *bool `json:"removed_from_cluster,omitempty"` - Warnings []string `json:"warnings,omitempty"` + Type string `json:"type"` + Initialized bool `json:"initialized"` + Sealed bool `json:"sealed"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` + Nonce string `json:"nonce"` + Version string `json:"version"` + BuildDate string `json:"build_date"` + Migration bool `json:"migration"` + ClusterName string `json:"cluster_name,omitempty"` + ClusterID string `json:"cluster_id,omitempty"` + RecoverySeal bool `json:"recovery_seal"` + RecoverySealType string `json:"recovery_seal_type,omitempty"` + StorageType string `json:"storage_type,omitempty"` + HCPLinkStatus string `json:"hcp_link_status,omitempty"` + HCPLinkResourceID string `json:"hcp_link_resource_ID,omitempty"` + RemovedFromCluster *bool `json:"removed_from_cluster,omitempty"` + Warnings []string `json:"warnings,omitempty"` + MigrationDoneAtEpoch int64 `json:"migration_done_at_epoch,omitempty"` } type UnsealOpts struct { diff --git a/changelog/_14334.txt b/changelog/_14334.txt new file mode 100644 index 0000000000..c19611afc5 --- /dev/null +++ b/changelog/_14334.txt @@ -0,0 +1,3 @@ +```release-note:improvement +api: Add migration_done_at_epoch to sys/seal-status response. +``` diff --git a/vault/core.go b/vault/core.go index 985895542b..810a50b49e 100644 --- a/vault/core.go +++ b/vault/core.go @@ -1337,7 +1337,7 @@ func NewCore(conf *CoreConfig) (*Core, error) { // For recovery mode we've now configured enough to return early. if c.recoveryMode { - checkResult, err := c.checkForSealMigration(context.Background(), conf.UnwrapSeal) + checkResult, _, err := c.checkForSealMigration(context.Background(), conf.UnwrapSeal) if err != nil { return nil, fmt.Errorf("error checking if a seal migration is needed: %w", err) } @@ -1840,7 +1840,7 @@ func (c *Core) unsealFragment(key []byte, migrate bool) error { return fmt.Errorf("can't perform a seal migration while joining a raft cluster") } if !migrate && c.migrationInfo != nil { - done, err := c.sealMigrated(ctx) + done, _, err := c.sealMigrated(ctx) if err != nil { return fmt.Errorf("error checking to see if seal is migrated: %w", err) } @@ -2072,26 +2072,28 @@ func (c *Core) getUnsealKey(ctx context.Context, seal Seal) ([]byte, error) { // For the auto->auto same seal migration scenario, it will return false even // if the preceding conditions are true but we cannot decrypt the master key // in storage using the configured seal. -func (c *Core) sealMigrated(ctx context.Context) (bool, error) { +// When no error is returned, returns a string that gives more information about +// why the bool return value is set as it is. +func (c *Core) sealMigrated(ctx context.Context) (bool, string, error) { sealMigDone := c.sealMigrationDone.Load() if sealMigDone != nil && !sealMigDone.IsZero() { - return true, nil + return true, "sealMigrationDone nonzero", nil } existBarrierSealConfig, existRecoverySealConfig, err := c.PhysicalSealConfigs(ctx) if err != nil { - return false, err + return false, "", err } if !c.seal.BarrierSealConfigType().IsSameAs(existBarrierSealConfig.Type) { - return false, nil + return false, "barrier seal config type in seal matches what's in storage", nil } if c.seal.RecoveryKeySupported() && !SealConfigTypeRecovery.IsSameAs(existRecoverySealConfig.Type) { - return false, nil + return false, "recovery seal config type in seal matches what's in storage", nil } if c.seal.BarrierSealConfigType() != c.migrationInfo.seal.BarrierSealConfigType() { - return true, nil + return true, "barrier seal config type in seal doesn't match what's in storage", nil } // The above checks can handle the auto->shamir and shamir->auto @@ -2103,13 +2105,13 @@ func (c *Core) sealMigrated(ctx context.Context) (bool, error) { switch { case len(keys) > 0 && err == nil: - return true, nil + return true, "seal has stored keys", nil case len(keysMig) > 0 && errMig == nil: - return false, nil + return false, "migration seal has stored keys", nil case errors.Is(err, &ErrDecrypt{}) && errors.Is(errMig, &ErrDecrypt{}): - return false, fmt.Errorf("decrypt error, neither the old nor new seal can read stored keys: old seal err=%v, new seal err=%v", errMig, err) + return false, "", fmt.Errorf("decrypt error, neither the old nor new seal can read stored keys: old seal err=%v, new seal err=%v", errMig, err) default: - return false, fmt.Errorf("neither the old nor new seal can read stored keys: old seal err=%v, new seal err=%v", errMig, err) + return false, "", fmt.Errorf("neither the old nor new seal can read stored keys: old seal err=%v, new seal err=%v", errMig, err) } } @@ -2121,17 +2123,17 @@ func (c *Core) migrateSeal(ctx context.Context) error { return c.migrateMultiSealConfig(ctx) } - ok, err := c.sealMigrated(ctx) + ok, info, err := c.sealMigrated(ctx) if err != nil { return fmt.Errorf("error checking if seal is migrated or not: %w", err) } if ok { - c.logger.Info("migration is already performed") + c.logger.Info("migration is already performed", "info", info) return nil } - c.logger.Info("seal migration initiated") + c.logger.Info("seal migration initiated", "info", info) switch { case c.migrationInfo.seal.RecoveryKeySupported() && c.seal.RecoveryKeySupported(): @@ -3345,16 +3347,16 @@ const ( sealMigrationCheckDoNotAjust ) -func (c *Core) checkForSealMigration(ctx context.Context, unwrapSeal Seal) (sealMigrationCheckResult, error) { +func (c *Core) checkForSealMigration(ctx context.Context, unwrapSeal Seal) (sealMigrationCheckResult, string, error) { existBarrierSealConfig, _, err := c.PhysicalSealConfigs(ctx) if err != nil { - return sealMigrationCheckError, fmt.Errorf("Error checking for existing seal: %s", err) + return sealMigrationCheckError, "", fmt.Errorf("Error checking for existing seal: %s", err) } // If we don't have an existing config or if it's the deprecated auto seal // which needs an upgrade, skip out if existBarrierSealConfig == nil || existBarrierSealConfig.Type == WrapperTypeHsmAutoDeprecated.String() { - return sealMigrationCheckSkip, nil + return sealMigrationCheckSkip, "no seal config or deprecated", nil } if unwrapSeal == nil { @@ -3368,28 +3370,28 @@ func (c *Core) checkForSealMigration(ctx context.Context, unwrapSeal Seal) (seal case storedType == configuredType: // 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. - return sealMigrationCheckDoNotAjust, nil + return sealMigrationCheckDoNotAjust, "same barrier and unwrap seal is nil", nil case configuredType == SealConfigTypeShamir: // The stored barrier config is not shamir, there is no disabled seal // in config, and either no configured seal (which equates to Shamir) // or an explicitly configured Shamir seal. - return sealMigrationCheckError, fmt.Errorf("cannot seal migrate from %q to Shamir, no disabled seal in configuration", + return sealMigrationCheckError, "", fmt.Errorf("cannot seal migrate from %q to Shamir, no disabled seal in configuration", existBarrierSealConfig.Type) case storedType == SealConfigTypeShamir: // The configured seal is not Shamir, the stored seal config is Shamir. // This is a migration away from Shamir. - return sealMigrationCheckAdjust, nil + return sealMigrationCheckAdjust, "configured seal is not shamir and stored seal config is", nil case configuredType == SealConfigTypeMultiseal && c.IsMultisealEnabled(): // We are going from a single non-shamir seal to multiseal, and multi seal is supported. // This scenario is not considered a migration in the sense of requiring an unwrapSeal, // but we will update the stored SealConfig later (see Core.migrateMultiSealConfig). - return sealMigrationCheckDoNotAjust, nil + return sealMigrationCheckDoNotAjust, "single non-shamir to multiseal", nil case configuredType == SealConfigTypeMultiseal: // The configured seal is multiseal and we know the stored type is not shamir, thus // we are going from auto seal to multiseal. - return sealMigrationCheckError, fmt.Errorf("cannot seal migrate from %q to %q, multiple seals are not supported", + return sealMigrationCheckError, "", fmt.Errorf("cannot seal migrate from %q to %q, multiple seals are not supported", existBarrierSealConfig.Type, c.seal.BarrierSealConfigType()) case storedType == SealConfigTypeMultiseal: // The stored type is multiseal and we know the type the configured type is not shamir, @@ -3398,12 +3400,12 @@ func (c *Core) checkForSealMigration(ctx context.Context, unwrapSeal Seal) (seal // This scenario is not considered a migration in the sense of requiring an unwrapSeal, // but we will update the stored SealConfig later (see Core.migrateMultiSealConfig). - return sealMigrationCheckDoNotAjust, nil + return sealMigrationCheckDoNotAjust, "multiseal to autoseal", nil default: // We know at this point that there is a configured non-Shamir seal, // that it does not match the stored non-Shamir seal config, and that // there is no explicitly disabled seal stanza. - return sealMigrationCheckError, fmt.Errorf("cannot seal migrate from %q to %q, no disabled seal in configuration", + return sealMigrationCheckError, "", fmt.Errorf("cannot seal migrate from %q to %q, no disabled seal in configuration", existBarrierSealConfig.Type, c.seal.BarrierSealConfigType()) } } else { @@ -3411,9 +3413,9 @@ func (c *Core) checkForSealMigration(ctx context.Context, unwrapSeal Seal) (seal // in the config and disabled. if unwrapSeal.BarrierSealConfigType() == SealConfigTypeShamir { - return sealMigrationCheckError, errors.New("Shamir seals cannot be set disabled (they should simply not be set)") + return sealMigrationCheckError, "", errors.New("Shamir seals cannot be set disabled (they should simply not be set)") } - return sealMigrationCheckDoNotAjust, nil + return sealMigrationCheckDoNotAjust, "unchanged", nil } } @@ -3439,7 +3441,7 @@ func (c *Core) checkForSealMigration(ctx context.Context, unwrapSeal Seal) (seal func (c *Core) adjustForSealMigration(unwrapSeal Seal) error { ctx := context.Background() - checkResult, err := c.checkForSealMigration(ctx, unwrapSeal) + checkResult, _, err := c.checkForSealMigration(ctx, unwrapSeal) if err != nil { return err } @@ -3723,7 +3725,7 @@ func (c *Core) IsSealMigrated(lock bool) bool { c.stateLock.RLock() defer c.stateLock.RUnlock() } - done, _ := c.sealMigrated(context.Background()) + done, _, _ := c.sealMigrated(context.Background()) return done } diff --git a/vault/logical_system.go b/vault/logical_system.go index 705de4358a..7e1c3a2bae 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -6026,25 +6026,26 @@ func (b *SystemBackend) pathInternalOpenAPI(ctx context.Context, req *logical.Re } type SealStatusResponse struct { - Type string `json:"type"` - Initialized bool `json:"initialized"` - Sealed bool `json:"sealed"` - T int `json:"t"` - N int `json:"n"` - Progress int `json:"progress"` - Nonce string `json:"nonce"` - Version string `json:"version"` - BuildDate string `json:"build_date"` - Migration bool `json:"migration"` - ClusterName string `json:"cluster_name,omitempty"` - ClusterID string `json:"cluster_id,omitempty"` - RecoverySeal bool `json:"recovery_seal"` - StorageType string `json:"storage_type,omitempty"` - HCPLinkStatus string `json:"hcp_link_status,omitempty"` - HCPLinkResourceID string `json:"hcp_link_resource_ID,omitempty"` - Warnings []string `json:"warnings,omitempty"` - RecoverySealType string `json:"recovery_seal_type,omitempty"` - RemovedFromCluster *bool `json:"removed_from_cluster,omitempty"` + Type string `json:"type"` + Initialized bool `json:"initialized"` + Sealed bool `json:"sealed"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` + Nonce string `json:"nonce"` + Version string `json:"version"` + BuildDate string `json:"build_date"` + Migration bool `json:"migration"` + ClusterName string `json:"cluster_name,omitempty"` + ClusterID string `json:"cluster_id,omitempty"` + RecoverySeal bool `json:"recovery_seal"` + StorageType string `json:"storage_type,omitempty"` + HCPLinkStatus string `json:"hcp_link_status,omitempty"` + HCPLinkResourceID string `json:"hcp_link_resource_ID,omitempty"` + Warnings []string `json:"warnings,omitempty"` + RecoverySealType string `json:"recovery_seal_type,omitempty"` + RemovedFromCluster *bool `json:"removed_from_cluster,omitempty"` + MigrationDoneAtEpoch int64 `json:"migration_done_at_epoch,omitempty"` } type SealBackendStatus struct { @@ -6157,6 +6158,9 @@ func (core *Core) GetSealStatus(ctx context.Context, lock bool) (*SealStatusResp RecoverySealType: recoverySealType, StorageType: core.StorageType(), } + if p := core.sealMigrationDone.Load(); p != nil { + s.MigrationDoneAtEpoch = p.Unix() + } if resourceIDonHCP != "" { s.HCPLinkStatus = hcpLinkStatus