1
0

Add ability to migrate autoseal to autoseal (#5930)

* Add ability to migrate autoseal to autoseal

This adds the ability to migrate from shamir to autoseal, autoseal to
shamir, or autoseal to autoseal, by allowing multiple seal stanzas. A
disabled stanza will be used as the config being migrated from; this can
also be used to provide an unwrap seal on ent over multiple unseals.

A new test is added to ensure that autoseal to autoseal works as
expected.

* Fix test

* Provide default shamir info if not given in config

* Linting feedback

* Remove context var that isn't used

* Don't run auto unseal watcher when in migration, and move SetCores to SetSealsForMigration func

* Slight logic cleanup

* Fix test build and fix bug

* Updates

* remove GetRecoveryKey function
This commit is contained in:
Jeff Mitchell 2019-03-04 17:11:56 -05:00 committed by Brian Kassouf
parent facbc4cc60
commit 213da13264
13 changed files with 351 additions and 245 deletions

View File

@ -9,7 +9,6 @@ import (
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/server"
"github.com/hashicorp/vault/helper/logging"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/physical"
@ -114,11 +113,7 @@ func TestSealMigration(t *testing.T) {
newSeal := vault.NewAutoSeal(seal.NewTestSeal(nil))
newSeal.SetCore(core)
autoSeal = newSeal
if err := adjustCoreForSealMigration(ctx, core, coreConfig, newSeal, &server.Config{
Seal: &server.Seal{
Type: "test-auto",
},
}); err != nil {
if err := adjustCoreForSealMigration(core, newSeal, nil); err != nil {
t.Fatal(err)
}
@ -197,13 +192,16 @@ func TestSealMigration(t *testing.T) {
cluster.Cores = nil
}
// We should see stored barrier keys; after the next stanza, we shouldn't
// We should see stored barrier keys; after the sixth test, we shouldn't
if entry, err := phys.Get(ctx, vault.StoredBarrierKeysPath); err != nil || entry == nil {
t.Fatalf("expected nil error and non-nil entry, got error %#v and entry %#v", err, entry)
}
// Fifth: create an autoseal and activate migration. Verify it doesn't work
// if disabled isn't set.
altTestSeal := seal.NewTestSeal(nil)
altTestSeal.Type = "test-alternate"
altSeal := vault.NewAutoSeal(altTestSeal)
// Fifth: migrate from auto-seal to auto-seal
{
coreConfig.Seal = autoSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
@ -212,17 +210,45 @@ func TestSealMigration(t *testing.T) {
core := cluster.Cores[0].Core
serverConf := &server.Config{
Seal: &server.Seal{
Type: "test-auto",
},
if err := adjustCoreForSealMigration(core, altSeal, autoSeal); err != nil {
t.Fatal(err)
}
if err := adjustCoreForSealMigration(ctx, core, coreConfig, shamirSeal, serverConf); err == nil {
t.Fatal("expected error since disabled isn't set true")
client := cluster.Cores[0].Client
client.SetToken(rootToken)
var resp *api.SealStatusResponse
unsealOpts := &api.UnsealOpts{}
for _, key := range keys {
unsealOpts.Key = key
unsealOpts.Migrate = true
resp, err = client.Sys().UnsealWithOptions(unsealOpts)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response")
}
}
serverConf.Seal.Disabled = true
if err := adjustCoreForSealMigration(ctx, core, coreConfig, shamirSeal, serverConf); err != nil {
if resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", *resp)
}
cluster.Cleanup()
cluster.Cores = nil
}
// Sixth: create an Shamir seal and activate migration. Verify it doesn't work
// if disabled isn't set.
{
coreConfig.Seal = altSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0].Core
if err := adjustCoreForSealMigration(core, shamirSeal, altSeal); err != nil {
t.Fatal(err)
}
@ -259,7 +285,7 @@ func TestSealMigration(t *testing.T) {
t.Fatalf("expected nil error and nil entry, got error %#v and entry %#v", err, entry)
}
// Sixth: verify autoseal is off and the expected key shares work
// Seventh: verify autoseal is off and the expected key shares work
{
coreConfig.Seal = shamirSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)

View File

@ -6,7 +6,6 @@ import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/hashicorp/vault/helper/metricsutil"
"io"
"io/ioutil"
"net"
@ -21,6 +20,8 @@ import (
"sync"
"time"
"github.com/hashicorp/vault/helper/metricsutil"
metrics "github.com/armon/go-metrics"
"github.com/armon/go-metrics/circonus"
"github.com/armon/go-metrics/datadog"
@ -501,44 +502,61 @@ func (c *ServerCommand) Run(args []string) int {
info["log level"] = logLevelString
infoKeys = append(infoKeys, "log level")
sealType := vaultseal.Shamir
if config.Seal != nil || os.Getenv("VAULT_SEAL_TYPE") != "" {
if config.Seal == nil {
sealType = os.Getenv("VAULT_SEAL_TYPE")
} else {
sealType = config.Seal.Type
}
}
var barrierSeal vault.Seal
var unwrapSeal vault.Seal
var seal vault.Seal
var sealConfigError error
if c.flagDevAutoSeal {
seal = vault.NewAutoSeal(vaultseal.NewTestSeal(nil))
barrierSeal = vault.NewAutoSeal(vaultseal.NewTestSeal(nil))
} else {
sealLogger := c.logger.Named(sealType)
allLoggers = append(allLoggers, sealLogger)
seal, sealConfigError = serverseal.ConfigureSeal(config, &infoKeys, &info, sealLogger, vault.NewDefaultSeal())
if sealConfigError != nil {
if !errwrap.ContainsType(sealConfigError, new(logical.KeyNotFoundError)) {
// Handle the case where no seal is provided
if len(config.Seals) == 0 {
config.Seals = append(config.Seals, &server.Seal{Type: vaultseal.Shamir})
}
for _, configSeal := range config.Seals {
sealType := vaultseal.Shamir
if !configSeal.Disabled && os.Getenv("VAULT_SEAL_TYPE") != "" {
sealType = os.Getenv("VAULT_SEAL_TYPE")
} else {
sealType = configSeal.Type
}
var seal vault.Seal
sealLogger := c.logger.Named(sealType)
allLoggers = append(allLoggers, sealLogger)
seal, sealConfigError = serverseal.ConfigureSeal(configSeal, &infoKeys, &info, sealLogger, vault.NewDefaultSeal())
if sealConfigError != nil {
if !errwrap.ContainsType(sealConfigError, new(logical.KeyNotFoundError)) {
c.UI.Error(fmt.Sprintf(
"Error parsing Seal configuration: %s", sealConfigError))
return 1
}
}
if seal == nil {
c.UI.Error(fmt.Sprintf(
"Error parsing Seal configuration: %s", sealConfigError))
"After configuring seal nil returned, seal type was %s", sealType))
return 1
}
if configSeal.Disabled {
unwrapSeal = seal
} else {
barrierSeal = seal
}
// Ensure that the seal finalizer is called, even if using verify-only
defer func() {
err = seal.Finalize(context.Background())
if err != nil {
c.UI.Error(fmt.Sprintf("Error finalizing seals: %v", err))
}
}()
}
}
// Ensure that the seal finalizer is called, even if using verify-only
defer func() {
if seal != nil {
err = seal.Finalize(context.Background())
if err != nil {
c.UI.Error(fmt.Sprintf("Error finalizing seals: %v", err))
}
}
}()
if seal == nil {
c.UI.Error(fmt.Sprintf("Could not create seal! Most likely proper Seal configuration information was not set, but no error was generated."))
if barrierSeal == nil {
c.UI.Error(fmt.Sprintf("Could not create barrier seal! Most likely proper Seal configuration information was not set, but no error was generated."))
return 1
}
@ -546,7 +564,7 @@ func (c *ServerCommand) Run(args []string) int {
Physical: backend,
RedirectAddr: config.Storage.RedirectAddr,
HAPhysical: nil,
Seal: seal,
Seal: barrierSeal,
AuditBackends: c.AuditBackends,
CredentialBackends: c.CredentialBackends,
LogicalBackends: c.LogicalBackends,
@ -937,7 +955,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
}))
// Before unsealing with stored keys, setup seal migration if needed
if err := adjustCoreForSealMigration(context.Background(), core, coreConfig, seal, config); err != nil {
if err := adjustCoreForSealMigration(core, barrierSeal, unwrapSeal); err != nil {
c.UI.Error(err.Error())
return 1
}
@ -946,27 +964,29 @@ CLUSTER_SYNTHESIS_COMPLETE:
// Vault cluster with multiple servers is configured with auto-unseal but is
// uninitialized. Once one server initializes the storage backend, this
// goroutine will pick up the unseal keys and unseal this instance.
go func() {
for {
err := core.UnsealWithStoredKeys(context.Background())
if err == nil {
return
}
if !core.IsInSealMigration() {
go func() {
for {
err := core.UnsealWithStoredKeys(context.Background())
if err == nil {
return
}
if vault.IsFatalError(err) {
c.logger.Error("error unsealing core", "error", err)
return
} else {
c.logger.Warn("failed to unseal core", "error", err)
}
if vault.IsFatalError(err) {
c.logger.Error("error unsealing core", "error", err)
return
} else {
c.logger.Warn("failed to unseal core", "error", err)
}
select {
case <-c.ShutdownCh:
return
case <-time.After(5 * time.Second):
select {
case <-c.ShutdownCh:
return
case <-time.After(5 * time.Second):
}
}
}
}()
}()
}
// Perform service discovery registrations and initialization of
// HTTP server after the verifyOnly check.
@ -1366,9 +1386,8 @@ func (c *ServerCommand) enableDev(core *vault.Core, coreConfig *vault.CoreConfig
return nil, err
}
// Upgrade the default K/V store
kvVer := "2"
if c.flagDevKVV1 {
if c.flagDevKVV1 || c.flagDevLeasedKV {
kvVer = "1"
}
req := &logical.Request{

View File

@ -1,6 +1,7 @@
package server
import (
"errors"
"fmt"
"io"
"io/ioutil"
@ -29,7 +30,7 @@ type Config struct {
Storage *Storage `hcl:"-"`
HAStorage *Storage `hcl:"-"`
Seal *Seal `hcl:"-"`
Seals []*Seal `hcl:"-"`
CacheSize int `hcl:"cache_size"`
DisableCache bool `hcl:"-"`
@ -276,9 +277,11 @@ func (c *Config) Merge(c2 *Config) *Config {
result.HAStorage = c2.HAStorage
}
result.Seal = c.Seal
if c2.Seal != nil {
result.Seal = c2.Seal
for _, s := range c.Seals {
result.Seals = append(result.Seals, s)
}
for _, s := range c2.Seals {
result.Seals = append(result.Seals, s)
}
result.Telemetry = c.Telemetry
@ -558,13 +561,13 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) {
}
if o := list.Filter("hsm"); len(o.Items) > 0 {
if err := parseSeal(&result, o, "hsm"); err != nil {
if err := parseSeals(&result, o, "hsm"); err != nil {
return nil, errwrap.Wrapf("error parsing 'hsm': {{err}}", err)
}
}
if o := list.Filter("seal"); len(o.Items) > 0 {
if err := parseSeal(&result, o, "seal"); err != nil {
if err := parseSeals(&result, o, "seal"); err != nil {
return nil, errwrap.Wrapf("error parsing 'seal': {{err}}", err)
}
}
@ -795,40 +798,46 @@ func parseHAStorage(result *Config, list *ast.ObjectList, name string) error {
return nil
}
func parseSeal(result *Config, list *ast.ObjectList, blockName string) error {
if len(list.Items) > 1 {
return fmt.Errorf("only one %q block is permitted", blockName)
func parseSeals(result *Config, list *ast.ObjectList, blockName string) error {
if len(list.Items) > 2 {
return fmt.Errorf("only two or less %q blocks are permitted", blockName)
}
// Get our item
item := list.Items[0]
key := blockName
if len(item.Keys) > 0 {
key = item.Keys[0].Token.Value().(string)
}
var m map[string]string
if err := hcl.DecodeObject(&m, item.Val); err != nil {
return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key))
}
var disabled bool
var err error
if v, ok := m["disabled"]; ok {
disabled, err = strconv.ParseBool(v)
if err != nil {
return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key))
seals := make([]*Seal, 0, len(list.Items))
for _, item := range list.Items {
key := "seal"
if len(item.Keys) > 0 {
key = item.Keys[0].Token.Value().(string)
}
delete(m, "disabled")
var m map[string]string
if err := hcl.DecodeObject(&m, item.Val); err != nil {
return multierror.Prefix(err, fmt.Sprintf("seal.%s:", key))
}
var disabled bool
var err error
if v, ok := m["disabled"]; ok {
disabled, err = strconv.ParseBool(v)
if err != nil {
return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key))
}
delete(m, "disabled")
}
seals = append(seals, &Seal{
Type: strings.ToLower(key),
Disabled: disabled,
Config: m,
})
}
result.Seal = &Seal{
Type: strings.ToLower(key),
Disabled: disabled,
Config: m,
if len(seals) == 2 &&
(seals[0].Disabled && seals[1].Disabled || !seals[0].Disabled && !seals[1].Disabled) {
return errors.New("seals: two seals provided but both are disabled or neither are disabled")
}
result.Seals = seals
return nil
}

View File

@ -2,7 +2,6 @@ package seal
import (
"fmt"
"os"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/command/server"
@ -11,39 +10,33 @@ import (
)
var (
ConfigureSeal func(*server.Config, *[]string, *map[string]string, log.Logger, vault.Seal) (vault.Seal, error) = configureSeal
ConfigureSeal = configureSeal
)
func configureSeal(config *server.Config, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (outseal vault.Seal, err error) {
if config.Seal != nil || os.Getenv("VAULT_SEAL_TYPE") != "" {
if config.Seal == nil {
config.Seal = &server.Seal{
Type: os.Getenv("VAULT_SEAL_TYPE"),
}
}
switch config.Seal.Type {
case seal.AliCloudKMS:
return configureAliCloudKMSSeal(config, infoKeys, info, logger, inseal)
func configureSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (outseal vault.Seal, err error) {
switch configSeal.Type {
case seal.AliCloudKMS:
return configureAliCloudKMSSeal(configSeal, infoKeys, info, logger, inseal)
case seal.AWSKMS:
return configureAWSKMSSeal(config, infoKeys, info, logger, inseal)
case seal.AWSKMS:
return configureAWSKMSSeal(configSeal, infoKeys, info, logger, inseal)
case seal.GCPCKMS:
return configureGCPCKMSSeal(config, infoKeys, info, logger, inseal)
case seal.GCPCKMS:
return configureGCPCKMSSeal(configSeal, infoKeys, info, logger, inseal)
case seal.AzureKeyVault:
return configureAzureKeyVaultSeal(config, infoKeys, info, logger, inseal)
case seal.AzureKeyVault:
return configureAzureKeyVaultSeal(configSeal, infoKeys, info, logger, inseal)
case seal.Transit:
return configureTransitSeal(config, infoKeys, info, logger, inseal)
case seal.Transit:
return configureTransitSeal(configSeal, infoKeys, info, logger, inseal)
case seal.PKCS11:
return nil, fmt.Errorf("Seal type 'pkcs11' requires the Vault Enterprise HSM binary")
case seal.PKCS11:
return nil, fmt.Errorf("Seal type 'pkcs11' requires the Vault Enterprise HSM binary")
default:
return nil, fmt.Errorf("Unknown seal type %q", config.Seal.Type)
}
case seal.Shamir:
return inseal, nil
default:
return nil, fmt.Errorf("Unknown seal type %q", configSeal.Type)
}
return inseal, nil
}

View File

@ -9,9 +9,9 @@ import (
"github.com/hashicorp/vault/vault/seal/alicloudkms"
)
func configureAliCloudKMSSeal(config *server.Config, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
func configureAliCloudKMSSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
kms := alicloudkms.NewSeal(logger)
kmsInfo, err := kms.SetConfig(config.Seal.Config)
kmsInfo, err := kms.SetConfig(configSeal.Config)
if err != nil {
// If the error is any other than logical.KeyNotFoundError, return the error
if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) {
@ -21,7 +21,7 @@ func configureAliCloudKMSSeal(config *server.Config, infoKeys *[]string, info *m
autoseal := vault.NewAutoSeal(kms)
if kmsInfo != nil {
*infoKeys = append(*infoKeys, "Seal Type", "AliCloud KMS Region", "AliCloud KMS KeyID")
(*info)["Seal Type"] = config.Seal.Type
(*info)["Seal Type"] = configSeal.Type
(*info)["AliCloud KMS Region"] = kmsInfo["region"]
(*info)["AliCloud KMS KeyID"] = kmsInfo["kms_key_id"]
if domain, ok := kmsInfo["domain"]; ok {

View File

@ -9,9 +9,9 @@ import (
"github.com/hashicorp/vault/vault/seal/awskms"
)
func configureAWSKMSSeal(config *server.Config, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
func configureAWSKMSSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
kms := awskms.NewSeal(logger)
kmsInfo, err := kms.SetConfig(config.Seal.Config)
kmsInfo, err := kms.SetConfig(configSeal.Config)
if err != nil {
// If the error is any other than logical.KeyNotFoundError, return the error
if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) {
@ -21,7 +21,7 @@ func configureAWSKMSSeal(config *server.Config, infoKeys *[]string, info *map[st
autoseal := vault.NewAutoSeal(kms)
if kmsInfo != nil {
*infoKeys = append(*infoKeys, "Seal Type", "AWS KMS Region", "AWS KMS KeyID")
(*info)["Seal Type"] = config.Seal.Type
(*info)["Seal Type"] = configSeal.Type
(*info)["AWS KMS Region"] = kmsInfo["region"]
(*info)["AWS KMS KeyID"] = kmsInfo["kms_key_id"]
if endpoint, ok := kmsInfo["endpoint"]; ok {

View File

@ -9,9 +9,9 @@ import (
"github.com/hashicorp/vault/vault/seal/azurekeyvault"
)
func configureAzureKeyVaultSeal(config *server.Config, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
func configureAzureKeyVaultSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
kv := azurekeyvault.NewSeal(logger)
kvInfo, err := kv.SetConfig(config.Seal.Config)
kvInfo, err := kv.SetConfig(configSeal.Config)
if err != nil {
// If the error is any other than logical.KeyNotFoundError, return the error
if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) {
@ -21,7 +21,7 @@ func configureAzureKeyVaultSeal(config *server.Config, infoKeys *[]string, info
autoseal := vault.NewAutoSeal(kv)
if kvInfo != nil {
*infoKeys = append(*infoKeys, "Seal Type", "Azure Environment", "Azure Vault Name", "Azure Key Name")
(*info)["Seal Type"] = config.Seal.Type
(*info)["Seal Type"] = configSeal.Type
(*info)["Azure Environment"] = kvInfo["environment"]
(*info)["Azure Vault Name"] = kvInfo["vault_name"]
(*info)["Azure Key Name"] = kvInfo["key_name"]

View File

@ -9,9 +9,9 @@ import (
"github.com/hashicorp/vault/vault/seal/gcpckms"
)
func configureGCPCKMSSeal(config *server.Config, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
func configureGCPCKMSSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
kms := gcpckms.NewSeal(logger)
kmsInfo, err := kms.SetConfig(config.Seal.Config)
kmsInfo, err := kms.SetConfig(configSeal.Config)
if err != nil {
// If the error is any other than logical.KeyNotFoundError, return the error
if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) {
@ -21,7 +21,7 @@ func configureGCPCKMSSeal(config *server.Config, infoKeys *[]string, info *map[s
autoseal := vault.NewAutoSeal(kms)
if kmsInfo != nil {
*infoKeys = append(*infoKeys, "Seal Type", "GCP KMS Project", "GCP KMS Region", "GCP KMS Key Ring", "GCP KMS Crypto Key")
(*info)["Seal Type"] = config.Seal.Type
(*info)["Seal Type"] = configSeal.Type
(*info)["GCP KMS Project"] = kmsInfo["project"]
(*info)["GCP KMS Region"] = kmsInfo["region"]
(*info)["GCP KMS Key Ring"] = kmsInfo["key_ring"]

View File

@ -9,9 +9,9 @@ import (
"github.com/hashicorp/vault/vault/seal/transit"
)
func configureTransitSeal(config *server.Config, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
func configureTransitSeal(configSeal *server.Seal, infoKeys *[]string, info *map[string]string, logger log.Logger, inseal vault.Seal) (vault.Seal, error) {
transitSeal := transit.NewSeal(logger)
sealInfo, err := transitSeal.SetConfig(config.Seal.Config)
sealInfo, err := transitSeal.SetConfig(configSeal.Config)
if err != nil {
// If the error is any other than logical.KeyNotFoundError, return the error
if !errwrap.ContainsType(err, new(logical.KeyNotFoundError)) {
@ -21,7 +21,7 @@ func configureTransitSeal(config *server.Config, infoKeys *[]string, info *map[s
autoseal := vault.NewAutoSeal(transitSeal)
if sealInfo != nil {
*infoKeys = append(*infoKeys, "Seal Type", "Transit Address", "Transit Mount Path", "Transit Key Name")
(*info)["Seal Type"] = config.Seal.Type
(*info)["Seal Type"] = configSeal.Type
(*info)["Transit Address"] = sealInfo["address"]
(*info)["Transit Mount Path"] = sealInfo["mount_path"]
(*info)["Transit Key Name"] = sealInfo["key_name"]

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/hashicorp/vault/command/server"
"github.com/hashicorp/vault/vault"
vaultseal "github.com/hashicorp/vault/vault/seal"
"github.com/pkg/errors"
@ -14,63 +13,78 @@ var (
onEnterprise = false
)
func adjustCoreForSealMigration(ctx context.Context, core *vault.Core, coreConfig *vault.CoreConfig, seal vault.Seal, config *server.Config) error {
func adjustCoreForSealMigration(core *vault.Core, barrierSeal, unwrapSeal vault.Seal) error {
existBarrierSealConfig, existRecoverySealConfig, err := core.PhysicalSealConfigs(context.Background())
if err != nil {
return fmt.Errorf("Error checking for existing seal: %s", err)
}
var existSeal vault.Seal
var newSeal vault.Seal
if existBarrierSealConfig != nil && existBarrierSealConfig.Type != vaultseal.HSMAutoDeprecated &&
(existBarrierSealConfig.Type != seal.BarrierType() ||
config.Seal != nil && config.Seal.Disabled) {
// If the existing seal is not Shamir, we're going to Shamir, which
// means we require them setting "disabled" to true in their
// configuration as a sanity check.
if (config.Seal == nil || !config.Seal.Disabled) && existBarrierSealConfig.Type != vaultseal.Shamir {
return errors.New(`Seal migration requires specifying "disabled" as "true" in the "seal" block of Vault's configuration file"`)
}
// Conversely, if they are going from Shamir to auto, we want to
// ensure disabled is *not* set
if existBarrierSealConfig.Type == vaultseal.Shamir && config.Seal != nil && config.Seal.Disabled {
coreConfig.Logger.Warn(`when not migrating, Vault's config should not specify "disabled" as "true" in the "seal" block of Vault's configuration file`)
// 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 == vaultseal.HSMAutoDeprecated {
return nil
}
if unwrapSeal == nil {
// 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
if existBarrierSealConfig.Type == barrierSeal.BarrierType() {
return nil
}
if existBarrierSealConfig.Type != vaultseal.Shamir && existRecoverySealConfig == nil {
return errors.New(`Recovery seal configuration not found for existing seal`)
// If we're not coming from Shamir, and the existing type doesn't match
// the barrier type, we need both the migration seal and the new seal
if existBarrierSealConfig.Type != vaultseal.Shamir && barrierSeal.BarrierType() != vaultseal.Shamir {
return errors.New(`Trying to migrate from auto-seal to auto-seal but no "disabled" seal stanza found`)
}
switch existBarrierSealConfig.Type {
case vaultseal.Shamir:
// The value reflected in config is what we're going to
existSeal = vault.NewDefaultSeal()
existSeal.SetCore(core)
newSeal = seal
newBarrierSealConfig := &vault.SealConfig{
Type: newSeal.BarrierType(),
SecretShares: 1,
SecretThreshold: 1,
StoredShares: 1,
}
newSeal.SetCachedBarrierConfig(newBarrierSealConfig)
newSeal.SetCachedRecoveryConfig(existBarrierSealConfig)
default:
if onEnterprise {
return errors.New("Migrating from autoseal to Shamir seal is not supported on Vault Enterprise")
}
// The disabled value reflected in config is what we're going from
existSeal = coreConfig.Seal
newSeal = vault.NewDefaultSeal()
newSeal.SetCore(core)
newSeal.SetCachedBarrierConfig(existRecoverySealConfig)
} else {
if unwrapSeal.BarrierType() == vaultseal.Shamir {
return errors.New("Shamir seals cannot be set disabled (they should simply not be set)")
}
core.SetSealsForMigration(existSeal, newSeal)
}
var existSeal vault.Seal
var newSeal vault.Seal
if existBarrierSealConfig.Type == barrierSeal.BarrierType() {
// In this case our migration seal is set so we are using it
// (potentially) for unwrapping. Set it on core for that purpose then
// exit.
core.SetSealsForMigration(nil, nil, unwrapSeal)
return nil
}
if existBarrierSealConfig.Type != vaultseal.Shamir && existRecoverySealConfig == nil {
return errors.New(`Recovery seal configuration not found for existing seal`)
}
switch existBarrierSealConfig.Type {
case vaultseal.Shamir:
// The value reflected in config is what we're going to
existSeal = vault.NewDefaultSeal()
newSeal = barrierSeal
newBarrierSealConfig := &vault.SealConfig{
Type: newSeal.BarrierType(),
SecretShares: 1,
SecretThreshold: 1,
StoredShares: 1,
}
newSeal.SetCachedBarrierConfig(newBarrierSealConfig)
newSeal.SetCachedRecoveryConfig(existBarrierSealConfig)
default:
if onEnterprise && barrierSeal.BarrierType() == vaultseal.Shamir {
return errors.New("Migrating from autoseal to Shamir seal is not currently supported on Vault Enterprise")
}
// If we're not cominng from Shamir we expect the previous seal to be
// in the config and disabled.
existSeal = unwrapSeal
newSeal = barrierSeal
newSeal.SetCachedBarrierConfig(existRecoverySealConfig)
}
core.SetSealsForMigration(existSeal, newSeal, unwrapSeal)
return nil
}

View File

@ -179,6 +179,10 @@ type Core struct {
// seal we're migrating *from*.
migrationSeal Seal
// unwrapSeal is the seal to use on Enterprise to unwrap values wrapped
// with the previous seal.
unwrapSeal Seal
// barrier is the security barrier wrapping the physical backend
barrier SecurityBarrier
@ -921,9 +925,12 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
return nil, errwrap.Wrapf("unable to retrieve stored keys: {{err}}", err)
}
if len(masterKeyShares) == 1 {
switch len(masterKeyShares) {
case 0:
return nil, errors.New("seal returned no master key shares")
case 1:
masterKey = masterKeyShares[0]
} else {
default:
masterKey, err = shamir.Combine(masterKeyShares)
if err != nil {
return nil, errwrap.Wrapf("failed to compute master key: {{err}}", err)
@ -942,10 +949,50 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
}
defer c.barrier.Seal()
// The seal used in this function will have been the migration seal,
// and c.seal will be the opposite type, so there are two
// possibilities: Shamir to auto, and auto to Shamir.
if !seal.RecoveryKeySupported() {
switch {
case c.migrationSeal.RecoveryKeySupported() && c.seal.RecoveryKeySupported():
// Set the recovery and barrier keys to be the same.
recoveryKey, err := c.migrationSeal.RecoveryKey(ctx)
if err != nil {
return nil, errwrap.Wrapf("error getting recovery key to set on new seal: {{err}}", err)
}
if err := c.seal.SetRecoveryKey(ctx, recoveryKey); err != nil {
return nil, errwrap.Wrapf("error setting new recovery key information during migrate: {{err}}", err)
}
barrierKeys, err := c.migrationSeal.GetStoredKeys(ctx)
if err != nil {
return nil, errwrap.Wrapf("error getting stored keys to set on new seal: {{err}}", err)
}
if err := c.seal.SetStoredKeys(ctx, barrierKeys); err != nil {
return nil, errwrap.Wrapf("error setting new barrier key information during migrate: {{err}}", err)
}
case c.migrationSeal.RecoveryKeySupported():
// Auto to Shamir, since recovery key isn't supported on new seal
// In this case we have to ensure that the recovery information was
// set properly.
if recoveryKey == nil {
return nil, errors.New("did not get expected recovery information to set new seal during migration")
}
// We have recovery keys; we're going to use them as the new
// barrier key.
if err := c.barrier.Rekey(ctx, recoveryKey); err != nil {
return nil, errwrap.Wrapf("error rekeying barrier during migration: {{err}}", err)
}
if err := c.barrier.Delete(ctx, StoredBarrierKeysPath); err != nil {
// Don't actually exit here as successful deletion isn't critical
c.logger.Error("error deleting stored barrier keys after migration; continuing anyways", "error", err)
}
masterKey = recoveryKey
case c.seal.RecoveryKeySupported():
// The new seal will have recovery keys; we set it to the existing
// master key, so barrier key shares -> recovery key shares
if err := c.seal.SetRecoveryKey(ctx, masterKey); err != nil {
@ -970,25 +1017,9 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
// Return the new key so it can be used to unlock the barrier
masterKey = newMasterKey
} else {
// In this case we have to ensure that the recovery information was
// set properly.
if recoveryKey == nil {
return nil, errors.New("did not get expected recovery information to set new seal during migration")
}
// Auto to Shamir. We have recovery keys; we're going to use them
// as the new barrier key
if err := c.barrier.Rekey(ctx, recoveryKey); err != nil {
return nil, errwrap.Wrapf("error rekeying barrier during migration: {{err}}", err)
}
if err := c.barrier.Delete(ctx, StoredBarrierKeysPath); err != nil {
// Don't actually exit here as successful deletion isn't critical
c.logger.Error("error deleting stored barrier keys after migration; continuing anyways", "error", err)
}
masterKey = recoveryKey
default:
return nil, errors.New("unhandled migration case (shamir to shamir)")
}
// At this point we've swapped things around and need to ensure we
@ -1700,12 +1731,20 @@ func (c *Core) PhysicalSealConfigs(ctx context.Context) (*SealConfig, *SealConfi
return barrierConf, recoveryConf, nil
}
func (c *Core) SetSealsForMigration(migrationSeal, newSeal Seal) {
func (c *Core) SetSealsForMigration(migrationSeal, newSeal, unwrapSeal Seal) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.migrationSeal = migrationSeal
c.seal = newSeal
c.logger.Warn("entering seal migration mode; Vault will not automatically unseal even if using an autoseal")
c.unwrapSeal = unwrapSeal
if c.unwrapSeal != nil {
c.unwrapSeal.SetCore(c)
}
if newSeal != nil && migrationSeal != nil {
c.migrationSeal = migrationSeal
c.migrationSeal.SetCore(c)
c.seal = newSeal
c.seal.SetCore(c)
c.logger.Warn("entering seal migration mode; Vault will not automatically unseal even if using an autoseal", "from_barrier_type", c.migrationSeal.BarrierType(), "to_barrier_type", c.seal.BarrierType())
}
}
func (c *Core) IsInSealMigration() bool {

View File

@ -8,6 +8,7 @@ import (
)
type TestSeal struct {
Type string
secret []byte
}
@ -15,6 +16,7 @@ var _ Access = (*TestSeal)(nil)
func NewTestSeal(secret []byte) *TestSeal {
return &TestSeal{
Type: Test,
secret: secret,
}
}
@ -28,7 +30,7 @@ func (t *TestSeal) Finalize(_ context.Context) error {
}
func (t *TestSeal) SealType() string {
return Test
return t.Type
}
func (t *TestSeal) KeyID() string {

View File

@ -303,30 +303,6 @@ func (d *autoSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
return conf.Clone(), nil
}
func (d *autoSeal) RecoveryKey(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 recovery keys: {{err}}", err)
}
pt, err := d.Decrypt(ctx, blobInfo)
if err != nil {
return nil, errwrap.Wrapf("failed to decrypt encrypted recovery keys: {{err}}", err)
}
return pt, 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 {
@ -377,7 +353,7 @@ func (d *autoSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error {
return fmt.Errorf("recovery key to verify is nil")
}
pt, err := d.RecoveryKey(ctx)
pt, err := d.getRecoveryKeyInternal(ctx)
if err != nil {
return err
}
@ -422,6 +398,34 @@ func (d *autoSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
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.