mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-28 17:22:41 +00:00
Add vault community changes for the Seal HA project. (#22515)
* Seal HA: Use new SealWrappedValue type to abstract seal wrapped values Introduce SealWrappedValue to abstract seal wrapped values. Make SealWrappedValue capable of marshalling into a BlobInfo, when there is plaintext or a single encryption, or to a custom serialization consisting of a header, length and a marshalled MultiWrapValue protobuf. * Vault-13769: Support configuring and using multiple seals for unsealing * Make sealWrapBackend start using multiple seals * Make seal.Access no longer implement wrapping.Wrapper. Instead, add the Encrypt and Decrypt methods to the Access interface. * Make raft snapshot system use funcs SealWrapValue + UnsealWrapValue. Move the snapshot.Sealer implementation to the vault package to avoid circular imports. * Update sealWrapBackend to use multiple seals for encryption. Use all the encryption wrappers when storing seal wrapped values. Try do decrypt using the highest priority wrapper, but try all combinations of encrypted values and wrappers if necessary. * Allow the use of multiple seals for entropy augmentation Add seal_name variable in entropy stanza Add new MultiSourcer to accommodate the new entropy augmentation behavior. * Individually health check each wrapper, and add a sys/seal-backend-status endpoint. * Address a race, and also a failed test mock that I didn't catch * Track partial wrapping failures... ... where one or more but not all access.Encrypts fail for a given write. Note these failures by adding a time ordered UUID storage entry containing the path in a special subdirectory of root storage. Adds a callback pattern to accomplish this, with certain high value writes like initial barrier key storage not allowing a partial failure. The followup work would be to detect return to health and iterate through these storage entries, rewrapping. * Add new data structure to track seal config generation (#4492) * Add new data structure to track seal config generation * Remove import cycle * Fix undefined variable errors * update comment * Update setSeal response * Fix setSealResponse in operator_diagnose * Scope the wrapper health check locks individually (#4491) * Refactor setSeal function in server.go. (#4505) Refactor setSeal function in server.go. * Decouple CreateSecureRandomReaderFunc from seal package. Instead of using a list of seal.SealInfo structs, make CreateSecureRandomReaderFunc use a list of new EntropySourcerInfo structs. This brakes the denpency of package configutil on the seal package. * Move SealGenerationInfo tracking to the seal Access. * Move SealGenerationInfo tracking to the seal Access. The SealGenerationInfo is now kept track by a Seal's Access instead of by the Config object. The access implementation now records the correct generation number on seal wrapped values. * Only store and read SealGenerationInfo if VAULT_ENABLE_SEAL_HA_BETA is true. * Add MultiWrapValue protobuf message MultiWrapValue can be used to keep track of different encryptions of a value. --------- Co-authored-by: Victor Rodriguez <vrizo@hashicorp.com> * Use generation to determine if a seal wrapped value is up-to-date. (#4542) * Add logging to seal Access implementation. * Seal HA buf format run (#4561) * Run buf format. * Add buf.lock to ensure go-kms-wrapping module is imported. * Vault-18958: Add unit tests for config checks * Add safety logic for seal configuration changes * Revert "Add safety logic for seal configuration changes" This reverts commit 7fec48035a5cf274e5a4d98901716d08d766ce90. * changes and tests for checking seal config * add ent tests * remove check for empty name and add type into test cases * add error message for empty name * fix no seals test --------- Co-authored-by: divyapola5 <divya@hashicorp.com> * Handle migrations between single-wrapper and multi-wrapper autoSeals * Extract method SetPhysicalSealConfig. * Extract function physicalSealConfig. The extracted function is the only code now reading SealConfig entries from storage. * Extract function setPhysicalSealConfig. The extracted function is the only code now writing SealConfig entries from storage (except for migration from the old recovery config path). * Move SealConfig to new file vault/seal_config.go. * Add SealConfigType quasy-enumeration. SealConfigType is to serve as the typed values for field SealConfig.Type. * Rename Seal.RecoveryType to RecoverySealConfigType. Make RecoverySealConfigType return a SealConfigType instead of a string. * Rename Seal.BarrierType to BarrierSealConfigType. Make BarrierSealConfigType return a SealConfigType. Remove seal.SealType (really a two-step rename to SealConfigType). * Add Seal methods ClearBarrierConfig and ClearRecoveryConfig. * Handle autoseal <-> multiseal migrations. While going between single-wrapper and multiple-wrapper autoseals are not migrations that require an unwrap seal (such as going from shamir to autoseal), the stored "barrier" SealConfig needs to be updated in these cases. Specifically, the value of SealConfg.Type is "multiseal" for autoSeals that have more than one wrapper; on the other hand, for autoseals with a single wrapper, SealConfig.Type is the type of the wrapper. * Remove error return value from NewAutoSeal constructor. * Automatically rewrap partially seal wrapped values on an interval * Add in rewrapping of partially wrapped values on an interval, regardless of seal health/status. * Don't set SealGenerationInfo Rewrapped flag in the partial rewrap call. * Unexport the SealGenerationInfo's Rewrapped field, add a mutex to it for thread safe access, and add accessor methods for it. * Add a success callback to the manual seal rewrap process that updates the SealGenerationInfo's rewrapped field. This is done via a callback to avoid an import cycle in the SealRewrap code. * Fix a failing seal wrap backend test which was broken by the unexporting of SealGenerationInfo's Rewrapped field. * Nil check the seal rewrap success callback before calling it. * Change SealGenerationInfo rewrapped parameter to an atomic.Bool rather than a sync.RWMutex for simplicity and performance. * Add nil check for SealAccess before updating SealGenerationInfo rewrapped status during seal rewrap call. * Update partial rewrap check interval from 10 seconds to 1 minute. * Update a reference to SealGenerationInfo Rewrapped field to use new getter method. * Fix up some data raciness in partial rewrapping. * Account for possibly nil storage entry when retrieving partially wrapped value. * Allow multi-wrapper autoSeals to include disabled seal wrappers. * Restore propagation of wrapper configuration errors by setSeal. Function setSeal is meant to propagate non KeyNotFound errors returned by calls to configutil.ConfigureWrapper. * Remove unused Access methods SetConfig and Type. * Allow multi-wrapper autoSeals to include disabled seal wrappers. Make it possible for an autoSeal that uses multiple wrappers to include disabled wrappers that can be used to decrypt entries, but are skipped for encryption. e an unwrapSeal when there are disabled seals. * Fix bug with not providing name (#4580) * add suffix to name defaults * add comment * only change name for disabled seal * Only attempt to rewrap partial values when all seals are healthy. * Only attempt to rewrap partial values when all seals are healthy. * Change logging level from info to debug for notice about rewrap skipping based on seal health. * Remove stale TODOs and commented out code. --------- Co-authored-by: rculpepper <rculpepper@hashicorp.com> Co-authored-by: Larroyo <95649169+DeLuci@users.noreply.github.com> Co-authored-by: Scott G. Miller <smiller@hashicorp.com> Co-authored-by: Divya Pola <87338962+divyapola5@users.noreply.github.com> Co-authored-by: Matt Schultz <matt.schultz@hashicorp.com> Co-authored-by: divyapola5 <divya@hashicorp.com> Co-authored-by: Rachel Culpepper <84159930+rculpepper@users.noreply.github.com>
This commit is contained in:
8
buf.lock
Normal file
8
buf.lock
Normal file
@@ -0,0 +1,8 @@
|
||||
# Generated by buf. DO NOT EDIT.
|
||||
version: v1
|
||||
deps:
|
||||
- remote: buf.build
|
||||
owner: hashicorp
|
||||
repository: go-kms-wrapping
|
||||
commit: b117606343c8401082b98ec432af4cce
|
||||
digest: shake256:6d6ec23f81669bf1d380b0783e6b4b86805f28733aed46222e7358441402b71760689ea10b45592db98caa3215a115120e03b1319192dfc918f966ccdc845715
|
||||
5
buf.yaml
5
buf.yaml
@@ -2,6 +2,11 @@
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
version: v1
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
deps:
|
||||
- buf.build/hashicorp/go-kms-wrapping
|
||||
lint:
|
||||
ignore_only:
|
||||
ENUM_VALUE_PREFIX:
|
||||
|
||||
@@ -137,10 +137,7 @@ func testVaultServerAllBackends(tb testing.TB) (*api.Client, func()) {
|
||||
// the function returns a client, the recovery keys, and a closer function
|
||||
func testVaultServerAutoUnseal(tb testing.TB) (*api.Client, []string, func()) {
|
||||
testSeal, _ := seal.NewTestSeal(nil)
|
||||
autoSeal, err := vault.NewAutoSeal(testSeal)
|
||||
if err != nil {
|
||||
tb.Fatal("unable to create autoseal", err)
|
||||
}
|
||||
autoSeal := vault.NewAutoSeal(testSeal)
|
||||
return testVaultServerUnsealWithKVVersionWithSeal(tb, "1", autoSeal)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
"github.com/hashicorp/go-kms-wrapping/entropy/v2"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
@@ -70,7 +70,7 @@ func (c *OperatorDiagnoseCommand) Synopsis() string {
|
||||
|
||||
func (c *OperatorDiagnoseCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault operator diagnose
|
||||
Usage: vault operator diagnose
|
||||
|
||||
This command troubleshoots Vault startup issues, such as TLS configuration or
|
||||
auto-unseal. It should be run using the same environment variables and configuration
|
||||
@@ -78,7 +78,7 @@ Usage: vault operator diagnose
|
||||
reproduced.
|
||||
|
||||
Start diagnose with a configuration file:
|
||||
|
||||
|
||||
$ vault operator diagnose -config=/etc/vault/config.hcl
|
||||
|
||||
Perform a diagnostic check while Vault is still running:
|
||||
@@ -432,31 +432,26 @@ func (c *OperatorDiagnoseCommand) offlineDiagnostics(ctx context.Context) error
|
||||
})
|
||||
|
||||
sealcontext, sealspan := diagnose.StartSpan(ctx, "Create Vault Server Configuration Seals")
|
||||
var seals []vault.Seal
|
||||
var sealConfigError error
|
||||
|
||||
barrierSeal, barrierWrapper, unwrapSeal, seals, sealConfigError, err := setSeal(server, config, make([]string, 0), make(map[string]string))
|
||||
// Check error here
|
||||
var setSealResponse *SetSealResponse
|
||||
existingSealGenerationInfo, err := vault.PhysicalSealGenInfo(sealcontext, *backend)
|
||||
if err != nil {
|
||||
diagnose.Fail(sealcontext, fmt.Sprintf("Unable to get Seal genration information from storage: %s.", err.Error()))
|
||||
goto SEALFAIL
|
||||
}
|
||||
|
||||
setSealResponse, err = setSeal(server, config, make([]string, 0), make(map[string]string), existingSealGenerationInfo, server.logger)
|
||||
if err != nil {
|
||||
diagnose.Advise(ctx, "For assistance with the seal stanza, see the Vault configuration documentation.")
|
||||
diagnose.Fail(sealcontext, fmt.Sprintf("Seal creation resulted in the following error: %s.", err.Error()))
|
||||
goto SEALFAIL
|
||||
}
|
||||
if sealConfigError != nil {
|
||||
diagnose.Fail(sealcontext, "Seal could not be configured: seals may already be initialized.")
|
||||
goto SEALFAIL
|
||||
}
|
||||
|
||||
for _, seal := range seals {
|
||||
// There is always one nil seal. We need to skip it so we don't start an empty Finalize-Seal-Shamir
|
||||
// section.
|
||||
if seal == nil {
|
||||
continue
|
||||
}
|
||||
for _, seal := range setSealResponse.getCreatedSeals() {
|
||||
seal := seal // capture range variable
|
||||
// Ensure that the seal finalizer is called, even if using verify-only
|
||||
defer func(seal *vault.Seal) {
|
||||
sealType := diagnose.CapitalizeFirstLetter((*seal).BarrierType().String())
|
||||
sealType := diagnose.CapitalizeFirstLetter((*seal).BarrierSealConfigType().String())
|
||||
finalizeSealContext, finalizeSealSpan := diagnose.StartSpan(ctx, "Finalize "+sealType+" Seal")
|
||||
err = (*seal).Finalize(finalizeSealContext)
|
||||
if err != nil {
|
||||
@@ -465,16 +460,26 @@ func (c *OperatorDiagnoseCommand) offlineDiagnostics(ctx context.Context) error
|
||||
finalizeSealSpan.End()
|
||||
}
|
||||
finalizeSealSpan.End()
|
||||
}(&seal)
|
||||
}(seal)
|
||||
}
|
||||
|
||||
if barrierSeal == nil {
|
||||
if setSealResponse.sealConfigError != nil {
|
||||
diagnose.Fail(sealcontext, "Seal could not be configured: seals may already be initialized.")
|
||||
} else if setSealResponse.barrierSeal == nil {
|
||||
diagnose.Fail(sealcontext, "Could not create barrier seal. No error was generated, but it is likely that the seal stanza is misconfigured. For guidance, see Vault's configuration documentation on the seal stanza.")
|
||||
}
|
||||
|
||||
SEALFAIL:
|
||||
sealspan.End()
|
||||
|
||||
var barrierSeal vault.Seal
|
||||
var unwrapSeal vault.Seal
|
||||
|
||||
if setSealResponse != nil {
|
||||
barrierSeal = setSealResponse.barrierSeal
|
||||
unwrapSeal = setSealResponse.unwrapSeal
|
||||
}
|
||||
|
||||
diagnose.Test(ctx, "Check Transit Seal TLS", func(ctx context.Context) error {
|
||||
var checkSealTransit bool
|
||||
for _, seal := range config.Seals {
|
||||
@@ -531,9 +536,20 @@ SEALFAIL:
|
||||
var secureRandomReader io.Reader
|
||||
// prepare a secure random reader for core
|
||||
randReaderTestName := "Initialize Randomness for Core"
|
||||
secureRandomReader, err = configutil.CreateSecureRandomReaderFunc(config.SharedConfig, barrierWrapper)
|
||||
var sources []*configutil.EntropySourcerInfo
|
||||
if barrierSeal != nil {
|
||||
for _, sealInfo := range barrierSeal.GetAccess().GetEnabledSealInfoByPriority() {
|
||||
if s, ok := sealInfo.Wrapper.(entropy.Sourcer); ok {
|
||||
sources = append(sources, &configutil.EntropySourcerInfo{
|
||||
Sourcer: s,
|
||||
Name: sealInfo.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
secureRandomReader, err = configutil.CreateSecureRandomReaderFunc(config.SharedConfig, sources, server.logger)
|
||||
if err != nil {
|
||||
return diagnose.SpotError(ctx, randReaderTestName, fmt.Errorf("Could not initialize randomness for core: %w.", err))
|
||||
return diagnose.SpotError(ctx, randReaderTestName, fmt.Errorf("could not initialize randomness for core: %w", err))
|
||||
}
|
||||
diagnose.SpotOk(ctx, randReaderTestName, "")
|
||||
coreConfig = createCoreConfig(server, config, *backend, configSR, barrierSeal, unwrapSeal, metricsHelper, metricSink, secureRandomReader)
|
||||
@@ -675,7 +691,7 @@ SEALFAIL:
|
||||
if barrierSeal == nil {
|
||||
return fmt.Errorf("Diagnose could not create a barrier seal object.")
|
||||
}
|
||||
if barrierSeal.BarrierType() == wrapping.WrapperTypeShamir {
|
||||
if barrierSeal.BarrierSealConfigType() == vault.SealConfigTypeShamir {
|
||||
diagnose.Skipped(ctx, "Skipping barrier encryption test. Only supported for auto-unseal.")
|
||||
return nil
|
||||
}
|
||||
@@ -684,11 +700,25 @@ SEALFAIL:
|
||||
return fmt.Errorf("Diagnose could not create unique UUID for unsealing.")
|
||||
}
|
||||
barrierEncValue := "diagnose-" + barrierUUID
|
||||
ciphertext, err := barrierWrapper.Encrypt(ctx, []byte(barrierEncValue), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error encrypting with seal barrier: %w.", err)
|
||||
ciphertext, errMap := barrierSeal.GetAccess().Encrypt(ctx, []byte(barrierEncValue), nil)
|
||||
if len(errMap) > 0 {
|
||||
var sealErrors []error
|
||||
for name, err := range errMap {
|
||||
sealErrors = append(sealErrors, fmt.Errorf("error encrypting with seal %q: %w", name, err))
|
||||
}
|
||||
if ciphertext == nil {
|
||||
// Full failure
|
||||
if len(sealErrors) == 1 {
|
||||
return sealErrors[0]
|
||||
} else {
|
||||
return fmt.Errorf("complete seal encryption failure: %w", errors.Join())
|
||||
}
|
||||
} else {
|
||||
// Partial failure
|
||||
return fmt.Errorf("partial seal encryption failure: %w", errors.Join())
|
||||
}
|
||||
}
|
||||
plaintext, err := barrierWrapper.Decrypt(ctx, ciphertext, nil)
|
||||
plaintext, _, err := barrierSeal.GetAccess().Decrypt(ctx, ciphertext, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error decrypting with seal barrier: %w", err)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -24,6 +25,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-kms-wrapping/entropy/v2"
|
||||
|
||||
systemd "github.com/coreos/go-systemd/daemon"
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
@@ -62,7 +66,6 @@ import (
|
||||
"github.com/hashicorp/vault/version"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/go-testing-interface"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/posener/complete"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"go.uber.org/atomic"
|
||||
@@ -547,7 +550,6 @@ func (c *ServerCommand) runRecoveryMode() int {
|
||||
|
||||
var barrierSeal vault.Seal
|
||||
var sealConfigError error
|
||||
var wrapper wrapping.Wrapper
|
||||
|
||||
if len(config.Seals) == 0 {
|
||||
config.Seals = append(config.Seals, &configutil.KMS{Type: wrapping.WrapperTypeShamir.String()})
|
||||
@@ -558,42 +560,26 @@ func (c *ServerCommand) runRecoveryMode() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
configSeal := config.Seals[0]
|
||||
sealType := wrapping.WrapperTypeShamir.String()
|
||||
if !configSeal.Disabled && os.Getenv("VAULT_SEAL_TYPE") != "" {
|
||||
sealType = os.Getenv("VAULT_SEAL_TYPE")
|
||||
configSeal.Type = sealType
|
||||
} else {
|
||||
sealType = configSeal.Type
|
||||
existingSealGenenrationInfo, err := vault.PhysicalSealGenInfo(context.Background(), backend)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error getting seal generation info: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
infoKeys = append(infoKeys, "Seal Type")
|
||||
info["Seal Type"] = sealType
|
||||
|
||||
var seal vault.Seal
|
||||
defaultSeal := vault.NewDefaultSeal(vaultseal.NewAccess(aeadwrapper.NewShamirWrapper()))
|
||||
sealLogger := c.logger.ResetNamed(fmt.Sprintf("seal.%s", sealType))
|
||||
wrapper, sealConfigError = configutil.ConfigureWrapper(configSeal, &infoKeys, &info, sealLogger)
|
||||
if sealConfigError != nil {
|
||||
if !errwrap.ContainsType(sealConfigError, new(logical.KeyNotFoundError)) {
|
||||
c.UI.Error(fmt.Sprintf(
|
||||
"Error parsing Seal configuration: %s", sealConfigError))
|
||||
return 1
|
||||
}
|
||||
setSealResponse, err := setSeal(c, config, infoKeys, info, existingSealGenenrationInfo, c.logger)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if wrapper == nil {
|
||||
seal = defaultSeal
|
||||
} else {
|
||||
seal, err = vault.NewAutoSeal(vaultseal.NewAccess(wrapper))
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("error creating auto seal: %v", err))
|
||||
}
|
||||
if setSealResponse.barrierSeal == nil {
|
||||
c.UI.Error(fmt.Sprintf("Error setting up seal: %v", setSealResponse.sealConfigError))
|
||||
return 1
|
||||
}
|
||||
barrierSeal = seal
|
||||
barrierSeal = setSealResponse.barrierSeal
|
||||
|
||||
// Ensure that the seal finalizer is called, even if using verify-only
|
||||
defer func() {
|
||||
err = seal.Finalize(context.Background())
|
||||
err = barrierSeal.Finalize(context.Background())
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error finalizing seals: %v", err))
|
||||
}
|
||||
@@ -1237,19 +1223,19 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
infoKeys = append(infoKeys, expKey)
|
||||
}
|
||||
|
||||
barrierSeal, barrierWrapper, unwrapSeal, seals, sealConfigError, err := setSeal(c, config, infoKeys, info)
|
||||
// Check error here
|
||||
existingSealGenenrationInfo, err := vault.PhysicalSealGenInfo(context.Background(), backend)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error getting seal generation info: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
setSealResponse, err := setSeal(c, config, infoKeys, info, existingSealGenenrationInfo, c.logger)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, seal := range seals {
|
||||
// There is always one nil seal. We need to skip it so we don't start an empty Finalize-Seal-Shamir
|
||||
// section.
|
||||
if seal == nil {
|
||||
continue
|
||||
}
|
||||
for _, seal := range setSealResponse.getCreatedSeals() {
|
||||
seal := seal // capture range variable
|
||||
// Ensure that the seal finalizer is called, even if using verify-only
|
||||
defer func(seal *vault.Seal) {
|
||||
@@ -1257,22 +1243,32 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error finalizing seals: %v", err))
|
||||
}
|
||||
}(&seal)
|
||||
}(seal)
|
||||
}
|
||||
|
||||
if barrierSeal == nil {
|
||||
if setSealResponse.barrierSeal == nil {
|
||||
c.UI.Error("Could not create barrier seal! Most likely proper Seal configuration information was not set, but no error was generated.")
|
||||
return 1
|
||||
}
|
||||
|
||||
// prepare a secure random reader for core
|
||||
secureRandomReader, err := configutil.CreateSecureRandomReaderFunc(config.SharedConfig, barrierWrapper)
|
||||
entropyAugLogger := c.logger.Named("entropy-augmentation")
|
||||
var entropySources []*configutil.EntropySourcerInfo
|
||||
for _, sealInfo := range setSealResponse.barrierSeal.GetAccess().GetEnabledSealInfoByPriority() {
|
||||
if s, ok := sealInfo.Wrapper.(entropy.Sourcer); ok {
|
||||
entropySources = append(entropySources, &configutil.EntropySourcerInfo{
|
||||
Sourcer: s,
|
||||
Name: sealInfo.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
secureRandomReader, err := configutil.CreateSecureRandomReaderFunc(config.SharedConfig, entropySources, entropyAugLogger)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
coreConfig := createCoreConfig(c, config, backend, configSR, barrierSeal, unwrapSeal, metricsHelper, metricSink, secureRandomReader)
|
||||
coreConfig := createCoreConfig(c, config, backend, configSR, setSealResponse.barrierSeal, setSealResponse.unwrapSeal, metricsHelper, metricSink, secureRandomReader)
|
||||
if c.flagDevThreeNode {
|
||||
return c.enableThreeNodeDevCluster(&coreConfig, info, infoKeys, c.flagDevListenAddr, os.Getenv("VAULT_DEV_TEMP_DIR"))
|
||||
}
|
||||
@@ -1545,7 +1541,7 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
if sealConfigError != nil {
|
||||
if setSealResponse.sealConfigError != nil {
|
||||
init, err := core.InitializedLocally(context.Background())
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error checking if core is initialized: %v", err))
|
||||
@@ -1899,6 +1895,7 @@ func (c *ServerCommand) enableDev(core *vault.Core, coreConfig *vault.CoreConfig
|
||||
barrierConfig := &vault.SealConfig{
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
Name: "shamir",
|
||||
}
|
||||
|
||||
if core.SealAccess().RecoveryKeySupported() {
|
||||
@@ -2514,82 +2511,234 @@ func CheckStorageMigration(b physical.Backend) (*StorageMigrationStatus, error)
|
||||
return &status, nil
|
||||
}
|
||||
|
||||
// setSeal return barrierSeal, barrierWrapper, unwrapSeal, and all the created seals from the configs so we can close them in Run
|
||||
// The two errors are the sealConfigError and the regular error
|
||||
func setSeal(c *ServerCommand, config *server.Config, infoKeys []string, info map[string]string) (vault.Seal, wrapping.Wrapper, vault.Seal, []vault.Seal, error, error) {
|
||||
var barrierSeal vault.Seal
|
||||
var unwrapSeal vault.Seal
|
||||
type SetSealResponse struct {
|
||||
barrierSeal vault.Seal
|
||||
unwrapSeal vault.Seal
|
||||
|
||||
var sealConfigError error
|
||||
var wrapper wrapping.Wrapper
|
||||
var barrierWrapper wrapping.Wrapper
|
||||
// sealConfigError is present if there was an error configuring wrappers, other than KeyNotFound.
|
||||
sealConfigError error
|
||||
}
|
||||
|
||||
func (r *SetSealResponse) getCreatedSeals() []*vault.Seal {
|
||||
var ret []*vault.Seal
|
||||
if r.barrierSeal != nil {
|
||||
ret = append(ret, &r.barrierSeal)
|
||||
}
|
||||
if r.unwrapSeal != nil {
|
||||
ret = append(ret, &r.unwrapSeal)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// setSeal return barrierSeal, barrierWrapper, unwrapSeal, all the created seals, and all the provided seals from the configs so we can close them in Run
|
||||
// The two errors are the sealConfigError and the regular error
|
||||
func setSeal(c *ServerCommand, config *server.Config, infoKeys []string, info map[string]string, existingSealGenerationInfo *vaultseal.SealGenerationInfo, sealLogger hclog.Logger) (*SetSealResponse, error) {
|
||||
if c.flagDevAutoSeal {
|
||||
var err error
|
||||
access, _ := vaultseal.NewTestSeal(nil)
|
||||
barrierSeal, err = vault.NewAutoSeal(access)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
return barrierSeal, nil, nil, nil, nil, nil
|
||||
barrierSeal := vault.NewAutoSeal(access)
|
||||
|
||||
return &SetSealResponse{barrierSeal: barrierSeal}, nil
|
||||
}
|
||||
|
||||
// Handle the case where no seal is provided
|
||||
switch len(config.Seals) {
|
||||
case 0:
|
||||
config.Seals = append(config.Seals, &configutil.KMS{Type: wrapping.WrapperTypeShamir.String()})
|
||||
config.Seals = append(config.Seals, &configutil.KMS{
|
||||
Type: vault.SealConfigTypeShamir.String(),
|
||||
Priority: 1,
|
||||
Name: "shamir",
|
||||
})
|
||||
case 1:
|
||||
// If there's only one seal and it's disabled assume they want to
|
||||
// migrate to a shamir seal and simply didn't provide it
|
||||
if config.Seals[0].Disabled {
|
||||
config.Seals = append(config.Seals, &configutil.KMS{Type: wrapping.WrapperTypeShamir.String()})
|
||||
config.Seals = append(config.Seals, &configutil.KMS{
|
||||
Type: vault.SealConfigTypeShamir.String(),
|
||||
Priority: 1,
|
||||
Name: "shamir",
|
||||
})
|
||||
}
|
||||
}
|
||||
var createdSeals []vault.Seal = make([]vault.Seal, len(config.Seals))
|
||||
|
||||
var sealConfigError error
|
||||
recordSealConfigError := func(err error) {
|
||||
sealConfigError = errors.Join(sealConfigError, err)
|
||||
}
|
||||
enabledSealInfos := make([]vaultseal.SealInfo, 0)
|
||||
disabledSealInfos := make([]vaultseal.SealInfo, 0)
|
||||
allSealKmsConfigs := make([]*configutil.KMS, 0)
|
||||
|
||||
type infoKeysAndMap struct {
|
||||
keys []string
|
||||
theMap map[string]string
|
||||
}
|
||||
sealWrapperInfoKeysMap := make(map[string]infoKeysAndMap)
|
||||
|
||||
for _, configSeal := range config.Seals {
|
||||
sealType := wrapping.WrapperTypeShamir.String()
|
||||
if !configSeal.Disabled && os.Getenv("VAULT_SEAL_TYPE") != "" {
|
||||
sealType = os.Getenv("VAULT_SEAL_TYPE")
|
||||
configSeal.Type = sealType
|
||||
} else {
|
||||
sealType = configSeal.Type
|
||||
sealTypeEnvVarName := "VAULT_SEAL_TYPE"
|
||||
if configSeal.Priority > 1 {
|
||||
sealTypeEnvVarName = sealTypeEnvVarName + "_" + configSeal.Name
|
||||
}
|
||||
|
||||
var seal vault.Seal
|
||||
sealLogger := c.logger.ResetNamed(fmt.Sprintf("seal.%s", sealType))
|
||||
defaultSeal := vault.NewDefaultSeal(vaultseal.NewAccess(aeadwrapper.NewShamirWrapper()))
|
||||
var sealInfoKeys []string
|
||||
sealInfoMap := map[string]string{}
|
||||
wrapper, sealConfigError = configutil.ConfigureWrapper(configSeal, &sealInfoKeys, &sealInfoMap, sealLogger)
|
||||
if sealConfigError != nil {
|
||||
if !errwrap.ContainsType(sealConfigError, new(logical.KeyNotFoundError)) {
|
||||
return barrierSeal, barrierWrapper, unwrapSeal, createdSeals, sealConfigError, fmt.Errorf(
|
||||
"Error parsing Seal configuration: %s", sealConfigError)
|
||||
if !configSeal.Disabled && os.Getenv(sealTypeEnvVarName) != "" {
|
||||
sealType := os.Getenv(sealTypeEnvVarName)
|
||||
configSeal.Type = sealType
|
||||
}
|
||||
|
||||
sealLogger := c.logger.ResetNamed(fmt.Sprintf("seal.%s", configSeal.Type))
|
||||
|
||||
var wrapperInfoKeys []string
|
||||
wrapperInfoMap := map[string]string{}
|
||||
wrapper, wrapperConfigError := configutil.ConfigureWrapper(configSeal, &wrapperInfoKeys, &wrapperInfoMap, sealLogger)
|
||||
if wrapperConfigError != nil {
|
||||
// It seems that we are checking for this particular error here is to distinguish between a
|
||||
// mis-configured seal vs one that fails for another reason. Apparently the only other reason is
|
||||
// a key not found error. It seems the intention is for the key not found error to be returned
|
||||
// as a seal specific error later
|
||||
if !errwrap.ContainsType(wrapperConfigError, new(logical.KeyNotFoundError)) {
|
||||
return nil, fmt.Errorf("error parsing Seal configuration: %s", wrapperConfigError)
|
||||
} else {
|
||||
sealLogger.Error("error configuring seal", "name", configSeal.Name, "err", wrapperConfigError)
|
||||
recordSealConfigError(wrapperConfigError)
|
||||
}
|
||||
}
|
||||
if wrapper == nil {
|
||||
seal = defaultSeal
|
||||
} else {
|
||||
var err error
|
||||
seal, err = vault.NewAutoSeal(vaultseal.NewAccess(wrapper))
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
// for some reason configureWrapper in kms.go returns nil wrapper and nil error for wrapping.WrapperTypeShamir
|
||||
if wrapper == nil && wrapperConfigError == nil {
|
||||
wrapper = aeadwrapper.NewShamirWrapper()
|
||||
}
|
||||
infoPrefix := ""
|
||||
|
||||
sealInfo := vaultseal.SealInfo{
|
||||
Wrapper: wrapper,
|
||||
Priority: configSeal.Priority,
|
||||
Name: configSeal.Name,
|
||||
SealConfigType: configSeal.Type,
|
||||
Disabled: configSeal.Disabled,
|
||||
}
|
||||
|
||||
if configSeal.Disabled {
|
||||
unwrapSeal = seal
|
||||
infoPrefix = "Old "
|
||||
disabledSealInfos = append(disabledSealInfos, sealInfo)
|
||||
} else {
|
||||
barrierSeal = seal
|
||||
barrierWrapper = wrapper
|
||||
enabledSealInfos = append(enabledSealInfos, sealInfo)
|
||||
}
|
||||
for _, k := range sealInfoKeys {
|
||||
infoKeys = append(infoKeys, infoPrefix+k)
|
||||
info[infoPrefix+k] = sealInfoMap[k]
|
||||
allSealKmsConfigs = append(allSealKmsConfigs, configSeal)
|
||||
|
||||
sealWrapperInfoKeysMap[sealInfo.Name] = infoKeysAndMap{
|
||||
keys: wrapperInfoKeys,
|
||||
theMap: wrapperInfoMap,
|
||||
}
|
||||
createdSeals = append(createdSeals, seal)
|
||||
}
|
||||
return barrierSeal, barrierWrapper, unwrapSeal, createdSeals, sealConfigError, nil
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Set the info keys, this modifies the function arguments `info` and `infoKeys`
|
||||
// TODO(SEALHA): Why are we doing this? What is its use?
|
||||
appendWrapperInfoKeys := func(prefix string, sealInfos []vaultseal.SealInfo) {
|
||||
if len(sealInfos) > 0 {
|
||||
useName := false
|
||||
if len(sealInfos) > 1 {
|
||||
useName = true
|
||||
}
|
||||
for _, sealInfo := range sealInfos {
|
||||
if useName {
|
||||
prefix = fmt.Sprintf("%s %s ", prefix, sealInfo.Name)
|
||||
}
|
||||
for _, k := range sealWrapperInfoKeysMap[sealInfo.Name].keys {
|
||||
infoKeys = append(infoKeys, prefix+k)
|
||||
info[prefix+k] = sealWrapperInfoKeysMap[sealInfo.Name].theMap[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
appendWrapperInfoKeys("", enabledSealInfos)
|
||||
appendWrapperInfoKeys("Old", disabledSealInfos)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Compute seal generation
|
||||
|
||||
sealGenerationInfo := c.computeSealGenerationInfo(existingSealGenerationInfo, allSealKmsConfigs)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Create the Seals
|
||||
|
||||
containsShamir := func(sealInfos []vaultseal.SealInfo) bool {
|
||||
for _, si := range sealInfos {
|
||||
if vault.SealConfigTypeShamir.IsSameAs(si.SealConfigType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
sealHaBetaEnabled, err := server.IsSealHABetaEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var barrierSeal vault.Seal
|
||||
var unwrapSeal vault.Seal
|
||||
|
||||
switch {
|
||||
case len(enabledSealInfos) == 0:
|
||||
return nil, errors.New("no enabled Seals in configuration")
|
||||
|
||||
case containsShamir(enabledSealInfos) && containsShamir(disabledSealInfos):
|
||||
return nil, errors.New("cannot migrate from one Shamir seal to another Shamir seal")
|
||||
|
||||
case len(enabledSealInfos) == 1 && containsShamir(enabledSealInfos):
|
||||
// The barrier seal is Shamir. If there are any disabled seals, then we put them all in the same
|
||||
// autoSeal.
|
||||
barrierSeal = vault.NewDefaultSeal(vaultseal.NewAccess(sealLogger, sealGenerationInfo, enabledSealInfos))
|
||||
if len(disabledSealInfos) > 0 {
|
||||
unwrapSeal = vault.NewAutoSeal(vaultseal.NewAccess(sealLogger, sealGenerationInfo, disabledSealInfos))
|
||||
}
|
||||
|
||||
case len(disabledSealInfos) == 1 && containsShamir(disabledSealInfos):
|
||||
// The unwrap seal is Shamir, we are migrating to an autoSeal.
|
||||
barrierSeal = vault.NewAutoSeal(vaultseal.NewAccess(sealLogger, sealGenerationInfo, enabledSealInfos))
|
||||
unwrapSeal = vault.NewDefaultSeal(vaultseal.NewAccess(sealLogger, sealGenerationInfo, disabledSealInfos))
|
||||
|
||||
case sealHaBetaEnabled:
|
||||
// We know we are not using Shamir seal, that we are not migrating away from one, and seal HA is enabled,
|
||||
// so just put enabled and disabled wrappers on the same seal Access
|
||||
allSealInfos := append(enabledSealInfos, disabledSealInfos...)
|
||||
barrierSeal = vault.NewAutoSeal(vaultseal.NewAccess(sealLogger, sealGenerationInfo, allSealInfos))
|
||||
|
||||
case len(enabledSealInfos) == 1:
|
||||
// We may have multiple seals disabled, but we know Shamir is not one of them.
|
||||
barrierSeal = vault.NewAutoSeal(vaultseal.NewAccess(sealLogger, sealGenerationInfo, enabledSealInfos))
|
||||
if len(disabledSealInfos) > 0 {
|
||||
unwrapSeal = vault.NewAutoSeal(vaultseal.NewAccess(sealLogger, sealGenerationInfo, disabledSealInfos))
|
||||
}
|
||||
|
||||
default:
|
||||
// We know there are multiple enabled seals and that the seal HA beta is not enabled
|
||||
return nil, errors.New("error: more than one enabled seal found")
|
||||
}
|
||||
|
||||
return &SetSealResponse{
|
||||
barrierSeal: barrierSeal,
|
||||
unwrapSeal: unwrapSeal,
|
||||
sealConfigError: sealConfigError,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ServerCommand) computeSealGenerationInfo(existingSealGenInfo *vaultseal.SealGenerationInfo, sealConfigs []*configutil.KMS) *vaultseal.SealGenerationInfo {
|
||||
if existingSealGenInfo == nil {
|
||||
return &vaultseal.SealGenerationInfo{
|
||||
Generation: 1,
|
||||
Seals: sealConfigs,
|
||||
}
|
||||
}
|
||||
if cmp.Equal(existingSealGenInfo.Seals, sealConfigs) {
|
||||
return existingSealGenInfo
|
||||
}
|
||||
|
||||
generation := existingSealGenInfo.Generation + 1
|
||||
c.logger.Info("incrementing seal config gen, new generation: ", "generation", generation)
|
||||
|
||||
// If the stored copy doesn't match the current configuration, we introduce a new generation which keeps track if a rewrap of all CSPs and seal wrapped values has completed (initially false).
|
||||
return &vaultseal.SealGenerationInfo{
|
||||
Generation: generation,
|
||||
Seals: sealConfigs,
|
||||
}
|
||||
}
|
||||
|
||||
func initHaBackend(c *ServerCommand, config *server.Config, coreConfig *vault.CoreConfig, backend physical.Backend) (bool, error) {
|
||||
|
||||
@@ -505,6 +505,10 @@ func CheckConfig(c *Config, e error) (*Config, error) {
|
||||
|
||||
sealMap := make(map[string]*configutil.KMS)
|
||||
for _, seal := range c.Seals {
|
||||
if seal.Name == "" {
|
||||
return nil, errors.New("seals: seal name is empty")
|
||||
}
|
||||
|
||||
if _, ok := sealMap[seal.Name]; ok {
|
||||
return nil, errors.New("seals: seal names must be unique")
|
||||
}
|
||||
|
||||
9
command/server/config_stubs_oss.go
Normal file
9
command/server/config_stubs_oss.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !enterprise
|
||||
|
||||
package server
|
||||
|
||||
//go:generate go run github.com/hashicorp/vault/tools/stubmaker
|
||||
|
||||
func IsSealHABetaEnabled() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/internalshared/configutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -220,3 +221,76 @@ func Test_parseDevTLSConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "no-seals-configured",
|
||||
config: &Config{SharedConfig: &configutil.SharedConfig{Seals: []*configutil.KMS{}}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "seal-with-empty-name",
|
||||
config: &Config{SharedConfig: &configutil.SharedConfig{
|
||||
Seals: []*configutil.KMS{
|
||||
{
|
||||
Type: "awskms",
|
||||
Disabled: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "seals-with-unique-names",
|
||||
config: &Config{SharedConfig: &configutil.SharedConfig{
|
||||
Seals: []*configutil.KMS{
|
||||
{
|
||||
Type: "awskms",
|
||||
Disabled: false,
|
||||
Name: "enabled-awskms",
|
||||
},
|
||||
{
|
||||
Type: "awskms",
|
||||
Disabled: true,
|
||||
Name: "disabled-awskms",
|
||||
},
|
||||
},
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "seals-with-same-names",
|
||||
config: &Config{SharedConfig: &configutil.SharedConfig{
|
||||
Seals: []*configutil.KMS{
|
||||
{
|
||||
Type: "awskms",
|
||||
Disabled: false,
|
||||
Name: "awskms",
|
||||
},
|
||||
{
|
||||
Type: "awskms",
|
||||
Disabled: true,
|
||||
Name: "awskms",
|
||||
},
|
||||
},
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := CheckConfig(tt.config, nil)
|
||||
if tt.expectError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1135,7 +1135,7 @@ func testParseSeals(t *testing.T) {
|
||||
"default_hmac_key_label": "vault-hsm-hmac-key",
|
||||
"generate_key": "true",
|
||||
},
|
||||
Name: "pkcs11",
|
||||
Name: "pkcs11-disabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
88
command/server/config_util_test.go
Normal file
88
command/server/config_util_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build !enterprise
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/internalshared/configutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheckSealConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config Config
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "no-seals",
|
||||
config: Config{SharedConfig: &configutil.SharedConfig{Seals: []*configutil.KMS{}}},
|
||||
},
|
||||
{
|
||||
name: "one-seal",
|
||||
config: Config{SharedConfig: &configutil.SharedConfig{Seals: []*configutil.KMS{
|
||||
{
|
||||
Disabled: false,
|
||||
},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "one-disabled-seal",
|
||||
config: Config{SharedConfig: &configutil.SharedConfig{Seals: []*configutil.KMS{
|
||||
{
|
||||
Disabled: true,
|
||||
},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "two-seals-one-disabled",
|
||||
config: Config{SharedConfig: &configutil.SharedConfig{Seals: []*configutil.KMS{
|
||||
{
|
||||
Disabled: false,
|
||||
},
|
||||
{
|
||||
Disabled: true,
|
||||
},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "two-seals-enabled",
|
||||
config: Config{SharedConfig: &configutil.SharedConfig{Seals: []*configutil.KMS{
|
||||
{
|
||||
Disabled: false,
|
||||
},
|
||||
{
|
||||
Disabled: false,
|
||||
},
|
||||
}}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "two-disabled-seals",
|
||||
config: Config{SharedConfig: &configutil.SharedConfig{Seals: []*configutil.KMS{
|
||||
{
|
||||
Disabled: true,
|
||||
},
|
||||
{
|
||||
Disabled: true,
|
||||
},
|
||||
}}},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.config.checkSealConfig()
|
||||
if tt.expectError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
@@ -65,6 +65,7 @@ require (
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/go-zookeeper/zk v1.0.3
|
||||
github.com/gocql/gocql v1.0.0
|
||||
github.com/gofrs/uuid v4.3.0+incompatible
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6
|
||||
@@ -358,7 +359,6 @@ require (
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
|
||||
@@ -68,10 +68,20 @@ func (tss *TransitSealServer) MakeSeal(t testing.T, key string) (vault.Seal, err
|
||||
"key_name": key,
|
||||
"tls_ca_cert": tss.CACertPEMFile,
|
||||
}
|
||||
transitSeal, _, err := configutil.GetTransitKMSFunc(&configutil.KMS{Config: wrapperConfig})
|
||||
transitSealWrapper, _, err := configutil.GetTransitKMSFunc(&configutil.KMS{Config: wrapperConfig})
|
||||
if err != nil {
|
||||
t.Fatalf("error setting wrapper config: %v", err)
|
||||
}
|
||||
|
||||
return vault.NewAutoSeal(seal.NewAccess(transitSeal))
|
||||
access, err := seal.NewAccessFromSealInfo(tss.Logger, 1, true, []seal.SealInfo{
|
||||
{
|
||||
Wrapper: transitSealWrapper,
|
||||
Priority: 1,
|
||||
Name: "transit",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vault.NewAutoSeal(access), nil
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ func handler(props *vault.HandlerProperties) http.Handler {
|
||||
|
||||
mux.Handle("/v1/sys/init", handleSysInit(core))
|
||||
mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core))
|
||||
mux.Handle("/v1/sys/seal-backend-status", handleSysSealBackendStatus(core))
|
||||
mux.Handle("/v1/sys/seal", handleSysSeal(core))
|
||||
mux.Handle("/v1/sys/step-down", handleRequestForwarding(core, handleSysStepDown(core)))
|
||||
mux.Handle("/v1/sys/unseal", handleSysUnseal(core))
|
||||
|
||||
@@ -167,11 +167,11 @@ func validateInitParameters(core *vault.Core, req InitRequest) error {
|
||||
switch core.SealAccess().RecoveryKeySupported() {
|
||||
case true:
|
||||
if len(barrierFlags) > 0 {
|
||||
return fmt.Errorf("parameters %s not applicable to seal type %s", strings.Join(barrierFlags, ","), core.SealAccess().BarrierType())
|
||||
return fmt.Errorf("parameters %s not applicable to seal type %s", strings.Join(barrierFlags, ","), core.SealAccess().BarrierSealConfigType())
|
||||
}
|
||||
default:
|
||||
if len(recoveryFlags) > 0 {
|
||||
return fmt.Errorf("parameters %s not applicable to seal type %s", strings.Join(recoveryFlags, ","), core.SealAccess().BarrierType())
|
||||
return fmt.Errorf("parameters %s not applicable to seal type %s", strings.Join(recoveryFlags, ","), core.SealAccess().BarrierSealConfigType())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -153,10 +153,7 @@ func TestSysInit_Put_ValidateParams(t *testing.T) {
|
||||
|
||||
func TestSysInit_Put_ValidateParams_AutoUnseal(t *testing.T) {
|
||||
testSeal, _ := seal.NewTestSeal(&seal.TestSealOpts{Name: "transit"})
|
||||
autoSeal, err := vault.NewAutoSeal(testSeal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
autoSeal := vault.NewAutoSeal(testSeal)
|
||||
|
||||
// Create the transit server.
|
||||
conf := &vault.CoreConfig{
|
||||
@@ -189,7 +186,8 @@ func TestSysInit_Put_ValidateParams_AutoUnseal(t *testing.T) {
|
||||
testResponseStatus(t, resp, http.StatusBadRequest)
|
||||
body := map[string][]string{}
|
||||
testResponseBody(t, resp, &body)
|
||||
if body["errors"][0] != "parameters secret_shares,secret_threshold not applicable to seal type transit" {
|
||||
if body["errors"][0] != "parameters secret_shares,secret_threshold not applicable to seal type transit" &&
|
||||
body["errors"][0] != "parameters secret_shares,secret_threshold not applicable to seal type test-auto" {
|
||||
t.Fatal(body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,17 @@ func handleSysSealStatus(core *vault.Core) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysSealBackendStatus(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||
return
|
||||
}
|
||||
|
||||
handleSysSealBackendStatusRaw(core, w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
status, err := core.GetSealStatus(ctx)
|
||||
@@ -174,6 +185,17 @@ func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Req
|
||||
respondOk(w, status)
|
||||
}
|
||||
|
||||
func handleSysSealBackendStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
status, err := core.GetSealBackendStatus(ctx)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
respondOk(w, status)
|
||||
}
|
||||
|
||||
// Note: because we didn't provide explicit tagging in the past we can't do it
|
||||
// now because if it then no longer accepts capitalized versions it could break
|
||||
// clients
|
||||
|
||||
@@ -6,11 +6,15 @@ package configutil
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-kms-wrapping/entropy/v2"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
@@ -43,7 +47,13 @@ const (
|
||||
)
|
||||
|
||||
type Entropy struct {
|
||||
Mode EntropyMode
|
||||
Mode EntropyMode
|
||||
SealName string
|
||||
}
|
||||
|
||||
type EntropySourcerInfo struct {
|
||||
Sourcer entropy.Sourcer
|
||||
Name string
|
||||
}
|
||||
|
||||
// KMS contains KMS configuration for the server
|
||||
@@ -121,12 +131,20 @@ func parseKMS(result *[]*KMS, list *ast.ObjectList, blockName string, maxKMS int
|
||||
}
|
||||
|
||||
name := strings.ToLower(key)
|
||||
// ensure that seals of the same type will have unique names for seal migration
|
||||
if disabled {
|
||||
name += "-disabled"
|
||||
}
|
||||
if v, ok := m["name"]; ok {
|
||||
name, ok = v.(string)
|
||||
if !ok {
|
||||
return multierror.Prefix(fmt.Errorf("unable to parse 'name' in kms type %q: unexpected type %T", key, v), fmt.Sprintf("%s.%s", blockName, key))
|
||||
}
|
||||
delete(m, "name")
|
||||
|
||||
if !regexp.MustCompile("^[a-zA-Z0-9-_]+$").MatchString(name) {
|
||||
return multierror.Prefix(errors.New("'name' field can only include alphanumeric characters, hyphens, and underscores"), fmt.Sprintf("%s.%s", blockName, key))
|
||||
}
|
||||
}
|
||||
|
||||
strMap := make(map[string]string, len(m))
|
||||
@@ -199,6 +217,10 @@ func configureWrapper(configKMS *KMS, infoKeys *[]string, info *map[string]strin
|
||||
|
||||
envConfig := GetEnvConfigFunc(configKMS)
|
||||
for name, val := range envConfig {
|
||||
// for the token, config takes precedence over env vars
|
||||
if name == "token" && configKMS.Config[name] != "" {
|
||||
continue
|
||||
}
|
||||
configKMS.Config[name] = val
|
||||
}
|
||||
|
||||
@@ -381,7 +403,7 @@ var GetTransitKMSFunc = func(kms *KMS, opts ...wrapping.Option) (wrapping.Wrappe
|
||||
return wrapper, info, nil
|
||||
}
|
||||
|
||||
func createSecureRandomReader(conf *SharedConfig, wrapper wrapping.Wrapper) (io.Reader, error) {
|
||||
func createSecureRandomReader(_ *SharedConfig, _ []*EntropySourcerInfo, _ hclog.Logger) (io.Reader, error) {
|
||||
return rand.Reader, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/golang/protobuf/proto"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-raftchunking"
|
||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||
"github.com/hashicorp/go-secure-stdlib/tlsutil"
|
||||
@@ -37,7 +36,6 @@ import (
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/cluster"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
"github.com/hashicorp/vault/version"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
@@ -1373,17 +1371,17 @@ func (b *RaftBackend) Peers(ctx context.Context) ([]Peer, error) {
|
||||
|
||||
// SnapshotHTTP is a wrapper for Snapshot that sends the snapshot as an HTTP
|
||||
// response.
|
||||
func (b *RaftBackend) SnapshotHTTP(out *logical.HTTPResponseWriter, access seal.Access) error {
|
||||
func (b *RaftBackend) SnapshotHTTP(out *logical.HTTPResponseWriter, sealer snapshot.Sealer) error {
|
||||
out.Header().Add("Content-Disposition", "attachment")
|
||||
out.Header().Add("Content-Type", "application/gzip")
|
||||
|
||||
return b.Snapshot(out, access)
|
||||
return b.Snapshot(out, sealer)
|
||||
}
|
||||
|
||||
// Snapshot takes a raft snapshot, packages it into a archive file and writes it
|
||||
// to the provided writer. Seal access is used to encrypt the SHASUM file so we
|
||||
// can validate the snapshot was taken using the same root keys or not.
|
||||
func (b *RaftBackend) Snapshot(out io.Writer, access seal.Access) error {
|
||||
func (b *RaftBackend) Snapshot(out io.Writer, sealer snapshot.Sealer) error {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
@@ -1391,15 +1389,7 @@ func (b *RaftBackend) Snapshot(out io.Writer, access seal.Access) error {
|
||||
return errors.New("raft storage is sealed")
|
||||
}
|
||||
|
||||
// If we have access to the seal create a sealer object
|
||||
var s snapshot.Sealer
|
||||
if access != nil {
|
||||
s = &sealer{
|
||||
access: access,
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot.Write(b.logger.Named("snapshot"), b.raft, s, out)
|
||||
return snapshot.Write(b.logger.Named("snapshot"), b.raft, sealer, out)
|
||||
}
|
||||
|
||||
// WriteSnapshotToTemp reads a snapshot archive off the provided reader,
|
||||
@@ -1407,7 +1397,7 @@ func (b *RaftBackend) Snapshot(out io.Writer, access seal.Access) error {
|
||||
// access is used to decrypt the SHASUM file in the archive to ensure this
|
||||
// snapshot has the same root key as the running instance. If the provided
|
||||
// access is nil then it will skip that validation.
|
||||
func (b *RaftBackend) WriteSnapshotToTemp(in io.ReadCloser, access seal.Access) (*os.File, func(), raft.SnapshotMeta, error) {
|
||||
func (b *RaftBackend) WriteSnapshotToTemp(in io.ReadCloser, sealer snapshot.Sealer) (*os.File, func(), raft.SnapshotMeta, error) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
@@ -1416,15 +1406,7 @@ func (b *RaftBackend) WriteSnapshotToTemp(in io.ReadCloser, access seal.Access)
|
||||
return nil, nil, metadata, errors.New("raft storage is sealed")
|
||||
}
|
||||
|
||||
// If we have access to the seal create a sealer object
|
||||
var s snapshot.Sealer
|
||||
if access != nil {
|
||||
s = &sealer{
|
||||
access: access,
|
||||
}
|
||||
}
|
||||
|
||||
snap, cleanup, err := snapshot.WriteToTempFileWithSealer(b.logger.Named("snapshot"), in, &metadata, s)
|
||||
snap, cleanup, err := snapshot.WriteToTempFileWithSealer(b.logger.Named("snapshot"), in, &metadata, sealer)
|
||||
return snap, cleanup, metadata, err
|
||||
}
|
||||
|
||||
@@ -1897,40 +1879,6 @@ func (l *RaftLock) Value() (bool, string, error) {
|
||||
return true, value, nil
|
||||
}
|
||||
|
||||
// sealer implements the snapshot.Sealer interface and is used in the snapshot
|
||||
// process for encrypting/decrypting the SHASUM file in snapshot archives.
|
||||
type sealer struct {
|
||||
access seal.Access
|
||||
}
|
||||
|
||||
// Seal encrypts the data with using the seal access object.
|
||||
func (s sealer) Seal(ctx context.Context, pt []byte) ([]byte, error) {
|
||||
if s.access == nil {
|
||||
return nil, errors.New("no seal access available")
|
||||
}
|
||||
eblob, err := s.access.Encrypt(ctx, pt, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proto.Marshal(eblob)
|
||||
}
|
||||
|
||||
// Open decrypts the data using the seal access object.
|
||||
func (s sealer) Open(ctx context.Context, ct []byte) ([]byte, error) {
|
||||
if s.access == nil {
|
||||
return nil, errors.New("no seal access available")
|
||||
}
|
||||
|
||||
var eblob wrapping.BlobInfo
|
||||
err := proto.Unmarshal(ct, &eblob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.access.Decrypt(ctx, &eblob, nil)
|
||||
}
|
||||
|
||||
// boltOptions returns a bolt.Options struct, suitable for passing to
|
||||
// bolt.Open(), pre-configured with all of our preferred defaults.
|
||||
func boltOptions(path string) *bolt.Options {
|
||||
|
||||
@@ -153,7 +153,7 @@ func (b *AESGCMBarrier) Initialized(ctx context.Context) (bool, error) {
|
||||
|
||||
// Initialize works only if the barrier has not been initialized
|
||||
// and makes use of the given root key.
|
||||
func (b *AESGCMBarrier) Initialize(ctx context.Context, key, sealKey []byte, reader io.Reader) error {
|
||||
func (b *AESGCMBarrier) Initialize(ctx context.Context, key []byte, sealKey []byte, reader io.Reader) error {
|
||||
// Verify the key size
|
||||
min, max := b.KeyLength()
|
||||
if len(key) < min || len(key) > max {
|
||||
@@ -168,7 +168,7 @@ func (b *AESGCMBarrier) Initialize(ctx context.Context, key, sealKey []byte, rea
|
||||
}
|
||||
|
||||
// Generate encryption key
|
||||
encrypt, err := b.GenerateKey(reader)
|
||||
encryptionKey, err := b.GenerateKey(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate encryption key: %w", err)
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func (b *AESGCMBarrier) Initialize(ctx context.Context, key, sealKey []byte, rea
|
||||
keyring, err = keyring.AddKey(&Key{
|
||||
Term: 1,
|
||||
Version: 1,
|
||||
Value: encrypt,
|
||||
Value: encryptionKey,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create keyring: %w", err)
|
||||
@@ -191,7 +191,7 @@ func (b *AESGCMBarrier) Initialize(ctx context.Context, key, sealKey []byte, rea
|
||||
}
|
||||
|
||||
if len(sealKey) > 0 {
|
||||
primary, err := b.aeadFromKey(encrypt)
|
||||
primary, err := b.aeadFromKey(encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
277
vault/core.go
277
vault/core.go
@@ -1103,7 +1103,17 @@ func CreateCore(conf *CoreConfig) (*Core, error) {
|
||||
wrapper := aeadwrapper.NewShamirWrapper()
|
||||
wrapper.SetConfig(context.Background(), awskms.WithLogger(c.logger.Named("shamir")))
|
||||
|
||||
c.seal = NewDefaultSeal(vaultseal.NewAccess(wrapper))
|
||||
access, err := vaultseal.NewAccessFromSealInfo(c.logger, 1, true, []vaultseal.SealInfo{
|
||||
{
|
||||
Wrapper: wrapper,
|
||||
Priority: 1,
|
||||
Name: "shamir",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.seal = NewDefaultSeal(access)
|
||||
}
|
||||
c.seal.SetCore(c)
|
||||
return c, nil
|
||||
@@ -1528,13 +1538,10 @@ func (c *Core) unsealFragment(key []byte, migrate bool) error {
|
||||
func (c *Core) unsealWithRaft(combinedKey []byte) error {
|
||||
ctx := context.Background()
|
||||
|
||||
if c.seal.BarrierType() == wrapping.WrapperTypeShamir {
|
||||
if c.seal.BarrierSealConfigType() == SealConfigTypeShamir {
|
||||
// If this is a legacy shamir seal this serves no purpose but it
|
||||
// doesn't hurt.
|
||||
shamirWrapper, err := c.seal.GetShamirWrapper()
|
||||
if err == nil {
|
||||
err = shamirWrapper.SetAesGcmKeyBytes(combinedKey)
|
||||
}
|
||||
err := c.seal.GetAccess().SetShamirSealKey(combinedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1703,14 +1710,14 @@ func (c *Core) sealMigrated(ctx context.Context) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if existBarrierSealConfig.Type != c.seal.BarrierType().String() {
|
||||
if !c.seal.BarrierSealConfigType().IsSameAs(existBarrierSealConfig.Type) {
|
||||
return false, nil
|
||||
}
|
||||
if c.seal.RecoveryKeySupported() && existRecoverySealConfig.Type != c.seal.RecoveryType() {
|
||||
if c.seal.RecoveryKeySupported() && !SealConfigTypeRecovery.IsSameAs(existRecoverySealConfig.Type) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if c.seal.BarrierType() != c.migrationInfo.seal.BarrierType() {
|
||||
if c.seal.BarrierSealConfigType() != c.migrationInfo.seal.BarrierSealConfigType() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -1736,7 +1743,9 @@ func (c *Core) sealMigrated(ctx context.Context) (bool, error) {
|
||||
// migrateSeal must be called with the stateLock held.
|
||||
func (c *Core) migrateSeal(ctx context.Context) error {
|
||||
if c.migrationInfo == nil {
|
||||
return nil
|
||||
// There is no defaultSeal <-> autoSeal migration, but we may need to
|
||||
// migrate seal configuration from single <-> multi autoSeal
|
||||
return c.migrateMultiSealConfig(ctx)
|
||||
}
|
||||
|
||||
ok, err := c.sealMigrated(ctx)
|
||||
@@ -1754,7 +1763,7 @@ func (c *Core) migrateSeal(ctx context.Context) error {
|
||||
switch {
|
||||
case c.migrationInfo.seal.RecoveryKeySupported() && c.seal.RecoveryKeySupported():
|
||||
c.logger.Info("migrating from one auto-unseal to another", "from",
|
||||
c.migrationInfo.seal.BarrierType(), "to", c.seal.BarrierType())
|
||||
c.migrationInfo.seal.BarrierSealConfigType(), "to", c.seal.BarrierSealConfigType())
|
||||
|
||||
// Set the recovery and barrier keys to be the same.
|
||||
recoveryKey, err := c.migrationInfo.seal.RecoveryKey(ctx)
|
||||
@@ -1776,7 +1785,7 @@ func (c *Core) migrateSeal(ctx context.Context) error {
|
||||
}
|
||||
|
||||
case c.migrationInfo.seal.RecoveryKeySupported():
|
||||
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.BarrierSealConfigType())
|
||||
// Auto to Shamir, since recovery key isn't supported on new seal
|
||||
|
||||
recoveryKey, err := c.migrationInfo.seal.RecoveryKey(ctx)
|
||||
@@ -1785,11 +1794,7 @@ func (c *Core) migrateSeal(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// We have recovery keys; we're going to use them as the new shamir KeK.
|
||||
shamirWrapper, err := c.seal.GetShamirWrapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = shamirWrapper.SetAesGcmKeyBytes(recoveryKey)
|
||||
err = c.seal.GetAccess().SetShamirSealKey(recoveryKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set master key in seal: %w", err)
|
||||
}
|
||||
@@ -1804,7 +1809,7 @@ func (c *Core) migrateSeal(ctx context.Context) error {
|
||||
}
|
||||
|
||||
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.BarrierSealConfigType())
|
||||
// Migration is happening from shamir -> auto. In this case use the shamir
|
||||
// combined key that was used to store the master key as the new recovery key.
|
||||
if err := c.seal.SetRecoveryKey(ctx, c.migrationInfo.unsealKey); err != nil {
|
||||
@@ -1896,7 +1901,7 @@ func (c *Core) unsealInternal(ctx context.Context, masterKey []byte) error {
|
||||
|
||||
// Force a cache bust here, which will also run migration code
|
||||
if c.seal.RecoveryKeySupported() {
|
||||
c.seal.SetRecoveryConfig(ctx, nil)
|
||||
c.seal.ClearRecoveryConfig(ctx)
|
||||
}
|
||||
|
||||
c.standby = false
|
||||
@@ -2383,6 +2388,21 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c
|
||||
if err := c.setupPluginReload(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// store the sealGenInfo
|
||||
sealGenerationInfo := c.seal.GetAccess().GetSealGenerationInfo()
|
||||
err := c.SetPhysicalSealGenInfo(context.Background(), sealGenerationInfo)
|
||||
if err != nil {
|
||||
c.logger.Error("failed to store seal generation info", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !sealGenerationInfo.IsRewrapped() {
|
||||
// Flag migration performed for seal-rewrap later
|
||||
atomic.StoreUint32(c.sealMigrationDone, 1)
|
||||
}
|
||||
|
||||
startPartialSealRewrapping(c)
|
||||
}
|
||||
|
||||
if c.getClusterListener() != nil && (c.ha != nil || shouldStartClusterListener(c)) {
|
||||
@@ -2443,9 +2463,9 @@ func (c *Core) postUnseal(ctx context.Context, ctxCancelFunc context.CancelFunc,
|
||||
}
|
||||
|
||||
// Purge these for safety in case of a rekey
|
||||
_ = c.seal.SetBarrierConfig(ctx, nil)
|
||||
_ = c.seal.ClearBarrierConfig(ctx)
|
||||
if c.seal.RecoveryKeySupported() {
|
||||
_ = c.seal.SetRecoveryConfig(ctx, nil)
|
||||
_ = c.seal.ClearRecoveryConfig(ctx)
|
||||
}
|
||||
|
||||
// Load prior un-updated store into version history cache to compare
|
||||
@@ -2703,54 +2723,92 @@ func lastRemoteUpstreamWALImpl(c *Core) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *Core) PhysicalSealConfigs(ctx context.Context) (*SealConfig, *SealConfig, error) {
|
||||
pe, err := c.physical.Get(ctx, barrierSealConfigPath)
|
||||
// physicalBarrierSealConfig reads the storage entry at configPath and parses and validates it as SealConfig.
|
||||
func physicalSealConfig(ctx context.Context, c *Core, label, configPath string) (*SealConfig, error) {
|
||||
pe, err := c.physical.Get(ctx, configPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to fetch barrier seal configuration at migration check time: %w", err)
|
||||
return nil, fmt.Errorf("failed to fetch %s seal configuration: %w", label, err)
|
||||
}
|
||||
if pe == nil {
|
||||
return nil, nil, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
barrierConf := new(SealConfig)
|
||||
config := new(SealConfig)
|
||||
|
||||
if err := jsonutil.DecodeJSON(pe.Value, barrierConf); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to decode barrier seal configuration at migration check time: %w", err)
|
||||
if err := jsonutil.DecodeJSON(pe.Value, config); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode %s seal configuration: %w", label, err)
|
||||
}
|
||||
err = barrierConf.Validate()
|
||||
err = config.Validate()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to validate barrier seal configuration at migration check time: %w", err)
|
||||
return nil, fmt.Errorf("failed to validate %s seal configuration: %w", label, err)
|
||||
}
|
||||
// In older versions of vault the default seal would not store a type. This
|
||||
// is here to offer backwards compatibility for older seal configs.
|
||||
if barrierConf.Type == "" {
|
||||
barrierConf.Type = wrapping.WrapperTypeShamir.String()
|
||||
if config.Type == "" {
|
||||
config.Type = SealConfigTypeShamir.String()
|
||||
}
|
||||
|
||||
var recoveryConf *SealConfig
|
||||
pe, err = c.physical.Get(ctx, recoverySealConfigPlaintextPath)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Core) PhysicalBarrierSealConfig(ctx context.Context) (*SealConfig, error) {
|
||||
return physicalSealConfig(ctx, c, "barrier", barrierSealConfigPath)
|
||||
}
|
||||
|
||||
func (c *Core) PhysicalRecoverySealConfig(ctx context.Context) (*SealConfig, error) {
|
||||
return physicalSealConfig(ctx, c, "recovery", recoverySealConfigPlaintextPath)
|
||||
}
|
||||
|
||||
func (c *Core) PhysicalRecoverySealConfigOldPath(ctx context.Context) (*SealConfig, error) {
|
||||
return physicalSealConfig(ctx, c, "recovery", recoverySealConfigPath)
|
||||
}
|
||||
|
||||
func (c *Core) PhysicalSealConfigs(ctx context.Context) (*SealConfig, *SealConfig, error) {
|
||||
barrierConf, err := c.PhysicalBarrierSealConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to fetch seal configuration at migration check time: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to get barrier seal configuration at migration check time: %w", err)
|
||||
}
|
||||
if pe != nil {
|
||||
recoveryConf = &SealConfig{}
|
||||
if err := jsonutil.DecodeJSON(pe.Value, recoveryConf); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to decode seal configuration at migration check time: %w", err)
|
||||
}
|
||||
err = recoveryConf.Validate()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to validate seal configuration at migration check time: %w", err)
|
||||
}
|
||||
// In older versions of vault the default seal would not store a type. This
|
||||
// is here to offer backwards compatibility for older seal configs.
|
||||
if recoveryConf.Type == "" {
|
||||
recoveryConf.Type = wrapping.WrapperTypeShamir.String()
|
||||
}
|
||||
if barrierConf == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
recoveryConf, err := c.PhysicalRecoverySealConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get recovery seal configuration at migration check time: %w", err)
|
||||
}
|
||||
|
||||
return barrierConf, recoveryConf, nil
|
||||
}
|
||||
|
||||
func (c *Core) SetPhysicalBarrierSealConfig(ctx context.Context, barrierSealConfig *SealConfig) error {
|
||||
return setPhysicalSealConfig(ctx, c, "barrier", barrierSealConfigPath, barrierSealConfig)
|
||||
}
|
||||
|
||||
func (c *Core) SetPhysicalRecoverySealConfig(ctx context.Context, recoverySealConfig *SealConfig) error {
|
||||
return setPhysicalSealConfig(ctx, c, "recovery", recoverySealConfigPlaintextPath, recoverySealConfig)
|
||||
}
|
||||
|
||||
func setPhysicalSealConfig(ctx context.Context, c *Core, label, configPath string, sealConfig *SealConfig) error {
|
||||
// Encode the seal configuration
|
||||
buf, err := json.Marshal(sealConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode %s seal configuration: %w", label, err)
|
||||
}
|
||||
|
||||
// Store the seal configuration
|
||||
pe := &physical.Entry{
|
||||
Key: configPath,
|
||||
Value: buf,
|
||||
}
|
||||
|
||||
if err := c.physical.Put(ctx, pe); err != nil {
|
||||
c.logger.Error(fmt.Sprintf("failed to write %s seal configuration", label), "error", err)
|
||||
return fmt.Errorf("failed to write %s seal configuration: %w", label, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// adjustForSealMigration takes the unwrapSeal, which is nil if (a) we're not
|
||||
// configured for seal migration or (b) we might be doing a seal migration away
|
||||
// from shamir. It will only be non-nil if there is a configured seal with
|
||||
@@ -2787,33 +2845,74 @@ func (c *Core) adjustForSealMigration(unwrapSeal Seal) error {
|
||||
// With unwrapSeal==nil, either we're not migrating, or we're migrating
|
||||
// from shamir.
|
||||
|
||||
storedType := SealConfigType(existBarrierSealConfig.Type)
|
||||
configuredType := c.seal.BarrierSealConfigType()
|
||||
|
||||
switch {
|
||||
case existBarrierSealConfig.Type == c.seal.BarrierType().String():
|
||||
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 nil
|
||||
case c.seal.BarrierType() == wrapping.WrapperTypeShamir:
|
||||
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 fmt.Errorf("cannot seal migrate from %q to Shamir, no disabled seal in configuration",
|
||||
existBarrierSealConfig.Type)
|
||||
case existBarrierSealConfig.Type == wrapping.WrapperTypeShamir.String():
|
||||
case storedType == SealConfigTypeShamir:
|
||||
// The configured seal is not Shamir, the stored seal config is Shamir.
|
||||
// This is a migration away from Shamir.
|
||||
unwrapSeal = NewDefaultSeal(vaultseal.NewAccess(aeadwrapper.NewShamirWrapper()))
|
||||
|
||||
// See note about creating a SealGenerationInfo for the unwrap seal in
|
||||
// function setSeal in server.go.
|
||||
sealAccess, err := vaultseal.NewAccessFromSealInfo(c.logger, 1, true, []vaultseal.SealInfo{
|
||||
{
|
||||
Wrapper: aeadwrapper.NewShamirWrapper(),
|
||||
Priority: 1,
|
||||
Name: "shamir",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unwrapSeal = NewDefaultSeal(sealAccess)
|
||||
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.
|
||||
betaEnabled, err := server.IsSealHABetaEnabled()
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case !betaEnabled:
|
||||
return fmt.Errorf("cannot seal migrate from %q to %q, Seal High Availability beta is not enabled",
|
||||
existBarrierSealConfig.Type, c.seal.BarrierSealConfigType())
|
||||
default:
|
||||
// We are going from a single non-shamir seal to multiseal, and the seal HA beta is enabled.
|
||||
// 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 nil
|
||||
}
|
||||
case storedType == SealConfigTypeMultiseal:
|
||||
// The stored type is multiseal and we know the type the configured type is not shamir,
|
||||
// thus we are going from multiseal to autoseal.
|
||||
//
|
||||
// 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 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 explicit disabled seal stanza.
|
||||
// there is no explicitly disabled seal stanza.
|
||||
return fmt.Errorf("cannot seal migrate from %q to %q, no disabled seal in configuration",
|
||||
existBarrierSealConfig.Type, c.seal.BarrierType())
|
||||
existBarrierSealConfig.Type, c.seal.BarrierSealConfigType())
|
||||
}
|
||||
} else {
|
||||
// If we're not coming from Shamir we expect the previous seal to be
|
||||
// in the config and disabled.
|
||||
|
||||
if unwrapSeal.BarrierType() == wrapping.WrapperTypeShamir {
|
||||
if unwrapSeal.BarrierSealConfigType() == SealConfigTypeShamir {
|
||||
return errors.New("Shamir seals cannot be set disabled (they should simply not be set)")
|
||||
}
|
||||
}
|
||||
@@ -2823,7 +2922,7 @@ func (c *Core) adjustForSealMigration(unwrapSeal Seal) error {
|
||||
unwrapSeal.SetCore(c)
|
||||
|
||||
// No stored recovery seal config found, what about the legacy recovery config?
|
||||
if existBarrierSealConfig.Type != wrapping.WrapperTypeShamir.String() && existRecoverySealConfig == nil {
|
||||
if existBarrierSealConfig.Type != SealConfigTypeShamir.String() && existRecoverySealConfig == nil {
|
||||
entry, err := c.physical.Get(ctx, recoverySealConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %q recovery seal configuration: %w", existBarrierSealConfig.Type, err)
|
||||
@@ -2837,14 +2936,42 @@ func (c *Core) adjustForSealMigration(unwrapSeal Seal) error {
|
||||
c.migrationInfo = &migrationInformation{
|
||||
seal: unwrapSeal,
|
||||
}
|
||||
if existBarrierSealConfig.Type != c.seal.BarrierType().String() {
|
||||
if existBarrierSealConfig.Type != c.seal.BarrierSealConfigType().String() {
|
||||
// It's unnecessary to call this when doing an auto->auto
|
||||
// same-seal-type migration, since they'll have the same configs before
|
||||
// and after migration.
|
||||
c.adjustSealConfigDuringMigration(existBarrierSealConfig, existRecoverySealConfig)
|
||||
}
|
||||
c.initSealsForMigration()
|
||||
c.logger.Warn("entering seal migration mode; Vault will not automatically unseal even if using an autoseal", "from_barrier_type", c.migrationInfo.seal.BarrierType(), "to_barrier_type", c.seal.BarrierType())
|
||||
c.logger.Warn("entering seal migration mode; Vault will not automatically unseal even if using an autoseal", "from_barrier_type", c.migrationInfo.seal.BarrierSealConfigType(), "to_barrier_type", c.seal.BarrierSealConfigType())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) migrateMultiSealConfig(ctx context.Context) error {
|
||||
barrierSealConfig, err := c.PhysicalBarrierSealConfig(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read existing seal configuration during multi seal migration: %v", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.seal.BarrierSealConfigType().IsSameAs(barrierSealConfig.Type):
|
||||
return nil
|
||||
case c.seal.BarrierSealConfigType() == SealConfigTypeMultiseal:
|
||||
// needs update
|
||||
case SealConfigTypeMultiseal.IsSameAs(barrierSealConfig.Type):
|
||||
// needs update
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note that SetBarrierConfig updates SealConfig.Type to the correct value.
|
||||
if err := c.seal.SetBarrierConfig(ctx, barrierSealConfig); err != nil {
|
||||
return fmt.Errorf("error storing barrier config during multi seal migration: %w", err)
|
||||
}
|
||||
|
||||
// Note that we don't need to trigger a seal rewrap here, since we'll do that when
|
||||
// we update the SealGenerationInfo. See standardUnsealStrategy.unseal().
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2871,7 +2998,7 @@ func (c *Core) migrateSealConfig(ctx context.Context) error {
|
||||
// recovery config to a clone of shamir's barrier config with stored
|
||||
// keys set to 0.
|
||||
bc = &SealConfig{
|
||||
Type: c.seal.BarrierType().String(),
|
||||
Type: c.seal.BarrierSealConfigType().String(),
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
StoredShares: 1,
|
||||
@@ -2910,7 +3037,7 @@ func (c *Core) adjustSealConfigDuringMigration(existBarrierSealConfig, existReco
|
||||
// recovery config to a clone of shamir's barrier config with stored
|
||||
// keys set to 0.
|
||||
newBarrierSealConfig := &SealConfig{
|
||||
Type: c.seal.BarrierType().String(),
|
||||
Type: c.seal.BarrierSealConfigType().String(),
|
||||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
StoredShares: 1,
|
||||
@@ -2924,14 +3051,14 @@ func (c *Core) adjustSealConfigDuringMigration(existBarrierSealConfig, existReco
|
||||
}
|
||||
|
||||
func (c *Core) unsealKeyToRootKeyPostUnseal(ctx context.Context, combinedKey []byte) ([]byte, error) {
|
||||
return c.unsealKeyToMasterKey(ctx, c.seal, combinedKey, true, false)
|
||||
return c.unsealKeyToRootKey(ctx, c.seal, combinedKey, true, false)
|
||||
}
|
||||
|
||||
func (c *Core) unsealKeyToMasterKeyPreUnseal(ctx context.Context, seal Seal, combinedKey []byte) ([]byte, error) {
|
||||
return c.unsealKeyToMasterKey(ctx, seal, combinedKey, false, true)
|
||||
return c.unsealKeyToRootKey(ctx, seal, combinedKey, false, true)
|
||||
}
|
||||
|
||||
// unsealKeyToMasterKey takes a key provided by the user, either a recovery key
|
||||
// unsealKeyToRootKey takes a key provided by the user, either a recovery key
|
||||
// if using an autoseal or an unseal key with Shamir. It returns a nil error
|
||||
// if the key is valid and an error otherwise. It also returns the master key
|
||||
// that can be used to unseal the barrier.
|
||||
@@ -2942,7 +3069,7 @@ func (c *Core) unsealKeyToMasterKeyPreUnseal(ctx context.Context, seal Seal, com
|
||||
// valid.
|
||||
// If allowMissing is true, a failure to find the master key in storage results
|
||||
// in a nil error and a nil master key being returned.
|
||||
func (c *Core) unsealKeyToMasterKey(ctx context.Context, seal Seal, combinedKey []byte, useTestSeal bool, allowMissing bool) ([]byte, error) {
|
||||
func (c *Core) unsealKeyToRootKey(ctx context.Context, seal Seal, combinedKey []byte, useTestSeal bool, allowMissing bool) ([]byte, error) {
|
||||
switch seal.StoredKeysSupported() {
|
||||
case vaultseal.StoredKeysSupportedGeneric:
|
||||
if err := seal.VerifyRecoveryKey(ctx, combinedKey); err != nil {
|
||||
@@ -2964,7 +3091,19 @@ func (c *Core) unsealKeyToMasterKey(ctx context.Context, seal Seal, combinedKey
|
||||
|
||||
case vaultseal.StoredKeysSupportedShamirRoot:
|
||||
if useTestSeal {
|
||||
testseal := NewDefaultSeal(vaultseal.NewAccess(aeadwrapper.NewShamirWrapper()))
|
||||
// Note that the seal generation should not matter, since the only thing we are doing with
|
||||
// this seal is calling GetStoredKeys (i.e. we are not encrypting anything).
|
||||
sealAccess, err := vaultseal.NewAccessFromSealInfo(c.logger, 1, true, []vaultseal.SealInfo{
|
||||
{
|
||||
Wrapper: aeadwrapper.NewShamirWrapper(),
|
||||
Priority: 1,
|
||||
Name: "shamir",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to setup seal wrapper for test barrier config: %w", err)
|
||||
}
|
||||
testseal := NewDefaultSeal(sealAccess)
|
||||
testseal.SetCore(c)
|
||||
cfg, err := seal.BarrierConfig(ctx)
|
||||
if err != nil {
|
||||
@@ -2974,11 +3113,7 @@ func (c *Core) unsealKeyToMasterKey(ctx context.Context, seal Seal, combinedKey
|
||||
seal = testseal
|
||||
}
|
||||
|
||||
shamirWrapper, err := seal.GetShamirWrapper()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = shamirWrapper.SetAesGcmKeyBytes(combinedKey)
|
||||
err := seal.GetAccess().SetShamirSealKey(combinedKey)
|
||||
if err != nil {
|
||||
return nil, &ErrInvalidKey{fmt.Sprintf("failed to setup unseal key: %v", err)}
|
||||
}
|
||||
|
||||
@@ -515,10 +515,7 @@ func TestRaft_SnapshotAPI_MidstreamFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
seal, setErr := vaultseal.NewToggleableTestSeal(nil)
|
||||
autoSeal, err := vault.NewAutoSeal(seal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
autoSeal := vault.NewAutoSeal(seal)
|
||||
cluster, _ := raftCluster(t, &RaftClusterOpts{
|
||||
NumCores: 1,
|
||||
Seal: autoSeal,
|
||||
@@ -550,9 +547,9 @@ func TestRaft_SnapshotAPI_MidstreamFailure(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
setErr(errors.New("seal failure"))
|
||||
setErr[0](errors.New("seal failure"))
|
||||
// Take a snapshot
|
||||
err = leaderClient.Sys().RaftSnapshot(w)
|
||||
err := leaderClient.Sys().RaftSnapshot(w)
|
||||
w.Close()
|
||||
if err == nil || err != api.ErrIncompleteSnapshot {
|
||||
t.Fatalf("expected err=%v, got: %v", api.ErrIncompleteSnapshot, err)
|
||||
|
||||
17
vault/ha.go
17
vault/ha.go
@@ -571,9 +571,9 @@ func (c *Core) waitForLeadership(newLeaderCh chan func(), manualStepDownCh, stop
|
||||
// everything is sane. If we have no sanity in the barrier, we actually
|
||||
// seal, as there's little we can do.
|
||||
{
|
||||
c.seal.SetBarrierConfig(activeCtx, nil)
|
||||
c.seal.ClearBarrierConfig(activeCtx)
|
||||
if c.seal.RecoveryKeySupported() {
|
||||
c.seal.SetRecoveryConfig(activeCtx, nil)
|
||||
c.seal.ClearRecoveryConfig(activeCtx)
|
||||
}
|
||||
|
||||
if err := c.performKeyUpgrades(activeCtx); err != nil {
|
||||
@@ -951,10 +951,13 @@ func (c *Core) reloadRootKey(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *Core) reloadShamirKey(ctx context.Context) error {
|
||||
_ = c.seal.SetBarrierConfig(ctx, nil)
|
||||
if cfg, _ := c.seal.BarrierConfig(ctx); cfg == nil {
|
||||
_ = c.seal.ClearBarrierConfig(ctx)
|
||||
|
||||
cfg, _ := c.seal.BarrierConfig(ctx)
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var shamirKey []byte
|
||||
switch c.seal.StoredKeysSupported() {
|
||||
case seal.StoredKeysSupportedGeneric:
|
||||
@@ -975,11 +978,7 @@ func (c *Core) reloadShamirKey(ctx context.Context) error {
|
||||
}
|
||||
shamirKey = keyring.rootKey
|
||||
}
|
||||
shamirWrapper, err := c.seal.GetShamirWrapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return shamirWrapper.SetAesGcmKeyBytes(shamirKey)
|
||||
return c.seal.GetAccess().SetShamirSealKey(shamirKey)
|
||||
}
|
||||
|
||||
func (c *Core) performKeyUpgrades(ctx context.Context) error {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
|
||||
@@ -279,7 +278,7 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
|
||||
var sealKey []byte
|
||||
var sealKeyShares [][]byte
|
||||
|
||||
if barrierConfig.StoredShares == 1 && c.seal.BarrierType() == wrapping.WrapperTypeShamir {
|
||||
if barrierConfig.StoredShares == 1 && c.seal.BarrierSealConfigType() == SealConfigTypeShamir {
|
||||
sealKey, sealKeyShares, err = c.generateShares(barrierConfig)
|
||||
if err != nil {
|
||||
c.logger.Error("error generating shares", "error", err)
|
||||
@@ -327,11 +326,7 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
|
||||
switch c.seal.StoredKeysSupported() {
|
||||
case seal.StoredKeysSupportedShamirRoot:
|
||||
keysToStore := [][]byte{barrierKey}
|
||||
shamirWrapper, err := c.seal.GetShamirWrapper()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := shamirWrapper.SetAesGcmKeyBytes(sealKey); err != nil {
|
||||
if err := c.seal.GetAccess().SetShamirSealKey(sealKey); err != nil {
|
||||
c.logger.Error("failed to set seal key", "error", err)
|
||||
return nil, fmt.Errorf("failed to set seal key: %w", err)
|
||||
}
|
||||
@@ -446,7 +441,7 @@ func (c *Core) UnsealWithStoredKeys(ctx context.Context) error {
|
||||
c.unsealWithStoredKeysLock.Lock()
|
||||
defer c.unsealWithStoredKeysLock.Unlock()
|
||||
|
||||
if c.seal.BarrierType() == wrapping.WrapperTypeShamir {
|
||||
if c.seal.BarrierSealConfigType() == SealConfigTypeShamir {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/physical/inmem"
|
||||
@@ -92,7 +91,7 @@ func testCore_Init_Common(t *testing.T, c *Core, conf *CoreConfig, barrierConf,
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if c.seal.BarrierType() == wrapping.WrapperTypeShamir && len(res.SecretShares) != barrierConf.SecretShares {
|
||||
if c.seal.BarrierSealConfigType() == SealConfigTypeShamir && len(res.SecretShares) != barrierConf.SecretShares {
|
||||
t.Fatalf("Bad: got\n%#v\nexpected conf matching\n%#v\n", *res, *barrierConf)
|
||||
}
|
||||
if recoveryConf != nil {
|
||||
|
||||
@@ -4727,6 +4727,18 @@ type SealStatusResponse struct {
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
type SealBackendStatus struct {
|
||||
Name string `json:"name"`
|
||||
Healthy bool `json:"healthy"`
|
||||
UnhealthySince string `json:"unhealthy_since,omitempty"`
|
||||
}
|
||||
|
||||
type SealBackendStatusResponse struct {
|
||||
Healthy bool `json:"healthy"`
|
||||
UnhealthySince string `json:"unhealthy_since,omitempty"`
|
||||
Backends []SealBackendStatus `json:"backends"`
|
||||
}
|
||||
|
||||
func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error) {
|
||||
sealed := core.Sealed()
|
||||
|
||||
@@ -4749,7 +4761,7 @@ func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error
|
||||
|
||||
if sealConfig == nil {
|
||||
s := &SealStatusResponse{
|
||||
Type: core.SealAccess().BarrierType().String(),
|
||||
Type: core.SealAccess().BarrierSealConfigType().String(),
|
||||
Initialized: initialized,
|
||||
Sealed: true,
|
||||
RecoverySeal: core.SealAccess().RecoveryKeySupported(),
|
||||
@@ -4807,6 +4819,40 @@ func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (c *Core) GetSealBackendStatus(ctx context.Context) (*SealBackendStatusResponse, error) {
|
||||
var r SealBackendStatusResponse
|
||||
if a, ok := c.seal.(*autoSeal); ok {
|
||||
r.Healthy = c.seal.Healthy()
|
||||
var uhMin time.Time
|
||||
for _, s := range a.GetAllSealInfoByPriority() {
|
||||
b := SealBackendStatus{
|
||||
Name: s.Name,
|
||||
Healthy: s.Healthy,
|
||||
}
|
||||
if !s.Healthy {
|
||||
if !s.LastSeenHealthy.IsZero() {
|
||||
b.UnhealthySince = s.LastSeenHealthy.String()
|
||||
}
|
||||
if uhMin.IsZero() || uhMin.After(s.LastSeenHealthy) {
|
||||
uhMin = s.LastSeenHealthy
|
||||
}
|
||||
}
|
||||
r.Backends = append(r.Backends, b)
|
||||
}
|
||||
if !uhMin.IsZero() {
|
||||
r.UnhealthySince = uhMin.String()
|
||||
}
|
||||
} else {
|
||||
r.Backends = []SealBackendStatus{
|
||||
{
|
||||
Name: "shamir", // "default?"
|
||||
Healthy: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
type LeaderResponse struct {
|
||||
HAEnabled bool `json:"ha_enabled"`
|
||||
IsSelf bool `json:"is_self"`
|
||||
|
||||
@@ -12,20 +12,27 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
snapshot "github.com/hashicorp/raft-snapshot"
|
||||
"github.com/hashicorp/vault/helper/constants"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// raftStoragePaths returns paths for use when raft is the storage mechanism.
|
||||
func (b *SystemBackend) raftStoragePaths() []*framework.Path {
|
||||
makeSealer := func(logger hclog.Logger, use string) func() snapshot.Sealer {
|
||||
return func() snapshot.Sealer {
|
||||
return NewSealAccessSealer(b.Core.seal.GetAccess(), logger, use)
|
||||
}
|
||||
}
|
||||
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: "storage/raft/bootstrap/answer",
|
||||
@@ -66,7 +73,7 @@ func (b *SystemBackend) raftStoragePaths() []*framework.Path {
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleRaftBootstrapChallengeWrite(),
|
||||
Callback: b.handleRaftBootstrapChallengeWrite(makeSealer(nil, "bootstrap_challenge_write")),
|
||||
Summary: "Creates a challenge for the new peer to be joined to the raft cluster.",
|
||||
},
|
||||
},
|
||||
@@ -128,11 +135,11 @@ func (b *SystemBackend) raftStoragePaths() []*framework.Path {
|
||||
Pattern: "storage/raft/snapshot",
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
Callback: b.handleStorageRaftSnapshotRead(),
|
||||
Callback: b.handleStorageRaftSnapshotRead(makeSealer(nil, "snapshot_read")),
|
||||
Summary: "Returns a snapshot of the current state of vault.",
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleStorageRaftSnapshotWrite(false),
|
||||
Callback: b.handleStorageRaftSnapshotWrite(false, makeSealer(b.logger, "snapshot_write")),
|
||||
Summary: "Installs the provided snapshot, returning the cluster to the state defined in it.",
|
||||
},
|
||||
},
|
||||
@@ -144,7 +151,7 @@ func (b *SystemBackend) raftStoragePaths() []*framework.Path {
|
||||
Pattern: "storage/raft/snapshot-force",
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleStorageRaftSnapshotWrite(true),
|
||||
Callback: b.handleStorageRaftSnapshotWrite(true, makeSealer(b.logger, "snapshot_write")),
|
||||
Summary: "Installs the provided snapshot, returning the cluster to the state defined in it. This bypasses checks ensuring the current Autounseal or Shamir keys are consistent with the snapshot data.",
|
||||
},
|
||||
},
|
||||
@@ -259,7 +266,7 @@ func (b *SystemBackend) handleRaftRemovePeerUpdate() framework.OperationFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRaftBootstrapChallengeWrite() framework.OperationFunc {
|
||||
func (b *SystemBackend) handleRaftBootstrapChallengeWrite(makeSealer func() snapshot.Sealer) framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
serverID := d.Get("server_id").(string)
|
||||
if len(serverID) == 0 {
|
||||
@@ -279,13 +286,11 @@ func (b *SystemBackend) handleRaftBootstrapChallengeWrite() framework.OperationF
|
||||
answer = answerRaw.([]byte)
|
||||
}
|
||||
|
||||
sealAccess := b.Core.seal.GetAccess()
|
||||
|
||||
eBlob, err := sealAccess.Encrypt(ctx, answer, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
sealer := makeSealer()
|
||||
if sealer == nil {
|
||||
return nil, errors.New("core has no seal Access to write raft bootstrap challenge")
|
||||
}
|
||||
protoBlob, err := proto.Marshal(eBlob)
|
||||
protoBlob, err := sealer.Seal(ctx, answer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -397,7 +402,7 @@ func (b *SystemBackend) handleRaftBootstrapAnswerWrite() framework.OperationFunc
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleStorageRaftSnapshotRead() framework.OperationFunc {
|
||||
func (b *SystemBackend) handleStorageRaftSnapshotRead(makeSealer func() snapshot.Sealer) framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
raftStorage, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
@@ -407,7 +412,7 @@ func (b *SystemBackend) handleStorageRaftSnapshotRead() framework.OperationFunc
|
||||
return nil, errors.New("no writer for request")
|
||||
}
|
||||
|
||||
err := raftStorage.SnapshotHTTP(req.ResponseWriter, b.Core.seal.GetAccess())
|
||||
err := raftStorage.SnapshotHTTP(req.ResponseWriter, makeSealer())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -559,7 +564,7 @@ func (b *SystemBackend) handleStorageRaftAutopilotConfigUpdate() framework.Opera
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleStorageRaftSnapshotWrite(force bool) framework.OperationFunc {
|
||||
func (b *SystemBackend) handleStorageRaftSnapshotWrite(force bool, makeSealer func() snapshot.Sealer) framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
raftStorage, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
@@ -569,21 +574,21 @@ func (b *SystemBackend) handleStorageRaftSnapshotWrite(force bool) framework.Ope
|
||||
return nil, errors.New("no reader for request")
|
||||
}
|
||||
|
||||
access := b.Core.seal.GetAccess()
|
||||
if force {
|
||||
access = nil
|
||||
var sealer snapshot.Sealer
|
||||
if !force {
|
||||
sealer = makeSealer()
|
||||
}
|
||||
|
||||
// We want to buffer the http request reader into a temp file here so we
|
||||
// don't have to hold the full snapshot in memory. We also want to do
|
||||
// the restore in two parts so we can restore the snapshot while the
|
||||
// stateLock is write locked.
|
||||
snapFile, cleanup, metadata, err := raftStorage.WriteSnapshotToTemp(req.HTTPRequest.Body, access)
|
||||
snapFile, cleanup, metadata, err := raftStorage.WriteSnapshotToTemp(req.HTTPRequest.Body, sealer)
|
||||
switch {
|
||||
case err == nil:
|
||||
case strings.Contains(err.Error(), "failed to open the sealed hashes"):
|
||||
switch b.Core.seal.BarrierType() {
|
||||
case wrapping.WrapperTypeShamir:
|
||||
switch b.Core.seal.BarrierSealConfigType() {
|
||||
case SealConfigTypeShamir:
|
||||
return logical.ErrorResponse("could not verify hash file, possibly the snapshot is using a different set of unseal keys; use the snapshot-force API to bypass this check"), logical.ErrInvalidRequest
|
||||
default:
|
||||
return logical.ErrorResponse("could not verify hash file, possibly the snapshot is using a different autoseal key; use the snapshot-force API to bypass this check"), logical.ErrInvalidRequest
|
||||
@@ -709,3 +714,52 @@ var sysRaftHelp = map[string][2]string{
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func NewSealAccessSealer(access seal.Access, logger hclog.Logger, use string) snapshot.Sealer {
|
||||
// If we have access to the seal create a sealer object
|
||||
var s snapshot.Sealer
|
||||
if access != nil {
|
||||
s = &sealer{
|
||||
access: access,
|
||||
logger: logger,
|
||||
use: use,
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// sealer implements the snapshot.Sealer interface and is used in the snapshot
|
||||
// process for encrypting/decrypting the SHASUM file in snapshot archives.
|
||||
type sealer struct {
|
||||
access seal.Access
|
||||
logger hclog.Logger
|
||||
use string
|
||||
}
|
||||
|
||||
var _ snapshot.Sealer = (*sealer)(nil)
|
||||
|
||||
// Seal encrypts the data with using the seal access object.
|
||||
func (s *sealer) Seal(ctx context.Context, pt []byte) ([]byte, error) {
|
||||
wrappedValue, err := SealWrapValue(ctx, s.access, true, pt, func(_ context.Context, errs map[string]error) error {
|
||||
if s.logger != nil {
|
||||
s.logger.Warn("sealed value not wrapped with all seals", "use", s.use)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MarshalSealWrappedValue(wrappedValue)
|
||||
}
|
||||
|
||||
// Open decrypts the data using the seal access object.
|
||||
func (s *sealer) Open(ctx context.Context, ct []byte) ([]byte, error) {
|
||||
wrappedEntryValue, err := UnmarshalSealWrappedValue(ct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pt, _, err := UnsealWrapValue(ctx, s.access, "", wrappedEntryValue)
|
||||
return pt, err
|
||||
}
|
||||
|
||||
@@ -801,8 +801,8 @@ func (c *Core) raftSnapshotRestoreCallback(grabLock bool, sealNode bool) func(co
|
||||
if err != nil {
|
||||
// The snapshot contained a root key or keyring we couldn't
|
||||
// recover
|
||||
switch c.seal.BarrierType() {
|
||||
case wrapping.WrapperTypeShamir:
|
||||
switch c.seal.BarrierSealConfigType() {
|
||||
case SealConfigTypeShamir:
|
||||
// If we are a shamir seal we can't do anything. Just
|
||||
// seal all nodes.
|
||||
|
||||
@@ -946,8 +946,8 @@ func (c *Core) getRaftChallenge(leaderInfo *raft.LeaderJoinInfo) (*raftInformati
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sealConfig.Type != c.seal.BarrierType().String() {
|
||||
return nil, fmt.Errorf("mismatching seal types between raft leader (%s) and follower (%s)", sealConfig.Type, c.seal.BarrierType())
|
||||
if sealConfig.Type != c.seal.BarrierSealConfigType().String() {
|
||||
return nil, fmt.Errorf("mismatching seal types between raft leader (%s) and follower (%s)", sealConfig.Type, c.seal.BarrierSealConfigType())
|
||||
}
|
||||
|
||||
challengeB64, ok := secret.Data["challenge"]
|
||||
@@ -1055,7 +1055,7 @@ func (c *Core) JoinRaftCluster(ctx context.Context, leaderInfos []*raft.LeaderJo
|
||||
// If we're using Shamir and using raft for both physical and HA, we
|
||||
// need to block until the node is unsealed, unless retry is set to
|
||||
// false.
|
||||
if c.seal.BarrierType() == wrapping.WrapperTypeShamir && !c.isRaftHAOnly() {
|
||||
if c.seal.BarrierSealConfigType() == SealConfigTypeShamir && !c.isRaftHAOnly() {
|
||||
c.raftInfo.Store(raftInfo)
|
||||
if err := c.seal.SetBarrierConfig(ctx, raftInfo.leaderBarrierConfig); err != nil {
|
||||
return err
|
||||
@@ -1078,7 +1078,7 @@ func (c *Core) JoinRaftCluster(ctx context.Context, leaderInfos []*raft.LeaderJo
|
||||
return fmt.Errorf("failed to send answer to raft leader node: %w", err)
|
||||
}
|
||||
|
||||
if c.seal.BarrierType() == wrapping.WrapperTypeShamir && !isRaftHAOnly {
|
||||
if c.seal.BarrierSealConfigType() == SealConfigTypeShamir && !isRaftHAOnly {
|
||||
// Reset the state
|
||||
c.raftInfo.Store((*raftInformation)(nil))
|
||||
|
||||
@@ -1281,7 +1281,11 @@ func (c *Core) joinRaftSendAnswer(ctx context.Context, sealAccess seal.Access, r
|
||||
return errors.New("raft is already initialized")
|
||||
}
|
||||
|
||||
plaintext, err := sealAccess.Decrypt(ctx, raftInfo.challenge, nil)
|
||||
multiWrapValue := &seal.MultiWrapValue{
|
||||
Generation: sealAccess.Generation(),
|
||||
Slots: []*wrapping.BlobInfo{raftInfo.challenge},
|
||||
}
|
||||
plaintext, _, err := sealAccess.Decrypt(ctx, multiWrapValue, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decrypting challenge: %w", err)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
@@ -171,8 +170,8 @@ func (c *Core) RekeyInit(config *SealConfig, recovery bool) logical.HTTPCodedErr
|
||||
|
||||
// BarrierRekeyInit is used to initialize the rekey settings for the barrier key
|
||||
func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError {
|
||||
switch c.seal.BarrierType() {
|
||||
case wrapping.WrapperTypeShamir:
|
||||
switch c.seal.BarrierSealConfigType() {
|
||||
case SealConfigTypeShamir:
|
||||
// As of Vault 1.3 all seals use StoredShares==1. The one exception is
|
||||
// legacy shamir seals, which we can read but not write (by design).
|
||||
// So if someone does a rekey, regardless of their intention, we're going
|
||||
@@ -399,12 +398,21 @@ func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string)
|
||||
c.logger.Error("rekey recovery key verification failed", "error", err)
|
||||
return nil, logical.CodedError(http.StatusBadRequest, fmt.Errorf("recovery key verification failed: %w", err).Error())
|
||||
}
|
||||
case c.seal.BarrierType() == wrapping.WrapperTypeShamir:
|
||||
case c.seal.BarrierSealConfigType() == SealConfigTypeShamir:
|
||||
if c.seal.StoredKeysSupported() == seal.StoredKeysSupportedShamirRoot {
|
||||
shamirWrapper := aeadwrapper.NewShamirWrapper()
|
||||
testseal := NewDefaultSeal(seal.NewAccess(shamirWrapper))
|
||||
access, err := seal.NewAccessFromSealInfo(c.logger, c.seal.GetAccess().Generation(), true, []seal.SealInfo{
|
||||
{
|
||||
Wrapper: aeadwrapper.NewShamirWrapper(),
|
||||
Priority: 1,
|
||||
Name: existingConfig.Name,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, logical.CodedError(http.StatusInternalServerError, fmt.Errorf("failed to setup test seal: %w", err).Error())
|
||||
}
|
||||
testseal := NewDefaultSeal(access)
|
||||
testseal.SetCore(c)
|
||||
err = shamirWrapper.SetAesGcmKeyBytes(recoveredKey)
|
||||
err = testseal.GetAccess().SetShamirSealKey(recoveredKey)
|
||||
if err != nil {
|
||||
return nil, logical.CodedError(http.StatusInternalServerError, fmt.Errorf("failed to setup unseal key: %w", err).Error())
|
||||
}
|
||||
@@ -532,10 +540,7 @@ func (c *Core) performBarrierRekey(ctx context.Context, newSealKey []byte) logic
|
||||
}
|
||||
|
||||
if c.seal.StoredKeysSupported() != seal.StoredKeysSupportedGeneric {
|
||||
shamirWrapper, err := c.seal.GetShamirWrapper()
|
||||
if err == nil {
|
||||
err = shamirWrapper.SetAesGcmKeyBytes(newSealKey)
|
||||
}
|
||||
err := c.seal.GetAccess().SetShamirSealKey(newSealKey)
|
||||
if err != nil {
|
||||
return logical.CodedError(http.StatusInternalServerError, fmt.Errorf("failed to update barrier seal key: %w", err).Error())
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func testCore_Rekey_Init_Common(t *testing.T, c *Core, recovery bool) {
|
||||
// If recovery key is supported, set newConf
|
||||
// to be a recovery seal config
|
||||
if c.seal.RecoveryKeySupported() {
|
||||
newConf.Type = c.seal.RecoveryType()
|
||||
newConf.Type = c.seal.RecoverySealConfigType().String()
|
||||
}
|
||||
|
||||
err = c.RekeyInit(newConf, recovery)
|
||||
@@ -155,9 +155,9 @@ func testCore_Rekey_Update_Common(t *testing.T, c *Core, keys [][]byte, root str
|
||||
// Start a rekey
|
||||
var expType string
|
||||
if recovery {
|
||||
expType = c.seal.RecoveryType()
|
||||
expType = c.seal.RecoverySealConfigType().String()
|
||||
} else {
|
||||
expType = c.seal.BarrierType().String()
|
||||
expType = c.seal.BarrierSealConfigType().String()
|
||||
}
|
||||
|
||||
newConf := &SealConfig{
|
||||
|
||||
283
vault/seal.go
283
vault/seal.go
@@ -4,22 +4,17 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2"
|
||||
"github.com/hashicorp/vault/command/server"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
|
||||
@@ -48,35 +43,41 @@ const (
|
||||
|
||||
// hsmStoredIVPath is the path to the initialization vector for stored keys
|
||||
hsmStoredIVPath = "core/hsm/iv"
|
||||
)
|
||||
|
||||
const (
|
||||
RecoveryTypeUnsupported = "unsupported"
|
||||
RecoveryTypeShamir = "shamir"
|
||||
// SealGenInfoPath is the path used to store our seal generation info.
|
||||
// This is required so that we can detect any seal config changes and introduce
|
||||
// a new generation which keeps track if a rewrap of all CSPs and seal wrapped
|
||||
// values has completed .
|
||||
SealGenInfoPath = "core/seal-gen-info"
|
||||
)
|
||||
|
||||
type Seal interface {
|
||||
SetCore(*Core)
|
||||
Init(context.Context) error
|
||||
Finalize(context.Context) error
|
||||
StoredKeysSupported() seal.StoredKeysSupport // SealAccess
|
||||
StoredKeysSupported() seal.StoredKeysSupport
|
||||
SealWrapable() bool
|
||||
SetStoredKeys(context.Context, [][]byte) error
|
||||
GetStoredKeys(context.Context) ([][]byte, error)
|
||||
BarrierType() wrapping.WrapperType // SealAccess
|
||||
BarrierConfig(context.Context) (*SealConfig, error) // SealAccess
|
||||
BarrierSealConfigType() SealConfigType
|
||||
BarrierConfig(context.Context) (*SealConfig, error)
|
||||
ClearBarrierConfig(context.Context) error
|
||||
SetBarrierConfig(context.Context, *SealConfig) error
|
||||
SetCachedBarrierConfig(*SealConfig)
|
||||
RecoveryKeySupported() bool // SealAccess
|
||||
RecoveryType() string
|
||||
RecoveryConfig(context.Context) (*SealConfig, error) // SealAccess
|
||||
|
||||
RecoveryKeySupported() bool
|
||||
// RecoveryType returns the SealConfigType for the recovery seal. We only ever
|
||||
// expect this to be one of SealConfigTypeRecovery or SealConfigTypeRecoveryUnsupported
|
||||
RecoverySealConfigType() SealConfigType
|
||||
RecoveryConfig(context.Context) (*SealConfig, error)
|
||||
RecoveryKey(context.Context) ([]byte, error)
|
||||
ClearRecoveryConfig(context.Context) error
|
||||
SetRecoveryConfig(context.Context, *SealConfig) error
|
||||
SetCachedRecoveryConfig(*SealConfig)
|
||||
SetRecoveryKey(context.Context, []byte) error
|
||||
VerifyRecoveryKey(context.Context, []byte) error // SealAccess
|
||||
GetAccess() seal.Access // SealAccess
|
||||
GetShamirWrapper() (*aeadwrapper.ShamirWrapper, error)
|
||||
VerifyRecoveryKey(context.Context, []byte) error
|
||||
GetAccess() seal.Access
|
||||
Healthy() bool
|
||||
}
|
||||
|
||||
type defaultSeal struct {
|
||||
@@ -126,8 +127,8 @@ func (d *defaultSeal) Finalize(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *defaultSeal) BarrierType() wrapping.WrapperType {
|
||||
return wrapping.WrapperTypeShamir
|
||||
func (d *defaultSeal) BarrierSealConfigType() SealConfigType {
|
||||
return SealConfigTypeShamir
|
||||
}
|
||||
|
||||
func (d *defaultSeal) StoredKeysSupported() seal.StoredKeysSupport {
|
||||
@@ -167,8 +168,7 @@ func (d *defaultSeal) GetStoredKeys(ctx context.Context) ([][]byte, error) {
|
||||
}
|
||||
|
||||
func (d *defaultSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
||||
cfg := d.config.Load().(*SealConfig)
|
||||
if cfg != nil {
|
||||
if cfg := d.config.Load().(*SealConfig); cfg != nil {
|
||||
return cfg.Clone(), nil
|
||||
}
|
||||
|
||||
@@ -177,46 +177,33 @@ func (d *defaultSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
||||
}
|
||||
|
||||
// Fetch the core configuration
|
||||
pe, err := d.core.physical.Get(ctx, barrierSealConfigPath)
|
||||
conf, err := d.core.PhysicalBarrierSealConfig(ctx)
|
||||
if err != nil {
|
||||
d.core.logger.Error("failed to read seal configuration", "error", err)
|
||||
return nil, fmt.Errorf("failed to check seal configuration: %w", err)
|
||||
}
|
||||
|
||||
// If the seal configuration is missing, we are not initialized
|
||||
if pe == nil {
|
||||
if conf == nil {
|
||||
d.core.logger.Info("seal configuration missing, not initialized")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var conf SealConfig
|
||||
|
||||
// Decode the barrier entry
|
||||
if err := jsonutil.DecodeJSON(pe.Value, &conf); err != nil {
|
||||
d.core.logger.Error("failed to decode seal configuration", "error", err)
|
||||
return nil, fmt.Errorf("failed to decode seal configuration: %w", err)
|
||||
}
|
||||
|
||||
switch conf.Type {
|
||||
// This case should not be valid for other types as only this is the default
|
||||
case "":
|
||||
conf.Type = d.BarrierType().String()
|
||||
case d.BarrierType().String():
|
||||
case d.BarrierSealConfigType().String():
|
||||
default:
|
||||
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())
|
||||
d.core.logger.Error("barrier seal type does not match expected type", "barrier_seal_type", conf.Type, "loaded_seal_type", d.BarrierSealConfigType())
|
||||
return nil, fmt.Errorf("barrier seal type of %q does not match expected type of %q", conf.Type, d.BarrierSealConfigType())
|
||||
}
|
||||
|
||||
// Check for a valid seal configuration
|
||||
if err := conf.Validate(); err != nil {
|
||||
d.core.logger.Error("invalid seal configuration", "error", err)
|
||||
return nil, fmt.Errorf("seal validation failed: %w", err)
|
||||
}
|
||||
|
||||
d.SetCachedBarrierConfig(&conf)
|
||||
d.SetCachedBarrierConfig(conf)
|
||||
return conf.Clone(), nil
|
||||
}
|
||||
|
||||
func (d *defaultSeal) ClearBarrierConfig(ctx context.Context) error {
|
||||
return d.SetBarrierConfig(ctx, nil)
|
||||
}
|
||||
|
||||
func (d *defaultSeal) SetBarrierConfig(ctx context.Context, config *SealConfig) error {
|
||||
if err := d.checkCore(); err != nil {
|
||||
return err
|
||||
@@ -229,7 +216,7 @@ func (d *defaultSeal) SetBarrierConfig(ctx context.Context, config *SealConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
config.Type = d.BarrierType().String()
|
||||
config.Type = d.BarrierSealConfigType().String()
|
||||
|
||||
// If we are doing a raft unseal we do not want to persist the barrier config
|
||||
// because storage isn't setup yet.
|
||||
@@ -238,21 +225,9 @@ func (d *defaultSeal) SetBarrierConfig(ctx context.Context, config *SealConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode the seal configuration
|
||||
buf, err := json.Marshal(config)
|
||||
err := d.core.SetPhysicalBarrierSealConfig(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode seal configuration: %w", err)
|
||||
}
|
||||
|
||||
// Store the seal configuration
|
||||
pe := &physical.Entry{
|
||||
Key: barrierSealConfigPath,
|
||||
Value: buf,
|
||||
}
|
||||
|
||||
if err := d.core.physical.Put(ctx, pe); err != nil {
|
||||
d.core.logger.Error("failed to write seal configuration", "error", err)
|
||||
return fmt.Errorf("failed to write seal configuration: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetCachedBarrierConfig(config.Clone())
|
||||
@@ -264,8 +239,8 @@ func (d *defaultSeal) SetCachedBarrierConfig(config *SealConfig) {
|
||||
d.config.Store(config)
|
||||
}
|
||||
|
||||
func (d *defaultSeal) RecoveryType() string {
|
||||
return RecoveryTypeUnsupported
|
||||
func (d *defaultSeal) RecoverySealConfigType() SealConfigType {
|
||||
return SealConfigTypeRecoveryUnsupported
|
||||
}
|
||||
|
||||
func (d *defaultSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
|
||||
@@ -276,6 +251,10 @@ func (d *defaultSeal) RecoveryKey(ctx context.Context) ([]byte, error) {
|
||||
return nil, fmt.Errorf("recovery not supported")
|
||||
}
|
||||
|
||||
func (d *defaultSeal) ClearRecoveryConfig(ctx context.Context) error {
|
||||
return d.SetRecoveryConfig(ctx, nil)
|
||||
}
|
||||
|
||||
func (d *defaultSeal) SetRecoveryConfig(ctx context.Context, config *SealConfig) error {
|
||||
return fmt.Errorf("recovery not supported")
|
||||
}
|
||||
@@ -291,128 +270,8 @@ func (d *defaultSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
|
||||
return fmt.Errorf("recovery not supported")
|
||||
}
|
||||
|
||||
func (d *defaultSeal) GetShamirWrapper() (*aeadwrapper.ShamirWrapper, error) {
|
||||
// defaultSeal is meant to be for Shamir seals, so it should always have a ShamirWrapper.
|
||||
// Nonetheless, NewDefaultSeal does not check, so let's play it safe.
|
||||
w, ok := d.GetAccess().GetWrapper().(*aeadwrapper.ShamirWrapper)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected defaultSeal to have a ShamirWrapper, but found a %T instead", d.GetAccess().GetWrapper())
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// SealConfig is used to describe the seal configuration
|
||||
type SealConfig struct {
|
||||
// The type, for sanity checking
|
||||
Type string `json:"type" mapstructure:"type"`
|
||||
|
||||
// SecretShares is the number of shares the secret is split into. This is
|
||||
// the N value of Shamir.
|
||||
SecretShares int `json:"secret_shares" mapstructure:"secret_shares"`
|
||||
|
||||
// SecretThreshold is the number of parts required to open the vault. This
|
||||
// is the T value of Shamir.
|
||||
SecretThreshold int `json:"secret_threshold" mapstructure:"secret_threshold"`
|
||||
|
||||
// PGPKeys is the array of public PGP keys used, if requested, to encrypt
|
||||
// the output unseal tokens. If provided, it sets the value of
|
||||
// SecretShares. Ordering is important.
|
||||
PGPKeys []string `json:"pgp_keys" mapstructure:"pgp_keys"`
|
||||
|
||||
// Nonce is a nonce generated by Vault used to ensure that when unseal keys
|
||||
// are submitted for a rekey operation, the rekey operation itself is the
|
||||
// one intended. This prevents hijacking of the rekey operation, since it
|
||||
// is unauthenticated.
|
||||
Nonce string `json:"nonce" mapstructure:"nonce"`
|
||||
|
||||
// Backup indicates whether or not a backup of PGP-encrypted unseal keys
|
||||
// should be stored at coreUnsealKeysBackupPath after successful rekeying.
|
||||
Backup bool `json:"backup" mapstructure:"backup"`
|
||||
|
||||
// How many keys to store, for seals that support storage. Always 0 or 1.
|
||||
StoredShares int `json:"stored_shares" mapstructure:"stored_shares"`
|
||||
|
||||
// Stores the progress of the rekey operation (key shares)
|
||||
RekeyProgress [][]byte `json:"-"`
|
||||
|
||||
// VerificationRequired indicates that after a rekey validation must be
|
||||
// performed (via providing shares from the new key) before the new key is
|
||||
// actually installed. This is omitted from JSON as we don't persist the
|
||||
// new key, it lives only in memory.
|
||||
VerificationRequired bool `json:"-"`
|
||||
|
||||
// VerificationKey is the new key that we will roll to after successful
|
||||
// validation
|
||||
VerificationKey []byte `json:"-"`
|
||||
|
||||
// VerificationNonce stores the current operation nonce for verification
|
||||
VerificationNonce string `json:"-"`
|
||||
|
||||
// Stores the progress of the verification operation (key shares)
|
||||
VerificationProgress [][]byte `json:"-"`
|
||||
}
|
||||
|
||||
// Validate is used to sanity check the seal configuration
|
||||
func (s *SealConfig) Validate() error {
|
||||
if s.SecretShares < 1 {
|
||||
return fmt.Errorf("shares must be at least one")
|
||||
}
|
||||
if s.SecretThreshold < 1 {
|
||||
return fmt.Errorf("threshold must be at least one")
|
||||
}
|
||||
if s.SecretShares > 1 && s.SecretThreshold == 1 {
|
||||
return fmt.Errorf("threshold must be greater than one for multiple shares")
|
||||
}
|
||||
if s.SecretShares > 255 {
|
||||
return fmt.Errorf("shares must be less than 256")
|
||||
}
|
||||
if s.SecretThreshold > 255 {
|
||||
return fmt.Errorf("threshold must be less than 256")
|
||||
}
|
||||
if s.SecretThreshold > s.SecretShares {
|
||||
return fmt.Errorf("threshold cannot be larger than shares")
|
||||
}
|
||||
if s.StoredShares > 1 {
|
||||
return fmt.Errorf("stored keys cannot be larger than 1")
|
||||
}
|
||||
if len(s.PGPKeys) > 0 && len(s.PGPKeys) != s.SecretShares {
|
||||
return fmt.Errorf("count mismatch between number of provided PGP keys and number of shares")
|
||||
}
|
||||
if len(s.PGPKeys) > 0 {
|
||||
for _, keystring := range s.PGPKeys {
|
||||
data, err := base64.StdEncoding.DecodeString(keystring)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding given PGP key: %w", err)
|
||||
}
|
||||
_, err = openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(data)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing given PGP key: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SealConfig) Clone() *SealConfig {
|
||||
ret := &SealConfig{
|
||||
Type: s.Type,
|
||||
SecretShares: s.SecretShares,
|
||||
SecretThreshold: s.SecretThreshold,
|
||||
Nonce: s.Nonce,
|
||||
Backup: s.Backup,
|
||||
StoredShares: s.StoredShares,
|
||||
VerificationRequired: s.VerificationRequired,
|
||||
VerificationNonce: s.VerificationNonce,
|
||||
}
|
||||
if len(s.PGPKeys) > 0 {
|
||||
ret.PGPKeys = make([]string, len(s.PGPKeys))
|
||||
copy(ret.PGPKeys, s.PGPKeys)
|
||||
}
|
||||
if len(s.VerificationKey) > 0 {
|
||||
ret.VerificationKey = make([]byte, len(s.VerificationKey))
|
||||
copy(ret.VerificationKey, s.VerificationKey)
|
||||
}
|
||||
return ret
|
||||
func (d *defaultSeal) Healthy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type ErrEncrypt struct {
|
||||
@@ -481,3 +340,57 @@ func readStoredKeys(ctx context.Context, storage physical.Backend, encryptor sea
|
||||
|
||||
return UnsealWrapStoredBarrierKeys(ctx, encryptor, pe)
|
||||
}
|
||||
|
||||
func (c *Core) SetPhysicalSealGenInfo(ctx context.Context, sealGenInfo *seal.SealGenerationInfo) error {
|
||||
if enabled, err := server.IsSealHABetaEnabled(); err != nil {
|
||||
return err
|
||||
} else if !enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sealGenInfo == nil {
|
||||
return errors.New("invalid seal generation information: generation is unknown")
|
||||
}
|
||||
// Encode the seal generation info
|
||||
buf, err := json.Marshal(sealGenInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode seal generation info: %w", err)
|
||||
}
|
||||
|
||||
// Store the seal generation info
|
||||
pe := &physical.Entry{
|
||||
Key: SealGenInfoPath,
|
||||
Value: buf,
|
||||
}
|
||||
|
||||
if err := c.physical.Put(ctx, pe); err != nil {
|
||||
c.logger.Error("failed to write seal generation info", "error", err)
|
||||
return fmt.Errorf("failed to write seal generation info: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PhysicalSealGenInfo(ctx context.Context, storage physical.Backend) (*seal.SealGenerationInfo, error) {
|
||||
if enabled, err := server.IsSealHABetaEnabled(); err != nil {
|
||||
return nil, err
|
||||
} else if !enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pe, err := storage.Get(ctx, SealGenInfoPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch seal generation info: %w", err)
|
||||
}
|
||||
if pe == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sealGenInfo := new(seal.SealGenerationInfo)
|
||||
|
||||
if err := jsonutil.DecodeJSON(pe.Value, sealGenInfo); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode seal generation info: %w", err)
|
||||
}
|
||||
|
||||
return sealGenInfo, nil
|
||||
}
|
||||
|
||||
170
vault/seal/multi_wrap_value.pb.go
Normal file
170
vault/seal/multi_wrap_value.pb.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc (unknown)
|
||||
// source: vault/seal/multi_wrap_value.proto
|
||||
|
||||
package seal
|
||||
|
||||
import (
|
||||
v2 "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// MultiWrapValue can be used to keep track of different encryptions of a value.
|
||||
type MultiWrapValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Generation is used to keep track of when the MultiWrapValue was generated.
|
||||
Generation uint64 `protobuf:"varint,1,opt,name=generation,proto3" json:"generation,omitempty"`
|
||||
// Slots has a BlobInfo for each key used to encrypt the value.
|
||||
Slots []*v2.BlobInfo `protobuf:"bytes,2,rep,name=slots,proto3" json:"slots,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiWrapValue) Reset() {
|
||||
*x = MultiWrapValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_vault_seal_multi_wrap_value_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiWrapValue) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiWrapValue) ProtoMessage() {}
|
||||
|
||||
func (x *MultiWrapValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_vault_seal_multi_wrap_value_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MultiWrapValue.ProtoReflect.Descriptor instead.
|
||||
func (*MultiWrapValue) Descriptor() ([]byte, []int) {
|
||||
return file_vault_seal_multi_wrap_value_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MultiWrapValue) GetGeneration() uint64 {
|
||||
if x != nil {
|
||||
return x.Generation
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *MultiWrapValue) GetSlots() []*v2.BlobInfo {
|
||||
if x != nil {
|
||||
return x.Slots
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_vault_seal_multi_wrap_value_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_vault_seal_multi_wrap_value_proto_rawDesc = []byte{
|
||||
0x0a, 0x21, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x65, 0x61, 0x6c, 0x2f, 0x6d, 0x75, 0x6c,
|
||||
0x74, 0x69, 0x5f, 0x77, 0x72, 0x61, 0x70, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x04, 0x73, 0x65, 0x61, 0x6c, 0x1a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e,
|
||||
0x67, 0x6f, 0x2e, 0x6b, 0x6d, 0x73, 0x2e, 0x77, 0x72, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e,
|
||||
0x76, 0x32, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7f,
|
||||
0x0a, 0x0e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x57, 0x72, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x4d, 0x0a, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73,
|
||||
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x67, 0x6f, 0x2e, 0x6b, 0x6d, 0x73, 0x2e, 0x77, 0x72,
|
||||
0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x32, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e,
|
||||
0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x42,
|
||||
0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61,
|
||||
0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x76, 0x61,
|
||||
0x75, 0x6c, 0x74, 0x2f, 0x73, 0x65, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_vault_seal_multi_wrap_value_proto_rawDescOnce sync.Once
|
||||
file_vault_seal_multi_wrap_value_proto_rawDescData = file_vault_seal_multi_wrap_value_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_vault_seal_multi_wrap_value_proto_rawDescGZIP() []byte {
|
||||
file_vault_seal_multi_wrap_value_proto_rawDescOnce.Do(func() {
|
||||
file_vault_seal_multi_wrap_value_proto_rawDescData = protoimpl.X.CompressGZIP(file_vault_seal_multi_wrap_value_proto_rawDescData)
|
||||
})
|
||||
return file_vault_seal_multi_wrap_value_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_vault_seal_multi_wrap_value_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_vault_seal_multi_wrap_value_proto_goTypes = []interface{}{
|
||||
(*MultiWrapValue)(nil), // 0: seal.MultiWrapValue
|
||||
(*v2.BlobInfo)(nil), // 1: github.com.hashicorp.go.kms.wrapping.v2.types.BlobInfo
|
||||
}
|
||||
var file_vault_seal_multi_wrap_value_proto_depIdxs = []int32{
|
||||
1, // 0: seal.MultiWrapValue.slots:type_name -> github.com.hashicorp.go.kms.wrapping.v2.types.BlobInfo
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_vault_seal_multi_wrap_value_proto_init() }
|
||||
func file_vault_seal_multi_wrap_value_proto_init() {
|
||||
if File_vault_seal_multi_wrap_value_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_vault_seal_multi_wrap_value_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiWrapValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_vault_seal_multi_wrap_value_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_vault_seal_multi_wrap_value_proto_goTypes,
|
||||
DependencyIndexes: file_vault_seal_multi_wrap_value_proto_depIdxs,
|
||||
MessageInfos: file_vault_seal_multi_wrap_value_proto_msgTypes,
|
||||
}.Build()
|
||||
File_vault_seal_multi_wrap_value_proto = out.File
|
||||
file_vault_seal_multi_wrap_value_proto_rawDesc = nil
|
||||
file_vault_seal_multi_wrap_value_proto_goTypes = nil
|
||||
file_vault_seal_multi_wrap_value_proto_depIdxs = nil
|
||||
}
|
||||
19
vault/seal/multi_wrap_value.proto
Normal file
19
vault/seal/multi_wrap_value.proto
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package seal;
|
||||
|
||||
import "github.com.hashicorp.go.kms.wrapping.v2.types.proto";
|
||||
|
||||
option go_package = "github.com/hashicorp/vault/vault/seal";
|
||||
|
||||
// MultiWrapValue can be used to keep track of different encryptions of a value.
|
||||
message MultiWrapValue {
|
||||
// Generation is used to keep track of when the MultiWrapValue was generated.
|
||||
uint64 generation = 1;
|
||||
|
||||
// Slots has a BlobInfo for each key used to encrypt the value.
|
||||
repeated github.com.hashicorp.go.kms.wrapping.v2.types.BlobInfo slots = 2;
|
||||
}
|
||||
@@ -5,10 +5,21 @@ package seal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
|
||||
"github.com/hashicorp/vault/internalshared/configutil"
|
||||
|
||||
metrics "github.com/armon/go-metrics"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/aead"
|
||||
)
|
||||
|
||||
type StoredKeysSupport int
|
||||
@@ -34,97 +45,488 @@ func (s StoredKeysSupport) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
type SealGenerationInfo struct {
|
||||
Generation uint64
|
||||
Seals []*configutil.KMS
|
||||
rewrapped atomic.Bool
|
||||
}
|
||||
|
||||
// SetRewrapped updates the SealGenerationInfo's rewrapped status to the provided value.
|
||||
func (sgi *SealGenerationInfo) SetRewrapped(value bool) {
|
||||
sgi.rewrapped.Store(value)
|
||||
}
|
||||
|
||||
// IsRewrapped returns the SealGenerationInfo's rewrapped status.
|
||||
func (sgi *SealGenerationInfo) IsRewrapped() bool {
|
||||
return sgi.rewrapped.Load()
|
||||
}
|
||||
|
||||
type SealInfo struct {
|
||||
wrapping.Wrapper
|
||||
Priority int
|
||||
Name string
|
||||
|
||||
// sealConfigType is the KMS.Type of this wrapper. It is a string rather than a SealConfigType
|
||||
// to avoid a circular go package depency
|
||||
SealConfigType string
|
||||
|
||||
// Disabled indicates, when true indicates that this wrapper should only be used for decryption.
|
||||
Disabled bool
|
||||
|
||||
HcLock sync.RWMutex
|
||||
LastHealthCheck time.Time
|
||||
LastSeenHealthy time.Time
|
||||
Healthy bool
|
||||
}
|
||||
|
||||
func (si *SealInfo) keyId(ctx context.Context) string {
|
||||
if id, err := si.Wrapper.KeyId(ctx); err == nil {
|
||||
return id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Access is the embedded implementation of autoSeal that contains logic
|
||||
// specific to encrypting and decrypting data, or in this case keys.
|
||||
type Access interface {
|
||||
wrapping.Wrapper
|
||||
wrapping.InitFinalizer
|
||||
|
||||
GetWrapper() wrapping.Wrapper
|
||||
Generation() uint64
|
||||
|
||||
// Encrypt encrypts the given byte slice and stores the resulting
|
||||
// information in the returned blob info. Which options are used depends on
|
||||
// the underlying wrapper. Supported options: WithAad.
|
||||
// Returns a MultiWrapValue as long as at least one seal Access wrapper encrypted the data successfully, and
|
||||
// if this is the case errors may still be returned if any wrapper failed. The error map is keyed by seal name.
|
||||
Encrypt(ctx context.Context, plaintext []byte, options ...wrapping.Option) (*MultiWrapValue, map[string]error)
|
||||
|
||||
// Decrypt decrypts the given byte slice and stores the resulting information in the
|
||||
// returned byte slice. Which options are used depends on the underlying wrapper.
|
||||
// Supported options: WithAad.
|
||||
// Returns the plaintext, a flag indicating whether the ciphertext is up-to-date
|
||||
// (according to IsUpToDate), and an error.
|
||||
Decrypt(ctx context.Context, ciphertext *MultiWrapValue, options ...wrapping.Option) ([]byte, bool, error)
|
||||
|
||||
// IsUpToDate returns true if a MultiWrapValue is up-to-date. An MultiWrapValue is
|
||||
// considered to be up-to-date if its generation matches the Access generation, and if
|
||||
// it has a slot with a key ID that match the current key ID of each of the Access
|
||||
// wrappers.
|
||||
IsUpToDate(ctx context.Context, value *MultiWrapValue, forceKeyIdRefresh bool) (bool, error)
|
||||
|
||||
// GetEnabledWrappers returns all the enabled seal Wrappers, in order of priority.
|
||||
GetEnabledWrappers() []wrapping.Wrapper
|
||||
|
||||
SetShamirSealKey([]byte) error
|
||||
GetShamirKeyBytes(ctx context.Context) ([]byte, error)
|
||||
|
||||
// GetAllSealInfoByPriority returns all the SealInfo for all the seal wrappers, including disabled ones.
|
||||
GetAllSealInfoByPriority() []*SealInfo
|
||||
|
||||
// GetEnabledSealInfoByPriority returns the SealInfo for the enabled seal wrappers.
|
||||
GetEnabledSealInfoByPriority() []*SealInfo
|
||||
|
||||
// AllSealsHealthy returns whether all enabled SealInfos are currently healthy.
|
||||
AllSealsHealthy() bool
|
||||
|
||||
GetSealGenerationInfo() *SealGenerationInfo
|
||||
}
|
||||
|
||||
type access struct {
|
||||
w wrapping.Wrapper
|
||||
sealGenerationInfo *SealGenerationInfo
|
||||
wrappersByPriority []*SealInfo
|
||||
keyIdSet keyIdSet
|
||||
logger hclog.Logger
|
||||
}
|
||||
|
||||
var _ Access = (*access)(nil)
|
||||
|
||||
func NewAccess(w wrapping.Wrapper) Access {
|
||||
return &access{
|
||||
w: w,
|
||||
func NewAccess(logger hclog.Logger, sealGenerationInfo *SealGenerationInfo, sealInfos []SealInfo) Access {
|
||||
if logger == nil {
|
||||
logger = hclog.NewNullLogger()
|
||||
}
|
||||
if sealGenerationInfo == nil {
|
||||
panic("cannot create a seal.Access without a SealGenerationInfo")
|
||||
}
|
||||
if len(sealInfos) == 0 {
|
||||
panic("cannot create a seal.Access without any seal info")
|
||||
}
|
||||
a := &access{
|
||||
sealGenerationInfo: sealGenerationInfo,
|
||||
logger: logger,
|
||||
}
|
||||
a.wrappersByPriority = make([]*SealInfo, len(sealInfos))
|
||||
for i, sealInfo := range sealInfos {
|
||||
v := sealInfo
|
||||
a.wrappersByPriority[i] = &v
|
||||
v.Healthy = true
|
||||
v.LastSeenHealthy = time.Now()
|
||||
}
|
||||
|
||||
sort.Slice(a.wrappersByPriority, func(i int, j int) bool { return a.wrappersByPriority[i].Priority < a.wrappersByPriority[j].Priority })
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *access) KeyId(ctx context.Context) (string, error) {
|
||||
return a.w.KeyId(ctx)
|
||||
func NewAccessFromSealInfo(logger hclog.Logger, generation uint64, rewrapped bool, sealInfos []SealInfo) (Access, error) {
|
||||
sealGenerationInfo := &SealGenerationInfo{
|
||||
Generation: generation,
|
||||
}
|
||||
sealGenerationInfo.SetRewrapped(rewrapped)
|
||||
ctx := context.Background()
|
||||
for _, sealInfo := range sealInfos {
|
||||
typ, err := sealInfo.Wrapper.Type(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sealGenerationInfo.Seals = append(sealGenerationInfo.Seals, &configutil.KMS{
|
||||
Type: typ.String(),
|
||||
Priority: sealInfo.Priority,
|
||||
Name: sealInfo.Name,
|
||||
})
|
||||
}
|
||||
return NewAccess(logger, sealGenerationInfo, sealInfos), nil
|
||||
}
|
||||
|
||||
func (a *access) SetConfig(ctx context.Context, options ...wrapping.Option) (*wrapping.WrapperConfig, error) {
|
||||
return a.w.SetConfig(ctx, options...)
|
||||
func (a *access) GetAllSealInfoByPriority() []*SealInfo {
|
||||
return copySealInfos(a.wrappersByPriority, false)
|
||||
}
|
||||
|
||||
func (a *access) GetWrapper() wrapping.Wrapper {
|
||||
return a.w
|
||||
func (a *access) GetEnabledSealInfoByPriority() []*SealInfo {
|
||||
return copySealInfos(a.wrappersByPriority, true)
|
||||
}
|
||||
|
||||
func (a *access) AllSealsHealthy() bool {
|
||||
for _, si := range a.wrappersByPriority {
|
||||
// Ignore disabled seals
|
||||
if si.Disabled {
|
||||
continue
|
||||
}
|
||||
si.HcLock.RLock()
|
||||
defer si.HcLock.RUnlock()
|
||||
if !si.Healthy {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func copySealInfos(sealInfos []*SealInfo, enabledOnly bool) []*SealInfo {
|
||||
ret := make([]*SealInfo, 0, len(sealInfos))
|
||||
for _, si := range sealInfos {
|
||||
if enabledOnly && si.Disabled {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, si)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *access) GetSealGenerationInfo() *SealGenerationInfo {
|
||||
return a.sealGenerationInfo
|
||||
}
|
||||
|
||||
func (a *access) Generation() uint64 {
|
||||
return a.sealGenerationInfo.Generation
|
||||
}
|
||||
|
||||
func (a *access) GetEnabledWrappers() []wrapping.Wrapper {
|
||||
var ret []wrapping.Wrapper
|
||||
for _, si := range a.GetEnabledSealInfoByPriority() {
|
||||
ret = append(ret, si.Wrapper)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *access) Init(ctx context.Context, options ...wrapping.Option) error {
|
||||
if initWrapper, ok := a.w.(wrapping.InitFinalizer); ok {
|
||||
return initWrapper.Init(ctx, options...)
|
||||
var keyIds []string
|
||||
for _, sealInfo := range a.GetAllSealInfoByPriority() {
|
||||
if initWrapper, ok := sealInfo.Wrapper.(wrapping.InitFinalizer); ok {
|
||||
if err := initWrapper.Init(ctx, options...); err != nil {
|
||||
return err
|
||||
}
|
||||
keyId, err := sealInfo.Wrapper.KeyId(ctx)
|
||||
if err != nil {
|
||||
a.logger.Warn("cannot determine key ID for seal", "seal", sealInfo.Name, "err", err)
|
||||
return fmt.Errorf("cannod determine key ID for seal %s: %w", sealInfo.Name, err)
|
||||
}
|
||||
keyIds = append(keyIds, keyId)
|
||||
}
|
||||
}
|
||||
a.keyIdSet.setIds(keyIds)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *access) Type(ctx context.Context) (wrapping.WrapperType, error) {
|
||||
return a.w.Type(ctx)
|
||||
func (a *access) IsUpToDate(ctx context.Context, value *MultiWrapValue, forceKeyIdRefresh bool) (bool, error) {
|
||||
// Note that we don't compare generations when the value is transitory, since all single-blobInfo
|
||||
// values are unmarshalled as transitory values.
|
||||
if value.Generation != 0 && value.Generation != a.Generation() {
|
||||
return false, nil
|
||||
}
|
||||
if forceKeyIdRefresh {
|
||||
test, errs := a.Encrypt(ctx, []byte{0})
|
||||
if test == nil {
|
||||
a.logger.Error("error refreshing seal key IDs")
|
||||
return false, JoinSealWrapErrors("cannot determine key IDs of Access wrappers", errs)
|
||||
}
|
||||
// TODO(SEALHA): What to do if there are partial failures?
|
||||
if len(errs) > 0 {
|
||||
msg := "could not determine key IDs of some Access wrappers"
|
||||
a.logger.Warn(msg)
|
||||
a.logger.Trace("partial failure refreshing seal key IDs", "err", JoinSealWrapErrors(msg, errs))
|
||||
}
|
||||
a.keyIdSet.set(test)
|
||||
}
|
||||
|
||||
return a.keyIdSet.equal(value), nil
|
||||
}
|
||||
|
||||
// Encrypt uses the underlying seal to encrypt the plaintext and returns it.
|
||||
func (a *access) Encrypt(ctx context.Context, plaintext []byte, options ...wrapping.Option) (blob *wrapping.BlobInfo, err error) {
|
||||
wTyp, err := a.w.Type(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (a *access) Encrypt(ctx context.Context, plaintext []byte, options ...wrapping.Option) (*MultiWrapValue, map[string]error) {
|
||||
var slots []*wrapping.BlobInfo
|
||||
errs := make(map[string]error)
|
||||
|
||||
for _, sealInfo := range a.GetEnabledSealInfoByPriority() {
|
||||
var encryptErr error
|
||||
defer func(now time.Time) {
|
||||
metrics.MeasureSince([]string{"seal", "encrypt", "time"}, now)
|
||||
metrics.MeasureSince([]string{"seal", sealInfo.Name, "encrypt", "time"}, now)
|
||||
|
||||
if encryptErr != nil {
|
||||
metrics.IncrCounter([]string{"seal", "encrypt", "error"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", sealInfo.Name, "encrypt", "error"}, 1)
|
||||
}
|
||||
}(time.Now())
|
||||
|
||||
metrics.IncrCounter([]string{"seal", "encrypt"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", sealInfo.Name, "encrypt"}, 1)
|
||||
|
||||
ciphertext, encryptErr := sealInfo.Wrapper.Encrypt(ctx, plaintext, options...)
|
||||
if encryptErr != nil {
|
||||
a.logger.Warn("error encrypting with seal", "seal", sealInfo.Name)
|
||||
a.logger.Trace("error encrypting with seal", "seal", sealInfo.Name, "err", encryptErr)
|
||||
|
||||
errs[sealInfo.Name] = encryptErr
|
||||
sealInfo.Healthy = false
|
||||
} else {
|
||||
a.logger.Trace("encrypted value using seal", "seal", sealInfo.Name, "keyId", ciphertext.KeyInfo.KeyId)
|
||||
|
||||
slots = append(slots, ciphertext)
|
||||
}
|
||||
}
|
||||
|
||||
defer func(now time.Time) {
|
||||
metrics.MeasureSince([]string{"seal", "encrypt", "time"}, now)
|
||||
metrics.MeasureSince([]string{"seal", wTyp.String(), "encrypt", "time"}, now)
|
||||
if len(slots) == 0 {
|
||||
a.logger.Error("all seals failed to encrypt value")
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
metrics.IncrCounter([]string{"seal", "encrypt", "error"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", wTyp.String(), "encrypt", "error"}, 1)
|
||||
}
|
||||
}(time.Now())
|
||||
a.logger.Trace("successfully encrypted value", "encryption seal wrappers", len(slots), "total enabled seal wrappers",
|
||||
len(a.GetEnabledSealInfoByPriority()))
|
||||
ret := &MultiWrapValue{
|
||||
Generation: a.Generation(),
|
||||
Slots: slots,
|
||||
}
|
||||
|
||||
metrics.IncrCounter([]string{"seal", "encrypt"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", wTyp.String(), "encrypt"}, 1)
|
||||
// cache key IDs
|
||||
a.keyIdSet.set(ret)
|
||||
|
||||
return a.w.Encrypt(ctx, plaintext, options...)
|
||||
return ret, errs
|
||||
}
|
||||
|
||||
// Decrypt uses the underlying seal to decrypt the cryptotext and returns it.
|
||||
// Decrypt uses the underlying seal to decrypt the ciphertext and returns it.
|
||||
// Note that it is possible depending on the wrapper used that both pt and err
|
||||
// are populated.
|
||||
func (a *access) Decrypt(ctx context.Context, data *wrapping.BlobInfo, options ...wrapping.Option) (pt []byte, err error) {
|
||||
wTyp, err := a.w.Type(ctx)
|
||||
// Returns the plaintext, a flag indicating whether the ciphertext is up-to-date
|
||||
// (according to IsUpToDate), and an error.
|
||||
func (a *access) Decrypt(ctx context.Context, ciphertext *MultiWrapValue, options ...wrapping.Option) ([]byte, bool, error) {
|
||||
blobInfoMap := slotsByKeyId(ciphertext)
|
||||
|
||||
isUpToDate, err := a.IsUpToDate(ctx, ciphertext, false)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// First, lets try the wrappers in order of priority and look for an exact key ID match
|
||||
for _, sealInfo := range a.GetAllSealInfoByPriority() {
|
||||
if keyId, err := sealInfo.Wrapper.KeyId(ctx); err == nil {
|
||||
if blobInfo, ok := blobInfoMap[keyId]; ok {
|
||||
pt, oldKey, err := a.tryDecrypt(ctx, sealInfo, blobInfo, options)
|
||||
if oldKey {
|
||||
a.logger.Trace("decrypted using OldKey", "seal", sealInfo.Name)
|
||||
return pt, false, err
|
||||
}
|
||||
if err == nil {
|
||||
a.logger.Trace("decrypted value using seal", "seal", sealInfo.Name)
|
||||
return pt, isUpToDate, nil
|
||||
}
|
||||
// If there is an error, keep trying with the other wrappers
|
||||
a.logger.Trace("error decrypting with seal, will try other seals", "seal", sealInfo.Name, "keyId", keyId, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No key ID match, so try each wrapper with all slots
|
||||
errs := make(map[string]error)
|
||||
for _, sealInfo := range a.GetAllSealInfoByPriority() {
|
||||
for _, blobInfo := range ciphertext.Slots {
|
||||
pt, oldKey, err := a.tryDecrypt(ctx, sealInfo, blobInfo, options)
|
||||
if oldKey {
|
||||
a.logger.Trace("decrypted using OldKey", "seal", sealInfo.Name)
|
||||
return pt, false, err
|
||||
}
|
||||
if err == nil {
|
||||
a.logger.Trace("decrypted value using seal", "seal", sealInfo.Name)
|
||||
return pt, isUpToDate, nil
|
||||
}
|
||||
errs[sealInfo.Name] = err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, JoinSealWrapErrors("error decrypting seal wrapped value", errs)
|
||||
}
|
||||
|
||||
func (a *access) tryDecrypt(ctx context.Context, sealInfo *SealInfo, ciphertext *wrapping.BlobInfo, options []wrapping.Option) ([]byte, bool, error) {
|
||||
var decryptErr error
|
||||
defer func(now time.Time) {
|
||||
metrics.MeasureSince([]string{"seal", "decrypt", "time"}, now)
|
||||
metrics.MeasureSince([]string{"seal", wTyp.String(), "decrypt", "time"}, now)
|
||||
metrics.MeasureSince([]string{"seal", sealInfo.Name, "decrypt", "time"}, now)
|
||||
|
||||
if err != nil {
|
||||
if decryptErr != nil {
|
||||
metrics.IncrCounter([]string{"seal", "decrypt", "error"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", wTyp.String(), "decrypt", "error"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", sealInfo.Name, "decrypt", "error"}, 1)
|
||||
}
|
||||
// TODO (multiseal): log an error?
|
||||
}(time.Now())
|
||||
|
||||
metrics.IncrCounter([]string{"seal", "decrypt"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", wTyp.String(), "decrypt"}, 1)
|
||||
metrics.IncrCounter([]string{"seal", sealInfo.Name, "decrypt"}, 1)
|
||||
|
||||
return a.w.Decrypt(ctx, data, options...)
|
||||
pt, err := sealInfo.Wrapper.Decrypt(ctx, ciphertext, options...)
|
||||
isOldKey := false
|
||||
if err != nil && err.Error() == "decrypted with old key" {
|
||||
// This is for compatibility with sealWrapMigration
|
||||
isOldKey = true
|
||||
}
|
||||
return pt, isOldKey, err
|
||||
}
|
||||
|
||||
func JoinSealWrapErrors(msg string, errorMap map[string]error) error {
|
||||
errs := []error{errors.New(msg)}
|
||||
for name, err := range errorMap {
|
||||
errs = append(errs, fmt.Errorf("error decrypting using seal %s: %w", name, err))
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (a *access) Finalize(ctx context.Context, options ...wrapping.Option) error {
|
||||
if finalizeWrapper, ok := a.w.(wrapping.InitFinalizer); ok {
|
||||
return finalizeWrapper.Finalize(ctx, options...)
|
||||
var errs []error
|
||||
|
||||
for _, w := range a.GetAllSealInfoByPriority() {
|
||||
if finalizeWrapper, ok := w.Wrapper.(wrapping.InitFinalizer); ok {
|
||||
if err := finalizeWrapper.Finalize(ctx, options...); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *access) SetShamirSealKey(key []byte) error {
|
||||
if len(a.wrappersByPriority) == 0 {
|
||||
return errors.New("no wrappers configured")
|
||||
}
|
||||
|
||||
wrapper := a.wrappersByPriority[0].Wrapper
|
||||
|
||||
shamirWrapper, ok := wrapper.(*aead.ShamirWrapper)
|
||||
if !ok {
|
||||
return errors.New("seal is not a Shamir seal")
|
||||
}
|
||||
|
||||
return shamirWrapper.SetAesGcmKeyBytes(key)
|
||||
}
|
||||
|
||||
func (a *access) GetShamirKeyBytes(ctx context.Context) ([]byte, error) {
|
||||
if len(a.wrappersByPriority) == 0 {
|
||||
return nil, errors.New("no wrapper configured")
|
||||
}
|
||||
|
||||
wrapper := a.wrappersByPriority[0].Wrapper
|
||||
|
||||
shamirWrapper, ok := wrapper.(*aead.ShamirWrapper)
|
||||
if !ok {
|
||||
return nil, errors.New("seal is not a shamir seal")
|
||||
}
|
||||
|
||||
return shamirWrapper.KeyBytes(ctx)
|
||||
}
|
||||
|
||||
func slotsByKeyId(value *MultiWrapValue) map[string]*wrapping.BlobInfo {
|
||||
ret := make(map[string]*wrapping.BlobInfo)
|
||||
for _, blobInfo := range value.Slots {
|
||||
keyId := ""
|
||||
if blobInfo.KeyInfo != nil {
|
||||
keyId = blobInfo.KeyInfo.KeyId
|
||||
}
|
||||
ret[keyId] = blobInfo
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type keyIdSet struct {
|
||||
keyIds atomic.Pointer[[]string]
|
||||
}
|
||||
|
||||
func (s *keyIdSet) set(value *MultiWrapValue) {
|
||||
keyIds := s.collect(value)
|
||||
s.setIds(keyIds)
|
||||
}
|
||||
|
||||
func (s *keyIdSet) setIds(keyIds []string) {
|
||||
keyIds = s.deduplicate(keyIds)
|
||||
s.keyIds.Store(&keyIds)
|
||||
}
|
||||
|
||||
func (s *keyIdSet) get() []string {
|
||||
pids := s.keyIds.Load()
|
||||
if pids == nil {
|
||||
return nil
|
||||
}
|
||||
return *pids
|
||||
}
|
||||
|
||||
func (s *keyIdSet) equal(value *MultiWrapValue) bool {
|
||||
keyIds := s.collect(value)
|
||||
expected := s.get()
|
||||
return reflect.DeepEqual(keyIds, expected)
|
||||
}
|
||||
|
||||
func (s *keyIdSet) collect(value *MultiWrapValue) []string {
|
||||
var keyIds []string
|
||||
for _, blobInfo := range value.Slots {
|
||||
if blobInfo.KeyInfo != nil {
|
||||
// Ideally we should always have a KeyInfo.KeyId, but:
|
||||
// 1) plaintext entries are stored on a blob info with Wrapped == false
|
||||
// 2) some unit test wrappers do not return a blob info
|
||||
keyIds = append(keyIds, blobInfo.KeyInfo.KeyId)
|
||||
}
|
||||
}
|
||||
return s.deduplicate(keyIds)
|
||||
}
|
||||
|
||||
func (s *keyIdSet) deduplicate(ids []string) []string {
|
||||
m := make(map[string]struct{})
|
||||
for _, id := range ids {
|
||||
m[id] = struct{}{}
|
||||
}
|
||||
deduplicated := make([]string, 0, len(m))
|
||||
for id := range m {
|
||||
deduplicated = append(deduplicated, id)
|
||||
}
|
||||
sort.Strings(deduplicated)
|
||||
return deduplicated
|
||||
}
|
||||
|
||||
94
vault/seal/seal_test.go
Normal file
94
vault/seal/seal_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package seal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
)
|
||||
|
||||
func Test_keyIdSet(t *testing.T) {
|
||||
type args struct {
|
||||
value *MultiWrapValue
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
idsToSet []string
|
||||
idsToTest []string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "a single ID",
|
||||
idsToSet: []string{"Nexus 6"},
|
||||
idsToTest: []string{"Nexus 6"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "two sets of equal IDs",
|
||||
idsToSet: []string{"A", "B"},
|
||||
idsToTest: []string{"A", "B"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "two sets of equal IDs, with duplicated ids set",
|
||||
idsToSet: []string{"A", "B", "A"},
|
||||
idsToTest: []string{"A", "B"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "two sets of equal IDs, with duplicated ids tested",
|
||||
idsToSet: []string{"A", "B"},
|
||||
idsToTest: []string{"A", "B", "A"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "two sets of equal IDs in different order",
|
||||
idsToSet: []string{"A", "B"},
|
||||
idsToTest: []string{"B", "A"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "two sets of different IDs",
|
||||
idsToSet: []string{"A", "B"},
|
||||
idsToTest: []string{"B", "C"},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
useSetIds := func(s *keyIdSet) {
|
||||
s.setIds(tt.idsToSet)
|
||||
}
|
||||
useSet := func(s *keyIdSet) {
|
||||
mwv := &MultiWrapValue{Generation: 6}
|
||||
for _, id := range tt.idsToSet {
|
||||
mwv.Slots = append(mwv.Slots, &wrapping.BlobInfo{
|
||||
KeyInfo: &wrapping.KeyInfo{
|
||||
KeyId: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
s.set(mwv)
|
||||
}
|
||||
|
||||
runTest := func(name string, setter func(*keyIdSet)) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
s := &keyIdSet{}
|
||||
setter(s)
|
||||
|
||||
mwv := &MultiWrapValue{Generation: 6}
|
||||
for _, id := range tt.idsToTest {
|
||||
mwv.Slots = append(mwv.Slots, &wrapping.BlobInfo{
|
||||
KeyInfo: &wrapping.KeyInfo{
|
||||
KeyId: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
if got := s.equal(mwv); got != tt.want {
|
||||
t.Errorf("equal() = %v, want %v, IDs set: %v, IDs tested: %v",
|
||||
got, tt.want, tt.idsToSet, tt.idsToTest)
|
||||
}
|
||||
})
|
||||
}
|
||||
runTest(tt.name+".set()", useSet)
|
||||
runTest(tt.name+".setIDs", useSetIds)
|
||||
}
|
||||
}
|
||||
@@ -5,45 +5,92 @@ package seal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
)
|
||||
|
||||
type TestSealOpts struct {
|
||||
Logger hclog.Logger
|
||||
StoredKeys StoredKeysSupport
|
||||
Secret []byte
|
||||
Name wrapping.WrapperType
|
||||
Logger hclog.Logger
|
||||
StoredKeys StoredKeysSupport
|
||||
Secret []byte
|
||||
Name wrapping.WrapperType
|
||||
WrapperCount int
|
||||
Generation uint64
|
||||
}
|
||||
|
||||
func NewTestSeal(opts *TestSealOpts) (Access, *ToggleableWrapper) {
|
||||
func NewTestSealOpts(opts *TestSealOpts) *TestSealOpts {
|
||||
if opts == nil {
|
||||
opts = new(TestSealOpts)
|
||||
}
|
||||
|
||||
w := &ToggleableWrapper{Wrapper: wrapping.NewTestWrapper(opts.Secret)}
|
||||
if opts.Name != "" {
|
||||
w.wrapperType = &opts.Name
|
||||
if opts.WrapperCount == 0 {
|
||||
opts.WrapperCount = 1
|
||||
}
|
||||
return NewAccess(w), w
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = logging.NewVaultLogger(hclog.Debug)
|
||||
}
|
||||
if opts.Generation == 0 {
|
||||
// we might at some point need to allow Generation == 0
|
||||
opts.Generation = 1
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func NewToggleableTestSeal(opts *TestSealOpts) (Access, func(error)) {
|
||||
if opts == nil {
|
||||
opts = new(TestSealOpts)
|
||||
func NewTestSeal(opts *TestSealOpts) (Access, []*ToggleableWrapper) {
|
||||
opts = NewTestSealOpts(opts)
|
||||
wrappers := make([]*ToggleableWrapper, opts.WrapperCount)
|
||||
sealInfos := make([]SealInfo, opts.WrapperCount)
|
||||
for i := 0; i < opts.WrapperCount; i++ {
|
||||
wrappers[i] = &ToggleableWrapper{Wrapper: wrapping.NewTestWrapper(opts.Secret)}
|
||||
sealInfos[i] = SealInfo{
|
||||
Wrapper: wrappers[i],
|
||||
Priority: i + 1,
|
||||
Name: fmt.Sprintf("%s-%d", opts.Name, i+1),
|
||||
}
|
||||
}
|
||||
|
||||
w := &ToggleableWrapper{Wrapper: wrapping.NewTestWrapper(opts.Secret)}
|
||||
return NewAccess(w), w.SetError
|
||||
sealAccess, err := NewAccessFromSealInfo(nil, opts.Generation, true, sealInfos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sealAccess, wrappers
|
||||
}
|
||||
|
||||
func NewToggleableTestSeal(opts *TestSealOpts) (Access, []func(error)) {
|
||||
opts = NewTestSealOpts(opts)
|
||||
|
||||
wrappers := make([]*ToggleableWrapper, opts.WrapperCount)
|
||||
sealInfos := make([]SealInfo, opts.WrapperCount)
|
||||
funcs := make([]func(error), opts.WrapperCount)
|
||||
for i := 0; i < opts.WrapperCount; i++ {
|
||||
w := &ToggleableWrapper{Wrapper: wrapping.NewTestWrapper(opts.Secret)}
|
||||
wrappers[i] = w
|
||||
sealInfos[i] = SealInfo{
|
||||
Wrapper: wrappers[i],
|
||||
Priority: i + 1,
|
||||
Name: fmt.Sprintf("%s-%d", opts.Name, i+1),
|
||||
}
|
||||
funcs[i] = w.SetError
|
||||
}
|
||||
|
||||
sealAccess, err := NewAccessFromSealInfo(nil, opts.Generation, true, sealInfos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sealAccess, funcs
|
||||
}
|
||||
|
||||
type ToggleableWrapper struct {
|
||||
wrapping.Wrapper
|
||||
wrapperType *wrapping.WrapperType
|
||||
error error
|
||||
l sync.RWMutex
|
||||
wrapperType *wrapping.WrapperType
|
||||
error error
|
||||
encryptError error
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
func (t *ToggleableWrapper) Encrypt(ctx context.Context, bytes []byte, opts ...wrapping.Option) (*wrapping.BlobInfo, error) {
|
||||
@@ -52,6 +99,9 @@ func (t *ToggleableWrapper) Encrypt(ctx context.Context, bytes []byte, opts ...w
|
||||
if t.error != nil {
|
||||
return nil, t.error
|
||||
}
|
||||
if t.encryptError != nil {
|
||||
return nil, t.encryptError
|
||||
}
|
||||
return t.Wrapper.Encrypt(ctx, bytes, opts...)
|
||||
}
|
||||
|
||||
@@ -70,6 +120,13 @@ func (t *ToggleableWrapper) SetError(err error) {
|
||||
t.error = err
|
||||
}
|
||||
|
||||
// An error only occuring on encrypt
|
||||
func (t *ToggleableWrapper) SetEncryptError(err error) {
|
||||
t.l.Lock()
|
||||
defer t.l.Unlock()
|
||||
t.encryptError = err
|
||||
}
|
||||
|
||||
func (t *ToggleableWrapper) Type(ctx context.Context) (wrapping.WrapperType, error) {
|
||||
if t.wrapperType != nil {
|
||||
return *t.wrapperType, nil
|
||||
|
||||
@@ -6,8 +6,6 @@ package vault
|
||||
import (
|
||||
"context"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
|
||||
@@ -26,8 +24,8 @@ func (s *SealAccess) StoredKeysSupported() seal.StoredKeysSupport {
|
||||
return s.seal.StoredKeysSupported()
|
||||
}
|
||||
|
||||
func (s *SealAccess) BarrierType() wrapping.WrapperType {
|
||||
return s.seal.BarrierType()
|
||||
func (s *SealAccess) BarrierSealConfigType() SealConfigType {
|
||||
return s.seal.BarrierSealConfigType()
|
||||
}
|
||||
|
||||
func (s *SealAccess) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
||||
@@ -46,11 +44,10 @@ func (s *SealAccess) VerifyRecoveryKey(ctx context.Context, key []byte) error {
|
||||
return s.seal.VerifyRecoveryKey(ctx, key)
|
||||
}
|
||||
|
||||
// TODO(SEALHA): This looks like it belongs in Seal instead, it only has two callers
|
||||
func (s *SealAccess) ClearCaches(ctx context.Context) {
|
||||
s.seal.SetBarrierConfig(ctx, nil)
|
||||
s.seal.ClearBarrierConfig(ctx)
|
||||
if s.RecoveryKeySupported() {
|
||||
s.seal.SetRecoveryConfig(ctx, nil)
|
||||
s.seal.ClearRecoveryConfig(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,15 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2"
|
||||
"github.com/armon/go-metrics"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
@@ -25,7 +23,7 @@ import (
|
||||
// barrierTypeUpgradeCheck checks for backwards compat on barrier type, not
|
||||
// applicable in the OSS side
|
||||
var (
|
||||
barrierTypeUpgradeCheck = func(_ wrapping.WrapperType, _ *SealConfig) {}
|
||||
barrierTypeUpgradeCheck = func(_ SealConfigType, _ *SealConfig) {}
|
||||
autoSealUnavailableDuration = []string{"seal", "unreachable", "time"}
|
||||
// vars for unit testings
|
||||
sealHealthTestIntervalNominal = 10 * time.Minute
|
||||
@@ -39,35 +37,42 @@ var (
|
||||
type autoSeal struct {
|
||||
seal.Access
|
||||
|
||||
barrierType wrapping.WrapperType
|
||||
barrierConfig atomic.Value
|
||||
recoveryConfig atomic.Value
|
||||
core *Core
|
||||
logger log.Logger
|
||||
barrierSealConfigType SealConfigType
|
||||
barrierConfig atomic.Value
|
||||
recoveryConfig atomic.Value
|
||||
core *Core
|
||||
logger log.Logger
|
||||
|
||||
hcLock sync.Mutex
|
||||
allSealsHealthy bool
|
||||
hcLock sync.RWMutex
|
||||
healthCheckStop chan struct{}
|
||||
}
|
||||
|
||||
// Ensure we are implementing the Seal interface
|
||||
var _ Seal = (*autoSeal)(nil)
|
||||
|
||||
func NewAutoSeal(lowLevel seal.Access) (*autoSeal, error) {
|
||||
func NewAutoSeal(lowLevel seal.Access) *autoSeal {
|
||||
ret := &autoSeal{
|
||||
Access: lowLevel,
|
||||
}
|
||||
ret.barrierConfig.Store((*SealConfig)(nil))
|
||||
ret.recoveryConfig.Store((*SealConfig)(nil))
|
||||
|
||||
// Having the wrapper type in a field is just a convenience since Seal.BarrierType()
|
||||
// does not return an error.
|
||||
var err error
|
||||
ret.barrierType, err = ret.Type(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// See SealConfigType for the rules about computing the type.
|
||||
if len(lowLevel.GetSealGenerationInfo().Seals) > 1 {
|
||||
ret.barrierSealConfigType = SealConfigTypeMultiseal
|
||||
} else {
|
||||
// Note that the Access constructors guarantee that there is at least one KMS config
|
||||
ret.barrierSealConfigType = SealConfigType(lowLevel.GetSealGenerationInfo().Seals[0].Type)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d *autoSeal) Healthy() bool {
|
||||
d.hcLock.RLock()
|
||||
defer d.hcLock.RUnlock()
|
||||
return d.allSealsHealthy
|
||||
}
|
||||
|
||||
func (d *autoSeal) SealWrapable() bool {
|
||||
@@ -100,12 +105,8 @@ func (d *autoSeal) Finalize(ctx context.Context) error {
|
||||
return d.Access.Finalize(ctx)
|
||||
}
|
||||
|
||||
func (d *autoSeal) BarrierType() wrapping.WrapperType {
|
||||
return d.barrierType
|
||||
}
|
||||
|
||||
func (d *autoSeal) GetShamirWrapper() (*aeadwrapper.ShamirWrapper, error) {
|
||||
return nil, fmt.Errorf("autoSeal does not use a ShamirWrapper")
|
||||
func (d *autoSeal) BarrierSealConfigType() SealConfigType {
|
||||
return d.barrierSealConfigType
|
||||
}
|
||||
|
||||
func (d *autoSeal) StoredKeysSupported() seal.StoredKeysSupport {
|
||||
@@ -137,16 +138,15 @@ func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error {
|
||||
return fmt.Errorf("no stored keys found")
|
||||
}
|
||||
|
||||
blobInfo, err := UnmarshalSealWrappedValue(pe.Value)
|
||||
wrappedEntryValue, err := UnmarshalSealWrappedValue(pe.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal stored keys: %w", err)
|
||||
}
|
||||
|
||||
keyId, err := d.Access.KeyId(ctx)
|
||||
uptodate, err := d.Access.IsUpToDate(ctx, wrappedEntryValue.getValue(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to check if stored keys are up-to-date: %w", err)
|
||||
}
|
||||
if blobInfo.KeyInfo != nil && blobInfo.KeyInfo.KeyId != keyId {
|
||||
if !uptodate {
|
||||
d.logger.Info("upgrading stored keys")
|
||||
|
||||
keys, err := UnsealWrapStoredBarrierKeys(ctx, d.GetAccess(), pe)
|
||||
@@ -166,70 +166,52 @@ func (d *autoSeal) upgradeStoredKeys(ctx context.Context) error {
|
||||
// the stored keys and the recovery key are encrypted with. The provided
|
||||
// Context must be non-nil.
|
||||
func (d *autoSeal) UpgradeKeys(ctx context.Context) error {
|
||||
// Many of the seals update their keys to the latest KeyId when Encrypt
|
||||
// is called.
|
||||
if _, err := d.Encrypt(ctx, []byte("a"), nil); err != nil {
|
||||
if err := d.upgradeRecoveryKey(ctx); err != nil { // re-encrypts the recovery key
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.upgradeRecoveryKey(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.upgradeStoredKeys(ctx); err != nil {
|
||||
if err := d.upgradeStoredKeys(ctx); err != nil { // re-encrypts the root key
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *autoSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
|
||||
if d.barrierConfig.Load().(*SealConfig) != nil {
|
||||
return d.barrierConfig.Load().(*SealConfig).Clone(), nil
|
||||
if cfg := d.barrierConfig.Load().(*SealConfig); cfg != nil {
|
||||
return cfg.Clone(), nil
|
||||
}
|
||||
|
||||
if err := d.checkCore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sealType := "barrier"
|
||||
|
||||
entry, err := d.core.physical.Get(ctx, barrierSealConfigPath)
|
||||
// Fetch the core configuration
|
||||
conf, err := d.core.PhysicalBarrierSealConfig(ctx)
|
||||
if err != nil {
|
||||
d.logger.Error("failed to read seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, fmt.Errorf("failed to read %q seal configuration: %w", sealType, err)
|
||||
d.logger.Error("failed to read seal configuration", "error", err)
|
||||
return nil, fmt.Errorf("failed to read seal configuration: %w", err)
|
||||
}
|
||||
|
||||
// If the seal configuration is missing, we are not initialized
|
||||
if entry == nil {
|
||||
if d.logger.IsInfo() {
|
||||
d.logger.Info("seal configuration missing, not initialized", "seal_type", sealType)
|
||||
}
|
||||
if conf == nil {
|
||||
d.logger.Info("seal configuration missing, not initialized")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
conf := &SealConfig{}
|
||||
err = json.Unmarshal(entry.Value, conf)
|
||||
if err != nil {
|
||||
d.logger.Error("failed to decode seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, fmt.Errorf("failed to decode %q seal configuration: %w", sealType, err)
|
||||
}
|
||||
barrierTypeUpgradeCheck(d.BarrierSealConfigType(), conf)
|
||||
|
||||
// Check for a valid seal configuration
|
||||
if err := conf.Validate(); err != nil {
|
||||
d.logger.Error("invalid seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, fmt.Errorf("%q seal validation failed: %w", sealType, err)
|
||||
}
|
||||
|
||||
barrierTypeUpgradeCheck(d.BarrierType(), conf)
|
||||
|
||||
if conf.Type != d.BarrierType().String() {
|
||||
d.logger.Error("barrier seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.BarrierType())
|
||||
return nil, fmt.Errorf("barrier seal type of %q does not match loaded type of %q", conf.Type, d.BarrierType())
|
||||
if conf.Type != d.BarrierSealConfigType().String() {
|
||||
d.logger.Error("barrier seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.BarrierSealConfigType())
|
||||
return nil, fmt.Errorf("barrier seal type of %q does not match loaded type of %q", conf.Type, d.BarrierSealConfigType())
|
||||
}
|
||||
|
||||
d.SetCachedBarrierConfig(conf)
|
||||
return conf.Clone(), nil
|
||||
}
|
||||
|
||||
func (d *autoSeal) ClearBarrierConfig(ctx context.Context) error {
|
||||
return d.SetBarrierConfig(ctx, nil)
|
||||
}
|
||||
|
||||
func (d *autoSeal) SetBarrierConfig(ctx context.Context, conf *SealConfig) error {
|
||||
if err := d.checkCore(); err != nil {
|
||||
return err
|
||||
@@ -240,23 +222,11 @@ func (d *autoSeal) SetBarrierConfig(ctx context.Context, conf *SealConfig) error
|
||||
return nil
|
||||
}
|
||||
|
||||
conf.Type = d.BarrierType().String()
|
||||
conf.Type = d.BarrierSealConfigType().String()
|
||||
|
||||
// Encode the seal configuration
|
||||
buf, err := json.Marshal(conf)
|
||||
err := d.core.SetPhysicalBarrierSealConfig(ctx, conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode barrier seal configuration: %w", err)
|
||||
}
|
||||
|
||||
// Store the seal configuration
|
||||
pe := &physical.Entry{
|
||||
Key: barrierSealConfigPath,
|
||||
Value: buf,
|
||||
}
|
||||
|
||||
if err := d.core.physical.Put(ctx, pe); err != nil {
|
||||
d.logger.Error("failed to write barrier seal configuration", "error", err)
|
||||
return fmt.Errorf("failed to write barrier seal configuration: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetCachedBarrierConfig(conf.Clone())
|
||||
@@ -268,79 +238,59 @@ func (d *autoSeal) SetCachedBarrierConfig(config *SealConfig) {
|
||||
d.barrierConfig.Store(config)
|
||||
}
|
||||
|
||||
func (d *autoSeal) RecoveryType() string {
|
||||
return RecoveryTypeShamir
|
||||
func (d *autoSeal) RecoverySealConfigType() SealConfigType {
|
||||
return SealConfigTypeRecovery
|
||||
}
|
||||
|
||||
// RecoveryConfig returns the recovery config on recoverySealConfigPlaintextPath.
|
||||
func (d *autoSeal) RecoveryConfig(ctx context.Context) (*SealConfig, error) {
|
||||
if d.recoveryConfig.Load().(*SealConfig) != nil {
|
||||
return d.recoveryConfig.Load().(*SealConfig).Clone(), nil
|
||||
if cfg := d.recoveryConfig.Load().(*SealConfig); cfg != nil {
|
||||
return cfg.Clone(), nil
|
||||
}
|
||||
|
||||
if err := d.checkCore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sealType := "recovery"
|
||||
|
||||
var entry *physical.Entry
|
||||
var err error
|
||||
entry, err = d.core.physical.Get(ctx, recoverySealConfigPlaintextPath)
|
||||
conf, err := d.core.PhysicalRecoverySealConfig(ctx)
|
||||
if err != nil {
|
||||
d.logger.Error("failed to read seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, fmt.Errorf("failed to read %q seal configuration: %w", sealType, err)
|
||||
d.logger.Error("failed to read recovery seal configuration", "error", err)
|
||||
return nil, fmt.Errorf("failed to read recovery seal configuration: %w", err)
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
if conf == nil {
|
||||
if d.core.Sealed() {
|
||||
d.logger.Info("seal configuration missing, but cannot check old path as core is sealed", "seal_type", sealType)
|
||||
d.logger.Info("recovery seal configuration missing, but cannot check old path as core is sealed")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check the old recovery seal config path so an upgraded standby will
|
||||
// return the correct seal config
|
||||
be, err := d.core.barrier.Get(ctx, recoverySealConfigPath)
|
||||
conf, err := d.core.PhysicalRecoverySealConfigOldPath(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read old recovery seal configuration: %w", err)
|
||||
}
|
||||
|
||||
// If the seal configuration is missing, then we are not initialized.
|
||||
if be == nil {
|
||||
if d.logger.IsInfo() {
|
||||
d.logger.Info("seal configuration missing, not initialized", "seal_type", sealType)
|
||||
}
|
||||
if conf == nil {
|
||||
d.logger.Info("recovery seal configuration missing, not initialized")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Reconstruct the physical entry
|
||||
entry = &physical.Entry{
|
||||
Key: be.Key,
|
||||
Value: be.Value,
|
||||
}
|
||||
}
|
||||
|
||||
conf := &SealConfig{}
|
||||
if err := json.Unmarshal(entry.Value, conf); err != nil {
|
||||
d.logger.Error("failed to decode seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, fmt.Errorf("failed to decode %q seal configuration: %w", sealType, err)
|
||||
}
|
||||
|
||||
// Check for a valid seal configuration
|
||||
if err := conf.Validate(); err != nil {
|
||||
d.logger.Error("invalid seal configuration", "seal_type", sealType, "error", err)
|
||||
return nil, fmt.Errorf("%q seal validation failed: %w", sealType, err)
|
||||
}
|
||||
|
||||
if conf.Type != d.RecoveryType() {
|
||||
d.logger.Error("recovery seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.RecoveryType())
|
||||
return nil, fmt.Errorf("recovery seal type of %q does not match loaded type of %q", conf.Type, d.RecoveryType())
|
||||
if !d.RecoverySealConfigType().IsSameAs(conf.Type) {
|
||||
d.logger.Error("recovery seal type does not match loaded type", "seal_type", conf.Type, "loaded_type", d.RecoverySealConfigType())
|
||||
return nil, fmt.Errorf("recovery seal type of %q does not match loaded type of %q", conf.Type, d.RecoverySealConfigType())
|
||||
}
|
||||
|
||||
d.recoveryConfig.Store(conf)
|
||||
return conf.Clone(), nil
|
||||
}
|
||||
|
||||
func (d *autoSeal) ClearRecoveryConfig(ctx context.Context) error {
|
||||
return d.SetRecoveryConfig(ctx, 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 {
|
||||
@@ -358,21 +308,9 @@ func (d *autoSeal) SetRecoveryConfig(ctx context.Context, conf *SealConfig) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
conf.Type = d.RecoveryType()
|
||||
conf.Type = d.RecoverySealConfigType().String()
|
||||
|
||||
// Encode the seal configuration
|
||||
buf, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode recovery seal configuration: %w", err)
|
||||
}
|
||||
|
||||
// Store the seal configuration directly in the physical storage
|
||||
pe := &physical.Entry{
|
||||
Key: recoverySealConfigPlaintextPath,
|
||||
Value: buf,
|
||||
}
|
||||
|
||||
if err := d.core.physical.Put(ctx, pe); err != nil {
|
||||
if err := d.core.SetPhysicalRecoverySealConfig(ctx, conf); err != nil {
|
||||
d.logger.Error("failed to write recovery seal configuration", "error", err)
|
||||
return fmt.Errorf("failed to write recovery seal configuration: %w", err)
|
||||
}
|
||||
@@ -441,7 +379,7 @@ func (d *autoSeal) getRecoveryKeyInternal(ctx context.Context) ([]byte, error) {
|
||||
return nil, fmt.Errorf("no recovery key found")
|
||||
}
|
||||
|
||||
pt, _, err := UnsealWrapRecoveryKey(ctx, d.Access, pe)
|
||||
pt, err := UnsealWrapRecoveryKey(ctx, d.Access, pe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt encrypted stored keys: %w", err)
|
||||
}
|
||||
@@ -458,18 +396,21 @@ func (d *autoSeal) upgradeRecoveryKey(ctx context.Context) error {
|
||||
return fmt.Errorf("no recovery key found")
|
||||
}
|
||||
|
||||
pt, blobInfo, err := UnsealWrapRecoveryKey(ctx, d.Access, pe)
|
||||
wrappedEntryValue, err := UnmarshalSealWrappedValue(pe.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to proto decode recovery key: %w", err)
|
||||
return fmt.Errorf("failed to unmarshal recovery key: %w", err)
|
||||
}
|
||||
uptodate, err := d.Access.IsUpToDate(ctx, wrappedEntryValue.getValue(), true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if recovery key is up-to-date: %w", err)
|
||||
}
|
||||
|
||||
keyId, err := d.Access.KeyId(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if blobInfo.KeyInfo != nil && blobInfo.KeyInfo.KeyId != keyId {
|
||||
if !uptodate {
|
||||
d.logger.Info("upgrading recovery key")
|
||||
pt, err := UnsealWrapRecoveryKey(ctx, d.Access, pe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt recovery key: %w", err)
|
||||
}
|
||||
|
||||
if err := d.SetRecoveryKey(ctx, pt); err != nil {
|
||||
return fmt.Errorf("failed to save upgraded recovery key: %w", err)
|
||||
@@ -528,17 +469,62 @@ func (d *autoSeal) StartHealthCheck() {
|
||||
ctx := d.core.activeContext
|
||||
|
||||
go func() {
|
||||
lastTestOk := true
|
||||
lastSeenOk := time.Now()
|
||||
check := func(t time.Time) {
|
||||
ctx, cancel := context.WithTimeout(ctx, sealHealthTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
fail := func(msg string, args ...interface{}) {
|
||||
d.logger.Warn(msg, args...)
|
||||
if lastTestOk {
|
||||
healthCheck.Reset(sealHealthTestIntervalUnhealthy)
|
||||
testVal := fmt.Sprintf("Heartbeat %d", mathrand.Intn(1000))
|
||||
anyUnhealthy := false
|
||||
for _, w := range d.Access.GetAllSealInfoByPriority() {
|
||||
func() {
|
||||
w.HcLock.Lock()
|
||||
defer w.HcLock.Unlock()
|
||||
mLabels := []metrics.Label{{Name: "seal_name", Value: w.Name}}
|
||||
fail := func(msg string, args ...interface{}) {
|
||||
d.logger.Warn(msg, args...)
|
||||
if w.Healthy {
|
||||
healthCheck.Reset(sealHealthTestIntervalUnhealthy)
|
||||
}
|
||||
w.Healthy = false
|
||||
d.core.MetricSink().SetGaugeWithLabels(autoSealUnavailableDuration, float32(time.Since(w.LastSeenHealthy).Milliseconds()), mLabels)
|
||||
}
|
||||
ciphertext, err := w.Encrypt(ctx, []byte(testVal), nil)
|
||||
checkTime := time.Now()
|
||||
w.LastHealthCheck = checkTime
|
||||
|
||||
if err != nil {
|
||||
fail("failed to encrypt seal health test value, seal backend may be unreachable", "error", err, "seal_name", w.Name)
|
||||
anyUnhealthy = true
|
||||
} else {
|
||||
func() {
|
||||
ctx, cancel := context.WithTimeout(ctx, sealHealthTestTimeout)
|
||||
defer cancel()
|
||||
plaintext, err := w.Decrypt(ctx, ciphertext, nil)
|
||||
if err != nil {
|
||||
fail("failed to decrypt seal health test value, seal backend may be unreachable", "error", err, "seal_name", w.Name)
|
||||
}
|
||||
if !bytes.Equal([]byte(testVal), plaintext) {
|
||||
fail("seal health test value failed to decrypt to expected value", "seal_name", w.Name)
|
||||
} else {
|
||||
d.logger.Debug("seal health test passed", "seal_name", w.Name)
|
||||
if !w.Healthy {
|
||||
d.logger.Info("seal backend is now healthy again", "downtime", t.Sub(w.LastSeenHealthy).String(), "seal_name", w.Name)
|
||||
healthCheck.Reset(sealHealthTestIntervalNominal)
|
||||
}
|
||||
|
||||
w.Healthy = true
|
||||
w.LastSeenHealthy = checkTime
|
||||
d.core.MetricSink().SetGaugeWithLabels(autoSealUnavailableDuration, 0, mLabels)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
||||
lastTestOk = false
|
||||
d.core.MetricSink().SetGauge(autoSealUnavailableDuration, float32(time.Since(lastSeenOk).Milliseconds()))
|
||||
d.hcLock.Lock()
|
||||
defer d.hcLock.Unlock()
|
||||
d.allSealsHealthy = !anyUnhealthy
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-healthCheckStop:
|
||||
@@ -548,38 +534,7 @@ func (d *autoSeal) StartHealthCheck() {
|
||||
healthCheckStop = nil
|
||||
return
|
||||
case t := <-healthCheck.C:
|
||||
func() {
|
||||
ctx, cancel := context.WithTimeout(ctx, sealHealthTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
testVal := fmt.Sprintf("Heartbeat %d", mathrand.Intn(1000))
|
||||
ciphertext, err := d.Access.Encrypt(ctx, []byte(testVal), nil)
|
||||
|
||||
if err != nil {
|
||||
fail("failed to encrypt seal health test value, seal backend may be unreachable", "error", err)
|
||||
} else {
|
||||
func() {
|
||||
ctx, cancel := context.WithTimeout(ctx, sealHealthTestTimeout)
|
||||
defer cancel()
|
||||
plaintext, err := d.Access.Decrypt(ctx, ciphertext, nil)
|
||||
if err != nil {
|
||||
fail("failed to decrypt seal health test value, seal backend may be unreachable", "error", err)
|
||||
}
|
||||
if !bytes.Equal([]byte(testVal), plaintext) {
|
||||
fail("seal health test value failed to decrypt to expected value")
|
||||
} else {
|
||||
d.logger.Debug("seal health test passed")
|
||||
if !lastTestOk {
|
||||
d.logger.Info("seal backend is now healthy again", "downtime", t.Sub(lastSeenOk).String())
|
||||
healthCheck.Reset(sealHealthTestIntervalNominal)
|
||||
}
|
||||
lastTestOk = true
|
||||
lastSeenOk = t
|
||||
d.core.MetricSink().SetGauge(autoSealUnavailableDuration, 0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
check(t)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -8,15 +8,14 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/vault/helper/metricsutil"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
@@ -69,22 +68,18 @@ func (p *phy) Len() int {
|
||||
|
||||
func TestAutoSeal_UpgradeKeys(t *testing.T) {
|
||||
core, _, _ := TestCoreUnsealed(t)
|
||||
testSeal, toggleableWrapper := seal.NewTestSeal(nil)
|
||||
testSeal, toggleableWrappers := seal.NewTestSeal(nil)
|
||||
|
||||
var encKeys []string
|
||||
changeKey := func(key string) {
|
||||
encKeys = append(encKeys, key)
|
||||
toggleableWrapper.Wrapper.(*wrapping.TestWrapper).SetKeyId(key)
|
||||
toggleableWrappers[0].Wrapper.(*wrapping.TestWrapper).SetKeyId(key)
|
||||
}
|
||||
|
||||
// Set initial encryption key.
|
||||
changeKey("kaz")
|
||||
|
||||
autoSeal, err := NewAutoSeal(testSeal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
autoSeal := NewAutoSeal(testSeal)
|
||||
autoSeal.SetCore(core)
|
||||
pBackend := newTestBackend(t)
|
||||
core.physical = pBackend
|
||||
@@ -137,10 +132,11 @@ func TestAutoSeal_UpgradeKeys(t *testing.T) {
|
||||
// in encKeys. Iterate over each phyEntry and verify it was
|
||||
// encrypted with its corresponding key in encKeys.
|
||||
for i, phyEntry := range phyEntries {
|
||||
blobInfo := &wrapping.BlobInfo{}
|
||||
if err := proto.Unmarshal(phyEntry.Value, blobInfo); err != nil {
|
||||
t.Errorf("phyKey = %s: failed to proto decode stored keys: %s", phyKey, err)
|
||||
wrappedEntryValue, err := UnmarshalSealWrappedValue(phyEntry.Value)
|
||||
if err != nil {
|
||||
t.Errorf("phyKey = %s: failed to unmarshal stored keys: %s", phyKey, err)
|
||||
}
|
||||
blobInfo := wrappedEntryValue.GetSlots()[0]
|
||||
if blobInfo.KeyInfo == nil {
|
||||
t.Errorf("phyKey = %s: KeyInfo missing: %+v", phyKey, blobInfo)
|
||||
}
|
||||
@@ -185,54 +181,34 @@ func TestAutoSeal_HealthCheck(t *testing.T) {
|
||||
metrics.NewGlobal(metricsConf, inmemSink)
|
||||
|
||||
pBackend := newTestBackend(t)
|
||||
testSealAccess, setErr := seal.NewToggleableTestSeal(nil)
|
||||
testSealAccess, setErrs := seal.NewToggleableTestSeal(nil)
|
||||
core, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{
|
||||
MetricSink: metricsutil.NewClusterMetricSink("", inmemSink),
|
||||
Physical: pBackend,
|
||||
})
|
||||
sealHealthTestIntervalNominal = 10 * time.Millisecond
|
||||
sealHealthTestIntervalUnhealthy = 10 * time.Millisecond
|
||||
autoSeal, err := NewAutoSeal(testSealAccess)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
autoSeal := NewAutoSeal(testSealAccess)
|
||||
autoSeal.SetCore(core)
|
||||
core.seal = autoSeal
|
||||
autoSeal.StartHealthCheck()
|
||||
defer autoSeal.StopHealthCheck()
|
||||
setErr(errors.New("disconnected"))
|
||||
setErrs[0](errors.New("disconnected"))
|
||||
|
||||
asu := strings.Join(autoSealUnavailableDuration, ".") + ";cluster=" + core.clusterName
|
||||
tries := 10
|
||||
for tries = 10; tries > 0; tries-- {
|
||||
intervals := inmemSink.Data()
|
||||
if len(intervals) == 1 {
|
||||
interval := inmemSink.Data()[0]
|
||||
|
||||
if _, ok := interval.Gauges[asu]; ok {
|
||||
if interval.Gauges[asu].Value > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !autoSeal.Healthy() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if tries == 0 {
|
||||
t.Fatalf("Expected value metric %s to be non-zero", asu)
|
||||
t.Fatalf("Expected to detect unhealthy seals")
|
||||
}
|
||||
|
||||
setErr(nil)
|
||||
setErrs[0](nil)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
intervals := inmemSink.Data()
|
||||
if len(intervals) == 1 {
|
||||
interval := inmemSink.Data()[0]
|
||||
|
||||
if _, ok := interval.Gauges[asu]; !ok {
|
||||
t.Fatalf("Expected metrics to include a value for gauge %s", asu)
|
||||
}
|
||||
if interval.Gauges[asu].Value != 0 {
|
||||
t.Fatalf("Expected value metric %s to be zero", asu)
|
||||
}
|
||||
if !autoSeal.Healthy() {
|
||||
t.Fatal("Expected seals to be healthy")
|
||||
}
|
||||
}
|
||||
|
||||
162
vault/seal_config.go
Normal file
162
vault/seal_config.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
)
|
||||
|
||||
// SealConfig is used to describe the seal configuration
|
||||
type SealConfig struct {
|
||||
// The type, for sanity checking. See SealConfigType for valid values.
|
||||
Type string `json:"type" mapstructure:"type"`
|
||||
|
||||
// SecretShares is the number of shares the secret is split into. This is
|
||||
// the N value of Shamir.
|
||||
SecretShares int `json:"secret_shares" mapstructure:"secret_shares"`
|
||||
|
||||
// SecretThreshold is the number of parts required to open the vault. This
|
||||
// is the T value of Shamir.
|
||||
SecretThreshold int `json:"secret_threshold" mapstructure:"secret_threshold"`
|
||||
|
||||
// PGPKeys is the array of public PGP keys used, if requested, to encrypt
|
||||
// the output unseal tokens. If provided, it sets the value of
|
||||
// SecretShares. Ordering is important.
|
||||
PGPKeys []string `json:"pgp_keys" mapstructure:"pgp_keys"`
|
||||
|
||||
// Nonce is a nonce generated by Vault used to ensure that when unseal keys
|
||||
// are submitted for a rekey operation, the rekey operation itself is the
|
||||
// one intended. This prevents hijacking of the rekey operation, since it
|
||||
// is unauthenticated.
|
||||
Nonce string `json:"nonce" mapstructure:"nonce"`
|
||||
|
||||
// Backup indicates whether or not a backup of PGP-encrypted unseal keys
|
||||
// should be stored at coreUnsealKeysBackupPath after successful rekeying.
|
||||
Backup bool `json:"backup" mapstructure:"backup"`
|
||||
|
||||
// How many keys to store, for seals that support storage. Always 0 or 1.
|
||||
StoredShares int `json:"stored_shares" mapstructure:"stored_shares"`
|
||||
|
||||
// Stores the progress of the rekey operation (key shares)
|
||||
RekeyProgress [][]byte `json:"-"`
|
||||
|
||||
// VerificationRequired indicates that after a rekey validation must be
|
||||
// performed (via providing shares from the new key) before the new key is
|
||||
// actually installed. This is omitted from JSON as we don't persist the
|
||||
// new key, it lives only in memory.
|
||||
VerificationRequired bool `json:"-"`
|
||||
|
||||
// VerificationKey is the new key that we will roll to after successful
|
||||
// validation
|
||||
VerificationKey []byte `json:"-"`
|
||||
|
||||
// VerificationNonce stores the current operation nonce for verification
|
||||
VerificationNonce string `json:"-"`
|
||||
|
||||
// Stores the progress of the verification operation (key shares)
|
||||
VerificationProgress [][]byte `json:"-"`
|
||||
|
||||
// Name is the name provided in the seal configuration to identify the seal
|
||||
Name string `json:"name" mapstructure:"name"`
|
||||
}
|
||||
|
||||
// Validate is used to sanity check the seal configuration
|
||||
func (s *SealConfig) Validate() error {
|
||||
if s.SecretShares < 1 {
|
||||
return fmt.Errorf("shares must be at least one")
|
||||
}
|
||||
if s.SecretThreshold < 1 {
|
||||
return fmt.Errorf("threshold must be at least one")
|
||||
}
|
||||
if s.SecretShares > 1 && s.SecretThreshold == 1 {
|
||||
return fmt.Errorf("threshold must be greater than one for multiple shares")
|
||||
}
|
||||
if s.SecretShares > 255 {
|
||||
return fmt.Errorf("shares must be less than 256")
|
||||
}
|
||||
if s.SecretThreshold > 255 {
|
||||
return fmt.Errorf("threshold must be less than 256")
|
||||
}
|
||||
if s.SecretThreshold > s.SecretShares {
|
||||
return fmt.Errorf("threshold cannot be larger than shares")
|
||||
}
|
||||
if s.StoredShares > 1 {
|
||||
return fmt.Errorf("stored keys cannot be larger than 1")
|
||||
}
|
||||
if len(s.PGPKeys) > 0 && len(s.PGPKeys) != s.SecretShares {
|
||||
return fmt.Errorf("count mismatch between number of provided PGP keys and number of shares")
|
||||
}
|
||||
if len(s.PGPKeys) > 0 {
|
||||
for _, keystring := range s.PGPKeys {
|
||||
data, err := base64.StdEncoding.DecodeString(keystring)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding given PGP key: %w", err)
|
||||
}
|
||||
_, err = openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(data)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing given PGP key: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SealConfig) Clone() *SealConfig {
|
||||
ret := &SealConfig{
|
||||
Type: s.Type,
|
||||
SecretShares: s.SecretShares,
|
||||
SecretThreshold: s.SecretThreshold,
|
||||
Nonce: s.Nonce,
|
||||
Backup: s.Backup,
|
||||
StoredShares: s.StoredShares,
|
||||
VerificationRequired: s.VerificationRequired,
|
||||
VerificationNonce: s.VerificationNonce,
|
||||
Name: s.Name,
|
||||
}
|
||||
if len(s.PGPKeys) > 0 {
|
||||
ret.PGPKeys = make([]string, len(s.PGPKeys))
|
||||
copy(ret.PGPKeys, s.PGPKeys)
|
||||
}
|
||||
if len(s.VerificationKey) > 0 {
|
||||
ret.VerificationKey = make([]byte, len(s.VerificationKey))
|
||||
copy(ret.VerificationKey, s.VerificationKey)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// SealConfigType specifies the "type" of a seal according to the following rules:
|
||||
// - For a defaultSeal, the type is SealConfigTypeShamir, since all defaultSeals use a shamir wrapper.
|
||||
//
|
||||
// - For an autoseal:
|
||||
// - if there is a single encryption wrapper, the type is the wrapper type
|
||||
// - if there are two or more encryption wrappers, the type is SealConfigTypeMultiseal
|
||||
//
|
||||
// - For a recovery seal, the type is SealConfigTypeShamir, since all recovery seals are defaultSeals.
|
||||
type SealConfigType string
|
||||
|
||||
const (
|
||||
SealConfigTypeMultiseal = SealConfigType("multiseal")
|
||||
SealConfigTypeShamir = SealConfigType(wrapping.WrapperTypeShamir)
|
||||
SealConfigTypePkcs11 = SealConfigType(wrapping.WrapperTypePkcs11)
|
||||
SealConfigTypeAwsKms = SealConfigType(wrapping.WrapperTypeAwsKms)
|
||||
SealConfigTypeHsmAutoDeprecated = SealConfigType(wrapping.WrapperTypeHsmAuto)
|
||||
|
||||
// SealConfigTypeRecovery is an alias for SealConfigTypeShamir since all recovery seals are
|
||||
// defaultSeals using shamir wrappers.
|
||||
SealConfigTypeRecovery = SealConfigTypeShamir
|
||||
|
||||
// SealConfigTypeRecoveryUnsupported is for convenience.
|
||||
SealConfigTypeRecoveryUnsupported = SealConfigType("unsupported")
|
||||
)
|
||||
|
||||
func (s SealConfigType) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s SealConfigType) IsSameAs(t string) bool {
|
||||
return s.String() == t
|
||||
}
|
||||
15
vault/seal_stubs_oss.go
Normal file
15
vault/seal_stubs_oss.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build !enterprise
|
||||
|
||||
package vault
|
||||
|
||||
//go:generate go run github.com/hashicorp/vault/tools/stubmaker
|
||||
|
||||
// isSealOldKeyError returns true if a value was decrypted using the
|
||||
// old "unwrapSeal".
|
||||
func isSealOldKeyError(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func startPartialSealRewrapping(c *Core) {
|
||||
// nothing to do
|
||||
}
|
||||
@@ -4,25 +4,31 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/go-hclog"
|
||||
aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
testing "github.com/mitchellh/go-testing-interface"
|
||||
)
|
||||
|
||||
func NewTestSeal(t testing.T, opts *seal.TestSealOpts) Seal {
|
||||
t.Helper()
|
||||
if opts == nil {
|
||||
opts = &seal.TestSealOpts{}
|
||||
}
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = logging.NewVaultLogger(hclog.Debug)
|
||||
}
|
||||
opts = seal.NewTestSealOpts(opts)
|
||||
logger := corehelpers.NewTestLogger(t).Named("sealAccess")
|
||||
|
||||
switch opts.StoredKeys {
|
||||
case seal.StoredKeysSupportedShamirRoot:
|
||||
newSeal := NewDefaultSeal(seal.NewAccess(aeadwrapper.NewShamirWrapper()))
|
||||
w := aeadwrapper.NewShamirWrapper()
|
||||
sealAccess, err := seal.NewAccessFromSealInfo(logger, opts.Generation, true, []seal.SealInfo{
|
||||
{
|
||||
Wrapper: w,
|
||||
Priority: 1,
|
||||
Name: "shamir",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating test seal", err)
|
||||
}
|
||||
newSeal := NewDefaultSeal(sealAccess)
|
||||
// Need StoredShares set or this will look like a legacy shamir seal.
|
||||
newSeal.SetCachedBarrierConfig(&SealConfig{
|
||||
StoredShares: 1,
|
||||
@@ -31,7 +37,18 @@ func NewTestSeal(t testing.T, opts *seal.TestSealOpts) Seal {
|
||||
})
|
||||
return newSeal
|
||||
case seal.StoredKeysNotSupported:
|
||||
newSeal := NewDefaultSeal(seal.NewAccess(aeadwrapper.NewShamirWrapper()))
|
||||
w := aeadwrapper.NewShamirWrapper()
|
||||
sealAccess, err := seal.NewAccessFromSealInfo(logger, opts.Generation, true, []seal.SealInfo{
|
||||
{
|
||||
Wrapper: w,
|
||||
Priority: 1,
|
||||
Name: "shamir",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating test seal", err)
|
||||
}
|
||||
newSeal := NewDefaultSeal(sealAccess)
|
||||
newSeal.SetCachedBarrierConfig(&SealConfig{
|
||||
StoredShares: 0,
|
||||
SecretThreshold: 1,
|
||||
@@ -40,10 +57,6 @@ func NewTestSeal(t testing.T, opts *seal.TestSealOpts) Seal {
|
||||
return newSeal
|
||||
default:
|
||||
access, _ := seal.NewTestSeal(opts)
|
||||
seal, err := NewAutoSeal(access)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return seal
|
||||
return NewAutoSeal(access)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,79 +9,158 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Seal Wrapping
|
||||
|
||||
// SealWrapValue creates a BlobInfo wrapper with the entryValue being optionally encrypted with the give seal Access.
|
||||
func SealWrapValue(ctx context.Context, access seal.Access, encrypt bool, entryValue []byte) (*wrapping.BlobInfo, error) {
|
||||
wrappedEntryValue := &wrapping.BlobInfo{
|
||||
Wrapped: false,
|
||||
Ciphertext: entryValue,
|
||||
type PartialWrapFailCallback func(context.Context, map[string]error) error
|
||||
|
||||
// Helper function to use for partial wrap fail callbacks where we don't want to allow a partial failure. See
|
||||
// for example barrier or recovery key wrapping. Just don't allow for those risky scenarios
|
||||
var DisallowPartialSealWrap = func(ctx context.Context, errs map[string]error) error {
|
||||
return seal.JoinSealWrapErrors("not allowing operation to proceed without full wrapping involving all configured seals", errs)
|
||||
}
|
||||
|
||||
// SealWrapValue creates a SealWrappedValue wrapper with the entryValue being optionally encrypted with the give seal Access.
|
||||
func SealWrapValue(ctx context.Context, access seal.Access, encrypt bool, entryValue []byte, wrapFailCallback PartialWrapFailCallback) (*SealWrappedValue, error) {
|
||||
if access == nil {
|
||||
return newTransitorySealWrappedValue(&wrapping.BlobInfo{
|
||||
Wrapped: false,
|
||||
Ciphertext: entryValue,
|
||||
}), nil
|
||||
}
|
||||
if !encrypt {
|
||||
// Maybe this should also be a transitory value, since we want to encrypt
|
||||
// as soon as we can?
|
||||
return NewPlaintextSealWrappedValue(access.Generation(), entryValue), nil
|
||||
}
|
||||
|
||||
if access != nil && encrypt {
|
||||
swi, err := access.Encrypt(ctx, entryValue, nil)
|
||||
if err != nil {
|
||||
multiWrapValue, errs := access.Encrypt(ctx, entryValue, nil)
|
||||
if multiWrapValue == nil {
|
||||
// no seal encryption was successful
|
||||
return nil, seal.JoinSealWrapErrors("error seal wrapping value: encryption generated no results", errs)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
// Partial failure, note this by calling the provided callback, if present
|
||||
if err := wrapFailCallback(ctx, errs); err != nil {
|
||||
// If the callback returns an error, caller is indicating it doesn't want this to proceed
|
||||
return nil, err
|
||||
}
|
||||
wrappedEntryValue.Wrapped = true
|
||||
wrappedEntryValue.Ciphertext = swi.Ciphertext
|
||||
wrappedEntryValue.Iv = swi.Iv
|
||||
wrappedEntryValue.Hmac = swi.Hmac
|
||||
wrappedEntryValue.KeyInfo = swi.KeyInfo
|
||||
}
|
||||
|
||||
return wrappedEntryValue, nil
|
||||
// Why are we "cleaning up" the blob infos?
|
||||
var ret []*wrapping.BlobInfo
|
||||
for _, blobInfo := range multiWrapValue.Slots {
|
||||
ret = append(ret, &wrapping.BlobInfo{
|
||||
Wrapped: true,
|
||||
Ciphertext: blobInfo.Ciphertext,
|
||||
Iv: blobInfo.Iv,
|
||||
Hmac: blobInfo.Hmac,
|
||||
KeyInfo: blobInfo.KeyInfo,
|
||||
})
|
||||
}
|
||||
|
||||
return NewSealWrappedValue(&seal.MultiWrapValue{
|
||||
Generation: multiWrapValue.Generation,
|
||||
Slots: ret,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// MarshalSealWrappedValue marshals a BlobInfo into a byte slice.
|
||||
func MarshalSealWrappedValue(wrappedEntryValue *wrapping.BlobInfo) ([]byte, error) {
|
||||
return proto.Marshal(wrappedEntryValue)
|
||||
}
|
||||
// UnsealWrapValue uses the seal Access to decrypt the wrappedEntryValue. It returns the decrypted value
|
||||
// and a flag indicating whether the wrappedEntryValue is current (according to Access.IsUpToDate).
|
||||
// migration is in progress.
|
||||
func UnsealWrapValue(ctx context.Context, access seal.Access, entryKey string, wrappedEntryValue *SealWrappedValue) (entryValue []byte, uptodate bool, err error) {
|
||||
multiWrapValue := &seal.MultiWrapValue{
|
||||
Generation: wrappedEntryValue.GetGeneration(),
|
||||
}
|
||||
for _, blobInfo := range wrappedEntryValue.GetSlots() {
|
||||
// TODO(SEALHA): Why doesn't sealWrapValue() set ValuePath? Could it be a migration issue? Can we stop setting it?
|
||||
blobInfoWithValuePath := &wrapping.BlobInfo{
|
||||
ValuePath: entryKey,
|
||||
Ciphertext: blobInfo.Ciphertext,
|
||||
Iv: blobInfo.Iv,
|
||||
Hmac: blobInfo.Hmac,
|
||||
KeyInfo: blobInfo.KeyInfo,
|
||||
}
|
||||
multiWrapValue.Slots = append(multiWrapValue.Slots, blobInfoWithValuePath)
|
||||
}
|
||||
|
||||
// UnmarshalSealWrappedValue attempts to unmarshal a BlobInfo.
|
||||
func UnmarshalSealWrappedValue(value []byte) (*wrapping.BlobInfo, error) {
|
||||
wrappedEntryValue := &wrapping.BlobInfo{}
|
||||
err := proto.Unmarshal(value, wrappedEntryValue)
|
||||
entryValue, uptodate, err = access.Decrypt(ctx, multiWrapValue, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if isSealOldKeyError(err) {
|
||||
uptodate = false
|
||||
} else {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
return wrappedEntryValue, nil
|
||||
|
||||
return entryValue, uptodate, nil
|
||||
}
|
||||
|
||||
// UnmarshalSealWrappedValueWithCanary unmarshalls a byte array into a BlobInfo, taking care of
|
||||
// removing the 's' canary value. Note that if the value does not end with the canary value,
|
||||
// or a BlobInfo cannot be unmarshalled, nil is returned.
|
||||
func UnmarshalSealWrappedValueWithCanary(value []byte) *wrapping.BlobInfo {
|
||||
// MarshalSealWrappedValue marshals a SealWrappedValue into a byte slice. If the seal wrapped value contains
|
||||
// a single wrapping.BlobInfo, the BlobInfo will be marshalled directly; otherwise the SealWrappedValue
|
||||
// will be.
|
||||
func MarshalSealWrappedValue(wrappedEntryValue *SealWrappedValue) ([]byte, error) {
|
||||
if len(wrappedEntryValue.value.Slots) > 1 {
|
||||
return wrappedEntryValue.marshal()
|
||||
}
|
||||
|
||||
return proto.Marshal(wrappedEntryValue.value.Slots[0])
|
||||
}
|
||||
|
||||
// UnmarshalSealWrappedValue attempts to unmarshal a SealWrappedValue. This method can unmarshal marshalled
|
||||
// SealWrappedValues as well as wrapping.BlobInfos. When a BlobInfo is encountered, a "transitory"
|
||||
// SealWrappedValue will be returned.
|
||||
func UnmarshalSealWrappedValue(value []byte) (*SealWrappedValue, error) {
|
||||
swv := &SealWrappedValue{}
|
||||
swvErr := swv.unmarshal(value)
|
||||
if swvErr == nil {
|
||||
return swv, nil
|
||||
}
|
||||
|
||||
blobInfo := &wrapping.BlobInfo{}
|
||||
blobInfoErr := proto.Unmarshal(value, blobInfo)
|
||||
if blobInfoErr == nil {
|
||||
return newTransitorySealWrappedValue(blobInfo), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error unmarshalling seal wrapped value: %w, %w", swvErr, blobInfoErr)
|
||||
}
|
||||
|
||||
// UnmarshalSealWrappedValueWithCanary unmarshalls a byte array into a SealWrappedValue, taking care of
|
||||
// removing the 's' canary value.
|
||||
// This method returns true if a SealWrappedValue was successfully unmarshaled.
|
||||
func UnmarshalSealWrappedValueWithCanary(value []byte) (*SealWrappedValue, bool) {
|
||||
eLen := len(value)
|
||||
if eLen > 0 && value[eLen-1] == 's' {
|
||||
if wrappedEntryValue, err := UnmarshalSealWrappedValue(value[:eLen-1]); err == nil {
|
||||
return wrappedEntryValue
|
||||
return wrappedEntryValue, true
|
||||
}
|
||||
// Else, note that having the canary value present is not a guarantee that
|
||||
// the value is wrapped, so if there is an error we will simply return a nil BlobInfo.
|
||||
}
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Stored Barrier Keys (a.k.a. Root Key)
|
||||
|
||||
// SealWrapStoredBarrierKeys takes the json-marshalled barriers (root) keys, encrypts them using the seal access,
|
||||
// SealWrapStoredBarrierKeys takes the barrier (root) keys, encrypts them using the seal access,
|
||||
// and returns a physical.Entry for storage.
|
||||
func SealWrapStoredBarrierKeys(ctx context.Context, access seal.Access, keys [][]byte) (*physical.Entry, error) {
|
||||
// Note that even though keys is a slice, it seems to always contain a single key.
|
||||
buf, err := json.Marshal(keys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode keys for storage: %w", err)
|
||||
}
|
||||
|
||||
blobInfo, err := SealWrapValue(ctx, access, true, buf)
|
||||
wrappedEntryValue, err := SealWrapValue(ctx, access, true, buf, DisallowPartialSealWrap)
|
||||
if err != nil {
|
||||
return nil, &ErrEncrypt{Err: fmt.Errorf("failed to encrypt keys for storage: %w", err)}
|
||||
}
|
||||
@@ -90,9 +169,11 @@ func SealWrapStoredBarrierKeys(ctx context.Context, access seal.Access, keys [][
|
||||
// returned by access.Encrypt() was marshalled directly. It probably would not matter if the value
|
||||
// was true, but setting if to false here makes TestSealWrapBackend_StorageBarrierKeyUpgrade_FromIVEntry
|
||||
// pass (maybe other tests as well?).
|
||||
blobInfo.Wrapped = false
|
||||
for _, blobInfo := range wrappedEntryValue.GetSlots() {
|
||||
blobInfo.Wrapped = false
|
||||
}
|
||||
|
||||
wrappedValue, err := MarshalSealWrappedValue(blobInfo)
|
||||
wrappedValue, err := MarshalSealWrappedValue(wrappedEntryValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal value for storage: %w", err)
|
||||
}
|
||||
@@ -104,12 +185,16 @@ func SealWrapStoredBarrierKeys(ctx context.Context, access seal.Access, keys [][
|
||||
|
||||
// UnsealWrapStoredBarrierKeys is the counterpart to SealWrapStoredBarrierKeys.
|
||||
func UnsealWrapStoredBarrierKeys(ctx context.Context, access seal.Access, pe *physical.Entry) ([][]byte, error) {
|
||||
blobInfo, err := UnmarshalSealWrappedValue(pe.Value)
|
||||
wrappedEntryValue, err := UnmarshalSealWrappedValue(pe.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to proto decode stored keys: %w", err)
|
||||
}
|
||||
|
||||
pt, err := access.Decrypt(ctx, blobInfo, nil)
|
||||
return decodeBarrierKeys(ctx, access, &wrappedEntryValue.value)
|
||||
}
|
||||
|
||||
func decodeBarrierKeys(ctx context.Context, access seal.Access, multiWrapValue *seal.MultiWrapValue) ([][]byte, error) {
|
||||
pt, _, err := access.Decrypt(ctx, multiWrapValue, nil)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "message authentication failed") {
|
||||
return nil, &ErrInvalidKey{Reason: fmt.Sprintf("failed to decrypt keys from storage: %v", err)}
|
||||
@@ -130,17 +215,12 @@ func UnsealWrapStoredBarrierKeys(ctx context.Context, access seal.Access, pe *ph
|
||||
|
||||
// SealWrapRecoveryKey encrypts the recovery key using the given seal access and returns a physical.Entry for storage.
|
||||
func SealWrapRecoveryKey(ctx context.Context, access seal.Access, key []byte) (*physical.Entry, error) {
|
||||
blobInfo, err := SealWrapValue(ctx, access, true, key)
|
||||
wrappedEntryValue, err := SealWrapValue(ctx, access, true, key, DisallowPartialSealWrap)
|
||||
if err != nil {
|
||||
return nil, &ErrEncrypt{Err: fmt.Errorf("failed to encrypt keys for storage: %w", err)}
|
||||
return nil, &ErrEncrypt{Err: fmt.Errorf("failed to encrypt recovery key for storage: %w", err)}
|
||||
}
|
||||
|
||||
// Not that we set Wrapped to false since it used to be that the BlobInfo returned by access.Encrypt()
|
||||
// was marshalled directly. It probably would not matter if the value was true, it doesn't seem to
|
||||
// break any tests.
|
||||
blobInfo.Wrapped = false
|
||||
|
||||
wrappedValue, err := MarshalSealWrappedValue(blobInfo)
|
||||
wrappedValue, err := MarshalSealWrappedValue(wrappedEntryValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal value for storage: %w", err)
|
||||
}
|
||||
@@ -151,15 +231,12 @@ func SealWrapRecoveryKey(ctx context.Context, access seal.Access, key []byte) (*
|
||||
}
|
||||
|
||||
// UnsealWrapRecoveryKey is the counterpart to SealWrapRecoveryKey.
|
||||
func UnsealWrapRecoveryKey(ctx context.Context, access seal.Access, pe *physical.Entry) ([]byte, *wrapping.BlobInfo, error) {
|
||||
blobInfo, err := UnmarshalSealWrappedValue(pe.Value)
|
||||
func UnsealWrapRecoveryKey(ctx context.Context, access seal.Access, pe *physical.Entry) ([]byte, error) {
|
||||
wrappedEntryValue, err := UnmarshalSealWrappedValue(pe.Value)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to proto decode recevory key: %w", err)
|
||||
return nil, fmt.Errorf("failed to proto decode recevory key: %w", err)
|
||||
}
|
||||
|
||||
pt, err := access.Decrypt(ctx, blobInfo, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to decrypt recovery key from storage: %w", err)
|
||||
}
|
||||
return pt, blobInfo, nil
|
||||
pt, _, err := UnsealWrapValue(ctx, access, pe.Key, wrappedEntryValue)
|
||||
return pt, err
|
||||
}
|
||||
|
||||
95
vault/seal_util_test.go
Normal file
95
vault/seal_util_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMarshalSealWrappedValue(t *testing.T) {
|
||||
isBlobInfo := func(bytes []byte) bool {
|
||||
err := proto.Unmarshal(bytes, &wrapping.BlobInfo{})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
isSealWrapValue := func(bytes []byte) bool {
|
||||
if isBlobInfo(bytes) {
|
||||
return false
|
||||
}
|
||||
err := (&SealWrappedValue{}).unmarshal(bytes)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
blobInfo := &wrapping.BlobInfo{
|
||||
Wrapped: false,
|
||||
Ciphertext: []byte("plaintext, actually"),
|
||||
}
|
||||
oneBlobInfo := []*wrapping.BlobInfo{blobInfo}
|
||||
twoBlobInfos := []*wrapping.BlobInfo{blobInfo, blobInfo}
|
||||
|
||||
wantBlobInfo := true
|
||||
wantMultiWrappedValue := !wantBlobInfo
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value *SealWrappedValue
|
||||
want bool
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "a BlobInfo generation 0",
|
||||
value: &SealWrappedValue{
|
||||
value: seal.MultiWrapValue{
|
||||
Generation: 0,
|
||||
Slots: oneBlobInfo,
|
||||
},
|
||||
},
|
||||
want: wantBlobInfo,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "two BlobInfos generation 0",
|
||||
value: &SealWrappedValue{
|
||||
value: seal.MultiWrapValue{
|
||||
Generation: 0,
|
||||
Slots: twoBlobInfos,
|
||||
},
|
||||
},
|
||||
want: wantMultiWrappedValue,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "two BlobInfos generation 42",
|
||||
value: &SealWrappedValue{
|
||||
value: seal.MultiWrapValue{
|
||||
Generation: 42,
|
||||
Slots: twoBlobInfos,
|
||||
},
|
||||
},
|
||||
want: wantMultiWrappedValue,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := MarshalSealWrappedValue(tt.value)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("MarshalSealWrappedValue(%v)", tt.value)) {
|
||||
return
|
||||
}
|
||||
if tt.want == wantBlobInfo {
|
||||
assertTrue(t, isBlobInfo(got), "expecting bytes to be a marshalled BlobInfo")
|
||||
} else {
|
||||
assertTrue(t, isSealWrapValue(got), "expecting bytes to be a marshalled SealWrappedValue")
|
||||
}
|
||||
|
||||
unmarshalled, err := UnmarshalSealWrappedValue(got)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, proto.Equal(&tt.value.value, &unmarshalled.value), "%v != %v", tt.value.value, unmarshalled.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
162
vault/seal_wrapped_value.go
Normal file
162
vault/seal_wrapped_value.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
)
|
||||
|
||||
// transitoryGeneration is the Generation value used by SealWrappebValues for
|
||||
// entries that need to be upgraded from pre Vault 1.15.
|
||||
// Note that the value is 0, since that is the default value for BlobInfo.Generation.
|
||||
const transitoryGeneration = uint64(0)
|
||||
|
||||
type SealWrappedValue struct {
|
||||
value seal.MultiWrapValue
|
||||
}
|
||||
|
||||
// NewSealWrappedValue creates a new seal wrapped value. Note that this
|
||||
// method will change to accept a slice of BlobInfos when multi-seal wrapping
|
||||
// is added.
|
||||
func NewSealWrappedValue(multiWrapValue *seal.MultiWrapValue) *SealWrappedValue {
|
||||
if len(multiWrapValue.Slots) == 0 {
|
||||
panic("cannot create a SealWrappedValue without a BlobInfo")
|
||||
}
|
||||
|
||||
return &SealWrappedValue{value: *multiWrapValue}
|
||||
}
|
||||
|
||||
func NewPlaintextSealWrappedValue(generation uint64, plaintext []byte) *SealWrappedValue {
|
||||
// TODO(victorr): see if we can use plaintext instead of blobInfo.Wrapped = false
|
||||
return NewSealWrappedValue(&seal.MultiWrapValue{
|
||||
Generation: generation,
|
||||
Slots: []*wrapping.BlobInfo{
|
||||
{
|
||||
Wrapped: false,
|
||||
Ciphertext: plaintext,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func newTransitorySealWrappedValue(blobInfo *wrapping.BlobInfo) *SealWrappedValue {
|
||||
return NewSealWrappedValue(&seal.MultiWrapValue{
|
||||
Generation: transitoryGeneration,
|
||||
Slots: []*wrapping.BlobInfo{blobInfo},
|
||||
})
|
||||
}
|
||||
|
||||
func (swv *SealWrappedValue) isTransitory() bool {
|
||||
return swv.value.Generation == transitoryGeneration
|
||||
}
|
||||
|
||||
func (swv *SealWrappedValue) isPlaintext() bool {
|
||||
return !swv.isEncrypted()
|
||||
}
|
||||
|
||||
// isEncrypted returns true if a BlobInfo has flag Wrapped set to true.
|
||||
func (swv *SealWrappedValue) isEncrypted() bool {
|
||||
// Note that we set Wrapped == false for StoredBarrierKeysPath and recoveryKeyPath, so check
|
||||
// for the presence of KeyInfo as well as the Wrapped flag.
|
||||
|
||||
for _, blobInfo := range swv.value.Slots {
|
||||
if blobInfo.Wrapped || (blobInfo.KeyInfo != nil) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (swv *SealWrappedValue) GetGeneration() uint64 {
|
||||
return swv.value.Generation
|
||||
}
|
||||
|
||||
func (swv *SealWrappedValue) GetSlots() []*wrapping.BlobInfo {
|
||||
return swv.value.Slots
|
||||
}
|
||||
|
||||
func (swv *SealWrappedValue) getPlaintextValue() ([]byte, error) {
|
||||
if swv.isEncrypted() {
|
||||
return nil, errors.New("cannot return plaintext value from a SealWrappedValue with encrypted data")
|
||||
}
|
||||
|
||||
return swv.value.Slots[0].Ciphertext, nil
|
||||
}
|
||||
|
||||
var sealWrappedValueHeader = []byte("multiwrapvalue:1")
|
||||
|
||||
const sealWrappedValueHeaderLength = 16
|
||||
|
||||
func init() {
|
||||
// Check that the header is 16 bytes long.
|
||||
if len(sealWrappedValueHeader) != sealWrappedValueHeaderLength {
|
||||
panic(fmt.Sprintf("sealWrappedValueHeader must be %d bytes long, but it is %d", sealWrappedValueHeaderLength, len(sealWrappedValueHeader)))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal a seal wrapped value. DO NOT USE DIRECTLY, use MarshalSealWrappedValue instead.
|
||||
// The marshalled bytes consists of:
|
||||
// a) A 16 byte header
|
||||
// b) 4 bytes specifying the length of the remaining bytes
|
||||
// c) the protobuf marshalling of the MultiWrapValue
|
||||
func (swv *SealWrappedValue) marshal() ([]byte, error) {
|
||||
protoBytes, err := proto.Marshal(&swv.value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var appendErr error
|
||||
write := func(value []byte) {
|
||||
if appendErr == nil {
|
||||
_, appendErr = buf.Write(value)
|
||||
}
|
||||
}
|
||||
|
||||
lengthBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(lengthBytes, uint32(len(protoBytes)))
|
||||
|
||||
write(sealWrappedValueHeader)
|
||||
write(lengthBytes)
|
||||
write(protoBytes)
|
||||
if appendErr != nil {
|
||||
return nil, appendErr
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Unmarshal a seal wrapped value. DO NOT USE DIRECTLY, use UnmarshalSealWrappedValue instead.
|
||||
func (swv *SealWrappedValue) unmarshal(value []byte) error {
|
||||
if len(value) < sealWrappedValueHeaderLength+4 {
|
||||
return errors.New("error unmarshalling SealWrappedValue, not enough bytes")
|
||||
}
|
||||
|
||||
header := value[0:sealWrappedValueHeaderLength]
|
||||
lengthBytes := value[sealWrappedValueHeaderLength : sealWrappedValueHeaderLength+4]
|
||||
protoBytes := value[sealWrappedValueHeaderLength+4:]
|
||||
|
||||
if bytes.Compare(sealWrappedValueHeader, header) != 0 {
|
||||
return errors.New("error unmarshalling SealWrappedValue, header mismatch")
|
||||
}
|
||||
length := binary.BigEndian.Uint32(lengthBytes)
|
||||
if int(length) != len(protoBytes) {
|
||||
return errors.New("error unmarshalling SealWrappedValue, length mismatch")
|
||||
}
|
||||
|
||||
if err := proto.Unmarshal(protoBytes, &swv.value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (swv *SealWrappedValue) getValue() *seal.MultiWrapValue {
|
||||
return &swv.value
|
||||
}
|
||||
103
vault/seal_wrapped_value_test.go
Normal file
103
vault/seal_wrapped_value_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSealWrappedValue_marshal_unmarshal(t *testing.T) {
|
||||
blobInfo := &wrapping.BlobInfo{
|
||||
Wrapped: false,
|
||||
Ciphertext: []byte("plaintext, actually"),
|
||||
}
|
||||
oneBlobInfo := []*wrapping.BlobInfo{blobInfo}
|
||||
twoBlobInfos := []*wrapping.BlobInfo{blobInfo, blobInfo}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value *SealWrappedValue
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "a BlobInfo generation 0",
|
||||
value: &SealWrappedValue{
|
||||
value: seal.MultiWrapValue{
|
||||
Generation: 0,
|
||||
Slots: oneBlobInfo,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "two BlobInfos generation 7",
|
||||
value: &SealWrappedValue{
|
||||
value: seal.MultiWrapValue{
|
||||
Generation: 7,
|
||||
Slots: twoBlobInfos,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "two BlobInfos generation 42",
|
||||
value: &SealWrappedValue{
|
||||
value: seal.MultiWrapValue{
|
||||
Generation: 42,
|
||||
Slots: twoBlobInfos,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.value.marshal()
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("marshal()")) {
|
||||
return
|
||||
}
|
||||
|
||||
unmarshalled := &SealWrappedValue{}
|
||||
assert.NoError(t, unmarshalled.unmarshal(got))
|
||||
assert.True(t, proto.Equal(&tt.value.value, &unmarshalled.value), "%v != %v", tt.value.value, unmarshalled.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSealWrappedValue_unmarshalMultiWrapError_error_conditions(t *testing.T) {
|
||||
unmarshal := (&SealWrappedValue{}).unmarshal
|
||||
|
||||
assert.EqualError(t, unmarshal([]byte{1, 2, 3}), "error unmarshalling SealWrappedValue, not enough bytes")
|
||||
|
||||
swv := &SealWrappedValue{
|
||||
value: seal.MultiWrapValue{
|
||||
Generation: 0,
|
||||
Slots: []*wrapping.BlobInfo{
|
||||
{
|
||||
Wrapped: false,
|
||||
Ciphertext: []byte("plaintext, actually"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bytes, err := swv.marshal()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, unmarshal(bytes))
|
||||
|
||||
badHeader := []byte("oops")
|
||||
badHeader = append(badHeader, bytes[len(badHeader):]...)
|
||||
|
||||
assert.EqualError(t, unmarshal(badHeader), "error unmarshalling SealWrappedValue, header mismatch")
|
||||
|
||||
badLength := bytes[0:sealWrappedValueHeaderLength]
|
||||
badLength = append(badLength, 1, 2, 3, 4)
|
||||
badLength = append(badLength, bytes[sealWrappedValueHeaderLength+4:]...)
|
||||
|
||||
assert.EqualError(t, unmarshal(badLength), "error unmarshalling SealWrappedValue, length mismatch")
|
||||
}
|
||||
@@ -74,16 +74,20 @@ func (d *sealUnwrapper) unwrap(ctx context.Context, key string) (entry, unwrappe
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
se := UnmarshalSealWrappedValueWithCanary(entry.Value)
|
||||
wrappedEntryValue, unmarshaled := UnmarshalSealWrappedValueWithCanary(entry.Value)
|
||||
switch {
|
||||
case se == nil:
|
||||
case !unmarshaled:
|
||||
unwrappedEntry = entry
|
||||
case se.Wrapped:
|
||||
case wrappedEntryValue.isEncrypted():
|
||||
return nil, nil, fmt.Errorf("cannot decode sealwrapped storage entry %q", entry.Key)
|
||||
default:
|
||||
pt, err := wrappedEntryValue.getPlaintextValue()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
unwrappedEntry = &physical.Entry{
|
||||
Key: entry.Key,
|
||||
Value: se.Ciphertext,
|
||||
Value: pt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user