1
0

Seal migration (OSS) (#781)

This commit is contained in:
Jeff Mitchell 2018-10-22 23:34:02 -07:00 committed by GitHub
parent c967980020
commit 2f5b464bc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 807 additions and 227 deletions

View File

@ -41,6 +41,15 @@ func (c *Sys) Unseal(shard string) (*SealStatusResponse, error) {
return sealStatusRequest(c, r)
}
func (c *Sys) UnsealWithOptions(opts *UnsealOpts) (*SealStatusResponse, error) {
r := c.c.NewRequest("PUT", "/v1/sys/unseal")
if err := r.SetJSONBody(opts); err != nil {
return nil, err
}
return sealStatusRequest(c, r)
}
func sealStatusRequest(c *Sys, r *Request) (*SealStatusResponse, error) {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
@ -64,7 +73,14 @@ type SealStatusResponse struct {
Progress int `json:"progress"`
Nonce string `json:"nonce"`
Version string `json:"version"`
Migration bool `json:"migration"`
ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"`
RecoverySeal bool `json:"recovery_seal"`
}
type UnsealOpts struct {
Key string `json:"key"`
Reset bool `json:"reset"`
Migrate bool `json:"migrate"`
}

View File

@ -330,6 +330,10 @@ func OutputSealStatus(ui cli.Ui, client *api.Client, status *api.SealStatusRespo
out = append(out, fmt.Sprintf("Unseal Nonce | %s", status.Nonce))
}
if status.Migration {
out = append(out, fmt.Sprintf("Seal Migration in Progress | %t", status.Migration))
}
out = append(out, fmt.Sprintf("Version | %s", status.Version))
if status.ClusterName != "" && status.ClusterID != "" {

View File

@ -79,7 +79,7 @@ func TestLoginCommand_Run(t *testing.T) {
t.Fatal(err)
}
if l, exp := len(storedToken), vault.TokenLength; l != exp {
if l, exp := len(storedToken), vault.TokenLength+2; l != exp {
t.Errorf("expected token to be %d characters, was %d: %q", exp, l, storedToken)
}
})
@ -206,7 +206,7 @@ func TestLoginCommand_Run(t *testing.T) {
// Verify only the token was printed
token := ui.OutputWriter.String()
if l, exp := len(token), vault.TokenLength; l != exp {
if l, exp := len(token), vault.TokenLength+2; l != exp {
t.Errorf("expected token to be %d characters, was %d: %q", exp, l, token)
}

View File

@ -161,13 +161,11 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Run("cancel", func(t *testing.T) {
t.Parallel()
otp := "3JhHkONiyiaNYj14nnD9xZQS"
client, closer := testVaultServer(t)
defer closer()
// Initialize a generation
if _, err := client.Sys().GenerateRootInit(otp, ""); err != nil {
if _, err := client.Sys().GenerateRootInit("", ""); err != nil {
t.Fatal(err)
}
@ -200,8 +198,6 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Run("init_otp", func(t *testing.T) {
t.Parallel()
otp := "3JhHkONiyiaNYj14nnD9xZQS"
client, closer := testVaultServer(t)
defer closer()
@ -210,7 +206,6 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
code := cmd.Run([]string{
"-init",
"-otp", otp,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
@ -350,7 +345,7 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Fatal(err)
}
if l, exp := len(token), vault.TokenLength; l != exp {
if l, exp := len(token), vault.TokenLength+2; l != exp {
t.Errorf("expected %d to be %d: %s", l, exp, token)
}
})
@ -436,7 +431,7 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Fatal(err)
}
if l, exp := len(token), vault.TokenLength; l != exp {
if l, exp := len(token), vault.TokenLength+2; l != exp {
t.Errorf("expected %d to be %d: %s", l, exp, token)
}
})

View File

@ -333,7 +333,7 @@ func TestOperatorInitCommand_Run(t *testing.T) {
root := match[0][1]
decryptedRoot := testPGPDecrypt(t, pgpkeys.TestPrivKey1, root)
if l, exp := len(decryptedRoot), vault.TokenLength; l != exp {
if l, exp := len(decryptedRoot), vault.TokenLength+2; l != exp {
t.Errorf("expected %d to be %d", l, exp)
}
})

View File

@ -149,7 +149,7 @@ func (c *OperatorMigrateCommand) migrate(config *migratorConfig) error {
}
if c.flagReset {
if err := SetMigration(from, false); err != nil {
if err := SetStorageMigration(from, false); err != nil {
return errwrap.Wrapf("error reseting migration lock: {{err}}", err)
}
return nil
@ -160,7 +160,7 @@ func (c *OperatorMigrateCommand) migrate(config *migratorConfig) error {
return errwrap.Wrapf("error mounting 'storage_destination': {{err}}", err)
}
migrationStatus, err := CheckMigration(from)
migrationStatus, err := CheckStorageMigration(from)
if err != nil {
return errors.New("error checking migration status")
}
@ -169,11 +169,11 @@ func (c *OperatorMigrateCommand) migrate(config *migratorConfig) error {
return fmt.Errorf("Storage migration in progress (started: %s).", migrationStatus.Start.Format(time.RFC3339))
}
if err := SetMigration(from, true); err != nil {
if err := SetStorageMigration(from, true); err != nil {
return errwrap.Wrapf("error setting migration lock: {{err}}", err)
}
defer SetMigration(from, false)
defer SetStorageMigration(from, false)
ctx, cancelFunc := context.WithCancel(context.Background())
@ -197,7 +197,7 @@ func (c *OperatorMigrateCommand) migrate(config *migratorConfig) error {
// migrateAll copies all keys in lexicographic order.
func (c *OperatorMigrateCommand) migrateAll(ctx context.Context, from physical.Backend, to physical.Backend) error {
return dfsScan(ctx, from, func(ctx context.Context, path string) error {
if path < c.flagStart || path == migrationLock || path == vault.CoreLockPath {
if path < c.flagStart || path == storageMigrationLock || path == vault.CoreLockPath {
return nil
}

View File

@ -264,7 +264,7 @@ func generateData() map[string][]byte {
}
// Add special keys that should be excluded from migration
result[migrationLock] = []byte{}
result[storageMigrationLock] = []byte{}
result[vault.CoreLockPath] = []byte{}
return result
@ -292,7 +292,7 @@ func compareStoredData(s physical.Backend, ref map[string][]byte, start string)
return err
}
if k == migrationLock || k == vault.CoreLockPath {
if k == storageMigrationLock || k == vault.CoreLockPath {
if entry == nil {
continue
}

View File

@ -6,6 +6,7 @@ import (
"os"
"strings"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/password"
"github.com/mitchellh/cli"
"github.com/posener/complete"
@ -17,7 +18,8 @@ var _ cli.CommandAutocomplete = (*OperatorUnsealCommand)(nil)
type OperatorUnsealCommand struct {
*BaseCommand
flagReset bool
flagReset bool
flagMigrate bool
testOutput io.Writer // for tests
}
@ -64,6 +66,16 @@ func (c *OperatorUnsealCommand) Flags() *FlagSets {
Usage: "Discard any previously entered keys to the unseal process.",
})
f.BoolVar(&BoolVar{
Name: "migrate",
Aliases: []string{},
Target: &c.flagMigrate,
Default: false,
EnvVar: "",
Completion: complete.PredictNothing,
Usage: "Indicate that this share is provided with the intent that it is part of a seal migration process.",
})
return set
}
@ -135,7 +147,10 @@ func (c *OperatorUnsealCommand) Run(args []string) int {
unsealKey = strings.TrimSpace(value)
}
status, err := client.Sys().Unseal(unsealKey)
status, err := client.Sys().UnsealWithOptions(&api.UnsealOpts{
Key: unsealKey,
Migrate: c.flagMigrate,
})
if err != nil {
c.UI.Error(fmt.Sprintf("Error unsealing: %s", err))
return 2

View File

@ -0,0 +1,291 @@
package command
import (
"context"
"encoding/base64"
"testing"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/server"
"github.com/hashicorp/vault/helper/logging"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/physical"
physInmem "github.com/hashicorp/vault/physical/inmem"
"github.com/hashicorp/vault/shamir"
"github.com/hashicorp/vault/vault"
"github.com/hashicorp/vault/vault/seal"
)
func TestSealMigration(t *testing.T) {
logger := logging.NewVaultLogger(hclog.Trace)
phys, err := physInmem.NewInmem(nil, logger)
if err != nil {
t.Fatal(err)
}
haPhys, err := physInmem.NewInmemHA(nil, logger)
if err != nil {
t.Fatal(err)
}
shamirSeal := vault.NewDefaultSeal()
coreConfig := &vault.CoreConfig{
Seal: shamirSeal,
Physical: phys,
HAPhysical: haPhys.(physical.HABackend),
DisableSealWrap: true,
}
clusterConfig := &vault.TestClusterOptions{
Logger: logger,
HandlerFunc: vaulthttp.Handler,
SkipInit: true,
NumCores: 1,
}
var keys []string
var rootToken string
// First: start up as normal with shamir seal, init it
{
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
coreConfig = cluster.Cores[0].CoreConfig
// Init
resp, err := client.Sys().Init(&api.InitRequest{
SecretShares: 2,
SecretThreshold: 2,
})
if err != nil {
t.Fatal(err)
}
keys = resp.KeysB64
rootToken = resp.RootToken
// Now seal
cluster.Cleanup()
// This will prevent cleanup from running again on the defer
cluster.Cores = nil
}
// Second: start up as normal with shamir seal and unseal, make sure
// everything is normal
{
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client
client.SetToken(rootToken)
var resp *api.SealStatusResponse
for _, key := range keys {
resp, err = client.Sys().Unseal(key)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response")
}
}
if resp.Sealed {
t.Fatal("expected unsealed state")
}
cluster.Cleanup()
cluster.Cores = nil
}
var autoSeal vault.Seal
// Third: create an autoseal and activate migration
{
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0].Core
newSeal := vault.NewAutoSeal(&seal.TestSeal{})
newSeal.SetCore(core)
autoSeal = newSeal
if err := adjustCoreForSealMigration(context.Background(), core, coreConfig, newSeal, &server.Config{
Seal: &server.Seal{
Type: "test-auto",
},
}); err != nil {
t.Fatal(err)
}
client := cluster.Cores[0].Client
client.SetToken(rootToken)
var resp *api.SealStatusResponse
unsealOpts := &api.UnsealOpts{}
for _, key := range keys {
unsealOpts.Key = key
unsealOpts.Migrate = false
resp, err = client.Sys().UnsealWithOptions(unsealOpts)
if err == nil {
t.Fatal("expected error due to lack of migrate parameter")
}
unsealOpts.Migrate = true
resp, err = client.Sys().UnsealWithOptions(unsealOpts)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response")
}
}
if resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", *resp)
}
cluster.Cleanup()
cluster.Cores = nil
}
// Fourth: verify autoseal and recovery key usage
{
coreConfig.Seal = autoSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0].Core
client := cluster.Cores[0].Client
client.SetToken(rootToken)
if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
t.Fatal(err)
}
resp, err := client.Sys().SealStatus()
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response")
}
if resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", *resp)
}
keyParts := [][]byte{}
for _, key := range keys {
raw, err := base64.StdEncoding.DecodeString(key)
if err != nil {
t.Fatal(err)
}
keyParts = append(keyParts, raw)
}
recoveredKey, err := shamir.Combine(keyParts)
if err != nil {
t.Fatal(err)
}
sealAccess := core.SealAccess()
if err := sealAccess.VerifyRecoveryKey(context.Background(), recoveredKey); err != nil {
t.Fatal(err)
}
cluster.Cleanup()
cluster.Cores = nil
}
// Fifth: create an autoseal and activate migration. Verify it doesn't work
// if disabled isn't set.
{
coreConfig.Seal = autoSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0].Core
serverConf := &server.Config{
Seal: &server.Seal{
Type: "test-auto",
},
}
if err := adjustCoreForSealMigration(context.Background(), core, coreConfig, shamirSeal, serverConf); err == nil {
t.Fatal("expected error since disabled isn't set true")
}
serverConf.Seal.Disabled = true
if err := adjustCoreForSealMigration(context.Background(), core, coreConfig, shamirSeal, serverConf); err != nil {
t.Fatal(err)
}
client := cluster.Cores[0].Client
client.SetToken(rootToken)
var resp *api.SealStatusResponse
unsealOpts := &api.UnsealOpts{}
for _, key := range keys {
unsealOpts.Key = key
unsealOpts.Migrate = false
resp, err = client.Sys().UnsealWithOptions(unsealOpts)
if err == nil {
t.Fatal("expected error due to lack of migrate parameter")
}
unsealOpts.Migrate = true
resp, err = client.Sys().UnsealWithOptions(unsealOpts)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response")
}
}
if resp.Sealed {
t.Fatalf("expected unsealed state; got %#v", *resp)
}
cluster.Cleanup()
cluster.Cores = nil
}
// Sixth: verify autoseal is off and the expected key shares work
{
coreConfig.Seal = shamirSeal
cluster := vault.NewTestCluster(t, coreConfig, clusterConfig)
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0].Core
client := cluster.Cores[0].Client
client.SetToken(rootToken)
if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
t.Fatal(err)
}
resp, err := client.Sys().SealStatus()
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response")
}
if !resp.Sealed {
t.Fatalf("expected sealed state; got %#v", *resp)
}
for _, key := range keys {
resp, err = client.Sys().Unseal(key)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response")
}
}
if resp.Sealed {
t.Fatal("expected unsealed state")
}
cluster.Cleanup()
cluster.Cores = nil
}
}

View File

@ -53,7 +53,7 @@ import (
var _ cli.Command = (*ServerCommand)(nil)
var _ cli.CommandAutocomplete = (*ServerCommand)(nil)
const migrationLock = "core/migration"
const storageMigrationLock = "core/migration"
type ServerCommand struct {
*BaseCommand
@ -464,7 +464,7 @@ func (c *ServerCommand) Run(args []string) int {
}
// Prevent server startup if migration is active
if c.migrationActive(backend) {
if c.storageMigrationActive(backend) {
return 1
}
@ -899,8 +899,13 @@ CLUSTER_SYNTHESIS_COMPLETE:
Core: core,
}))
err = core.UnsealWithStoredKeys(context.Background())
if err != nil {
// Before unsealing with stored keys, setup seal migration if needed
if err := adjustCoreForSealMigration(context.Background(), core, coreConfig, seal, config); err != nil {
c.UI.Error(err.Error())
return 1
}
if err := core.UnsealWithStoredKeys(context.Background()); err != nil {
if !errwrap.ContainsType(err, new(vault.NonFatalError)) {
c.UI.Error(fmt.Sprintf("Error initializing core: %s", err))
return 1
@ -1784,13 +1789,13 @@ func (c *ServerCommand) removePidFile(pidPath string) error {
return os.Remove(pidPath)
}
// migrationActive checks and warns against in-progress storage migrations.
// storageMigrationActive checks and warns against in-progress storage migrations.
// This function will block until storage is available.
func (c *ServerCommand) migrationActive(backend physical.Backend) bool {
func (c *ServerCommand) storageMigrationActive(backend physical.Backend) bool {
first := true
for {
migrationStatus, err := CheckMigration(backend)
migrationStatus, err := CheckStorageMigration(backend)
if err == nil {
if migrationStatus != nil {
startTime := migrationStatus.Start.Format(time.RFC3339)
@ -1803,12 +1808,12 @@ func (c *ServerCommand) migrationActive(backend physical.Backend) bool {
}
if first {
first = false
c.UI.Warn("\nWARNING! Unable to read migration status.")
c.UI.Warn("\nWARNING! Unable to read storage migration status.")
// unexpected state, so stop buffering log messages
c.logGate.Flush()
}
c.logger.Warn("migration check error", "error", err.Error())
c.logger.Warn("storage migration check error", "error", err.Error())
select {
case <-time.After(2 * time.Second):
@ -1819,12 +1824,12 @@ func (c *ServerCommand) migrationActive(backend physical.Backend) bool {
return false
}
type MigrationStatus struct {
type StorageMigrationStatus struct {
Start time.Time `json:"start"`
}
func CheckMigration(b physical.Backend) (*MigrationStatus, error) {
entry, err := b.Get(context.Background(), migrationLock)
func CheckStorageMigration(b physical.Backend) (*StorageMigrationStatus, error) {
entry, err := b.Get(context.Background(), storageMigrationLock)
if err != nil {
return nil, err
@ -1834,7 +1839,7 @@ func CheckMigration(b physical.Backend) (*MigrationStatus, error) {
return nil, nil
}
var status MigrationStatus
var status StorageMigrationStatus
if err := jsonutil.DecodeJSON(entry.Value, &status); err != nil {
return nil, err
}
@ -1842,12 +1847,12 @@ func CheckMigration(b physical.Backend) (*MigrationStatus, error) {
return &status, nil
}
func SetMigration(b physical.Backend, active bool) error {
func SetStorageMigration(b physical.Backend, active bool) error {
if !active {
return b.Delete(context.Background(), migrationLock)
return b.Delete(context.Background(), storageMigrationLock)
}
status := MigrationStatus{
status := StorageMigrationStatus{
Start: time.Now(),
}
@ -1857,7 +1862,7 @@ func SetMigration(b physical.Backend, active bool) error {
}
entry := &physical.Entry{
Key: migrationLock,
Key: storageMigrationLock,
Value: enc,
}

View File

@ -135,8 +135,9 @@ func (b *Storage) GoString() string {
// Seal contains Seal configuration for the server
type Seal struct {
Type string
Config map[string]string
Type string
Disabled bool
Config map[string]string
}
func (h *Seal) GoString() string {
@ -748,9 +749,20 @@ func parseSeal(result *Config, list *ast.ObjectList, blockName string) error {
return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key))
}
var disabled bool
var err error
if v, ok := m["disabled"]; ok {
disabled, err = strconv.ParseBool(v)
if err != nil {
return multierror.Prefix(err, fmt.Sprintf("%s.%s:", blockName, key))
}
delete(m, "disabled")
}
result.Seal = &Seal{
Type: strings.ToLower(key),
Config: m,
Type: strings.ToLower(key),
Disabled: disabled,
Config: m,
}
return nil

75
command/server_util.go Normal file
View File

@ -0,0 +1,75 @@
package command
import (
"context"
"fmt"
"github.com/hashicorp/vault/command/server"
"github.com/hashicorp/vault/vault"
"github.com/pkg/errors"
)
var (
onEnterprise = false
)
func adjustCoreForSealMigration(ctx context.Context, core *vault.Core, coreConfig *vault.CoreConfig, seal vault.Seal, config *server.Config) error {
existBarrierSealConfig, existRecoverySealConfig, err := core.PhysicalSealConfigs(context.Background())
if err != nil {
return fmt.Errorf("Error checking for existing seal: %s", err)
}
var existSeal vault.Seal
var newSeal vault.Seal
if existBarrierSealConfig != nil &&
(existBarrierSealConfig.Type != seal.BarrierType() ||
config.Seal != nil && config.Seal.Disabled) {
// If the existing seal is not Shamir, we're going to Shamir, which
// means we require them setting "disabled" to true in their
// configuration as a sanity check.
if (config.Seal == nil || !config.Seal.Disabled) && existBarrierSealConfig.Type != seal.Shamir {
return errors.New(`Seal migration requires specifying "disabled" as "true" in the "seal" block of Vault's configuration file"`)
}
// Conversely, if they are going from Shamir to auto, we want to
// ensure disabled is *not* set
if existBarrierSealConfig.Type == seal.Shamir && config.Seal != nil && config.Seal.Disabled {
coreConfig.Logger.Warn(`when not migrating, Vault's config should not specify "disabled" as "true" in the "seal" block of Vault's configuration file`)
return nil
}
if existBarrierSealConfig.Type != seal.shamir && existRecoverySealConfig == nil {
return errors.New(`Recovery seal configuration not found for existing seal`)
}
switch existBarrierSealConfig.Type {
case seal.Shamir:
// The value reflected in config is what we're going to
existSeal = vault.NewDefaultSeal()
existSeal.SetCore(core)
newSeal = seal
newBarrierSealConfig := &vault.SealConfig{
Type: newSeal.BarrierType(),
SecretShares: 1,
SecretThreshold: 1,
StoredShares: 1,
}
newSeal.SetCachedBarrierConfig(newBarrierSealConfig)
newSeal.SetCachedRecoveryConfig(existBarrierSealConfig)
default:
if onEnterprise {
return errors.New("Migrating from autoseal to Shamir seal is not supported on Vault Enterprise")
}
// The disabled value reflected in config is what we're going from
existSeal = coreConfig.Seal
newSeal = vault.NewDefaultSeal()
newSeal.SetCore(core)
newSeal.SetCachedBarrierConfig(existRecoverySealConfig)
}
core.SetSealsForMigration(existSeal, newSeal)
}
return nil
}

View File

@ -105,6 +105,7 @@ func testHttpData(t *testing.T, method string, token string, addr string, body i
}
func testResponseStatus(t *testing.T, resp *http.Response, code int) {
t.Helper()
if resp.StatusCode != code {
body := new(bytes.Buffer)
io.Copy(body, resp.Body)

View File

@ -36,13 +36,13 @@ func TestSysGenerateRootAttempt_Status(t *testing.T) {
"encoded_root_token": "",
"pgp_fingerprint": "",
"nonce": "",
"otp": "",
"otp_length": json.Number("24"),
"otp_length": json.Number("26"),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
expected["otp"] = actual["otp"]
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
@ -52,15 +52,7 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
defer ln.Close()
TestServerAuth(t, addr, token)
otpBytes, err := vault.GenerateRandBytes(16)
if err != nil {
t.Fatal(err)
}
otp := base64.StdEncoding.EncodeToString(otpBytes)
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"otp": otp,
})
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", nil)
testResponseStatus(t, resp, 200)
var actual map[string]interface{}
@ -72,8 +64,7 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
"encoded_token": "",
"encoded_root_token": "",
"pgp_fingerprint": "",
"otp": "",
"otp_length": json.Number("24"),
"otp_length": json.Number("26"),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -81,8 +72,9 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
t.Fatalf("nonce was empty")
}
expected["nonce"] = actual["nonce"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
expected["otp"] = actual["otp"]
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt")
@ -97,7 +89,7 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
"encoded_root_token": "",
"pgp_fingerprint": "",
"otp": "",
"otp_length": json.Number("24"),
"otp_length": json.Number("26"),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -133,7 +125,7 @@ func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
"encoded_root_token": "",
"pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793",
"otp": "",
"otp_length": json.Number("24"),
"otp_length": json.Number("26"),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -141,8 +133,8 @@ func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
t.Fatalf("nonce was empty")
}
expected["nonce"] = actual["nonce"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
@ -152,15 +144,7 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
defer ln.Close()
TestServerAuth(t, addr, token)
otpBytes, err := vault.GenerateRandBytes(16)
if err != nil {
t.Fatal(err)
}
otp := base64.StdEncoding.EncodeToString(otpBytes)
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"otp": otp,
})
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", nil)
var actual map[string]interface{}
expected := map[string]interface{}{
@ -171,8 +155,7 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
"encoded_token": "",
"encoded_root_token": "",
"pgp_fingerprint": "",
"otp": "",
"otp_length": json.Number("24"),
"otp_length": json.Number("26"),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -180,14 +163,15 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
t.Fatalf("nonce was empty")
}
expected["nonce"] = actual["nonce"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
expected["otp"] = actual["otp"]
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt")
testResponseStatus(t, resp, 204)
resp, err = http.Get(addr + "/v1/sys/generate-root/attempt")
resp, err := http.Get(addr + "/v1/sys/generate-root/attempt")
if err != nil {
t.Fatalf("err: %s", err)
}
@ -203,7 +187,7 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
"pgp_fingerprint": "",
"nonce": "",
"otp": "",
"otp_length": json.Number("24"),
"otp_length": json.Number("26"),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -218,15 +202,8 @@ func TestSysGenerateRoot_badKey(t *testing.T) {
defer ln.Close()
TestServerAuth(t, addr, token)
otpBytes, err := vault.GenerateRandBytes(16)
if err != nil {
t.Fatal(err)
}
otp := base64.StdEncoding.EncodeToString(otpBytes)
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/update", map[string]interface{}{
"key": "0123",
"otp": otp,
})
testResponseStatus(t, resp, 400)
}
@ -237,14 +214,7 @@ func TestSysGenerateRoot_ReAttemptUpdate(t *testing.T) {
defer ln.Close()
TestServerAuth(t, addr, token)
otpBytes, err := vault.GenerateRandBytes(16)
if err != nil {
t.Fatal(err)
}
otp := base64.StdEncoding.EncodeToString(otpBytes)
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
"otp": otp,
})
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", nil)
testResponseStatus(t, resp, 200)
resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt")

View File

@ -90,12 +90,6 @@ func handleSysUnseal(core *vault.Core) http.Handler {
respondError(w, http.StatusBadRequest, err)
return
}
if !req.Reset && req.Key == "" {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be specified in request body as JSON, or 'reset' set to true"))
return
}
if req.Reset {
if !core.Sealed() {
@ -103,46 +97,68 @@ func handleSysUnseal(core *vault.Core) http.Handler {
return
}
core.ResetUnsealProcess()
} else {
// Decode the key, which is base64 or hex encoded
min, max := core.BarrierKeyLength()
key, err := hex.DecodeString(req.Key)
// We check min and max here to ensure that a string that is base64
// encoded but also valid hex will not be valid and we instead base64
// decode it
if err != nil || len(key) < min || len(key) > max {
key, err = base64.StdEncoding.DecodeString(req.Key)
if err != nil {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be a valid hex or base64 string"))
return
}
}
handleSysSealStatusRaw(core, w, r)
return
}
// Attempt the unseal
ctx := context.Background()
if core.SealAccess().RecoveryKeySupported() {
_, err = core.UnsealWithRecoveryKeys(ctx, key)
} else {
_, err = core.Unseal(key)
}
isInSealMigration := core.IsInSealMigration()
if !req.Migrate && isInSealMigration {
respondError(
w, http.StatusBadRequest,
errors.New("'migrate' parameter must be set true in JSON body when in seal migration mode"))
return
}
if req.Migrate && !isInSealMigration {
respondError(
w, http.StatusBadRequest,
errors.New("'migrate' parameter set true in JSON body when not in seal migration mode"))
return
}
if req.Key == "" {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be specified in request body as JSON, or 'reset' set to true"))
return
}
// Decode the key, which is base64 or hex encoded
min, max := core.BarrierKeyLength()
key, err := hex.DecodeString(req.Key)
// We check min and max here to ensure that a string that is base64
// encoded but also valid hex will not be valid and we instead base64
// decode it
if err != nil || len(key) < min || len(key) > max {
key, err = base64.StdEncoding.DecodeString(req.Key)
if err != nil {
switch {
case errwrap.ContainsType(err, new(vault.ErrInvalidKey)):
case errwrap.Contains(err, vault.ErrBarrierInvalidKey.Error()):
case errwrap.Contains(err, vault.ErrBarrierNotInit.Error()):
case errwrap.Contains(err, vault.ErrBarrierSealed.Error()):
case errwrap.Contains(err, consts.ErrStandby.Error()):
default:
respondError(w, http.StatusInternalServerError, err)
return
}
respondError(w, http.StatusBadRequest, err)
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be a valid hex or base64 string"))
return
}
}
// Attempt the unseal
if core.SealAccess().RecoveryKeySupported() {
_, err = core.UnsealWithRecoveryKeys(key)
} else {
_, err = core.Unseal(key)
}
if err != nil {
switch {
case errwrap.ContainsType(err, new(vault.ErrInvalidKey)):
case errwrap.Contains(err, vault.ErrBarrierInvalidKey.Error()):
case errwrap.Contains(err, vault.ErrBarrierNotInit.Error()):
case errwrap.Contains(err, vault.ErrBarrierSealed.Error()):
case errwrap.Contains(err, consts.ErrStandby.Error()):
default:
respondError(w, http.StatusInternalServerError, err)
return
}
respondError(w, http.StatusBadRequest, err)
return
}
// Return the seal status
handleSysSealStatusRaw(core, w, r)
})
@ -213,6 +229,7 @@ func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Req
Progress: progress,
Nonce: nonce,
Version: version.GetVersion().VersionNumber(),
Migration: core.IsInSealMigration(),
ClusterName: clusterName,
ClusterID: clusterID,
RecoverySeal: core.SealAccess().RecoveryKeySupported(),
@ -228,12 +245,17 @@ type SealStatusResponse struct {
Progress int `json:"progress"`
Nonce string `json:"nonce"`
Version string `json:"version"`
Migration bool `json:"migration"`
ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"`
RecoverySeal bool `json:"recovery_seal"`
}
// 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
type UnsealRequest struct {
Key string
Reset bool
Key string
Reset bool
Migrate bool
}

View File

@ -5,10 +5,10 @@ import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"strconv"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
@ -35,6 +35,7 @@ func TestSysSealStatus(t *testing.T) {
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"migration": false,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -52,8 +53,8 @@ func TestSysSealStatus(t *testing.T) {
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: expected: %#v\nactual: %#v", expected, actual)
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
@ -118,6 +119,7 @@ func TestSysUnseal(t *testing.T) {
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"migration": false,
}
if i == len(keys)-1 {
expected["sealed"] = false
@ -144,8 +146,8 @@ func TestSysUnseal(t *testing.T) {
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: expected: \n%#v\nactual: \n%#v", expected, actual)
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
}
@ -198,6 +200,7 @@ func TestSysUnseal_Reset(t *testing.T) {
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"migration": false,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -219,8 +222,8 @@ func TestSysUnseal_Reset(t *testing.T) {
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected:\n%#v\nactual:\n%#v\n", expected, actual)
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}
@ -237,6 +240,7 @@ func TestSysUnseal_Reset(t *testing.T) {
"type": "shamir",
"recovery_seal": false,
"initialized": true,
"migration": false,
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
@ -255,8 +259,8 @@ func TestSysUnseal_Reset(t *testing.T) {
} else {
expected["cluster_id"] = actual["cluster_id"]
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected:\n%#v\nactual:\n%#v\n", expected, actual)
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal(diff)
}
}

View File

@ -26,6 +26,7 @@ import (
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/helper/logging"
"github.com/hashicorp/vault/helper/mlock"
"github.com/hashicorp/vault/helper/namespace"
@ -153,9 +154,13 @@ type Core struct {
// physical backend is the un-trusted backend with durable data
physical physical.Backend
// Our Seal, for seal configuration information
// seal is our seal, for seal configuration information
seal Seal
// migrationSeal is the seal to use during a migration operation. It is the
// seal we're migrating *from*.
migrationSeal Seal
// barrier is the security barrier wrapping the physical backend
barrier SecurityBarrier
@ -458,6 +463,38 @@ type CoreConfig struct {
AllLoggers []log.Logger
}
func (c *CoreConfig) Clone() *CoreConfig {
return &CoreConfig{
DevToken: c.DevToken,
LogicalBackends: c.LogicalBackends,
CredentialBackends: c.CredentialBackends,
AuditBackends: c.AuditBackends,
Physical: c.Physical,
HAPhysical: c.HAPhysical,
Seal: c.Seal,
Logger: c.Logger,
DisableCache: c.DisableCache,
DisableMlock: c.DisableMlock,
CacheSize: c.CacheSize,
RedirectAddr: c.RedirectAddr,
ClusterAddr: c.ClusterAddr,
DefaultLeaseTTL: c.DefaultLeaseTTL,
MaxLeaseTTL: c.MaxLeaseTTL,
ClusterName: c.ClusterName,
ClusterCipherSuites: c.ClusterCipherSuites,
EnableUI: c.EnableUI,
EnableRaw: c.EnableRaw,
PluginDirectory: c.PluginDirectory,
DisableSealWrap: c.DisableSealWrap,
ReloadFuncs: c.ReloadFuncs,
ReloadFuncsLock: c.ReloadFuncsLock,
LicensingConfig: c.LicensingConfig,
DevLicenseDuration: c.DevLicenseDuration,
DisablePerformanceStandby: c.DisablePerformanceStandby,
AllLoggers: c.AllLoggers,
}
}
// NewCore is used to construct a new core
func NewCore(conf *CoreConfig) (*Core, error) {
if conf.HAPhysical != nil && conf.HAPhysical.HAEnabled() {
@ -712,6 +749,14 @@ func (c *Core) ResetUnsealProcess() {
// this method is done with it. If you want to keep the key around, a copy
// should be made.
func (c *Core) Unseal(key []byte) (bool, error) {
return c.unseal(key, false)
}
func (c *Core) UnsealWithRecoveryKeys(key []byte) (bool, error) {
return c.unseal(key, true)
}
func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
defer metrics.MeasureSince([]string{"core", "unseal"}, time.Now())
c.stateLock.Lock()
@ -739,60 +784,17 @@ func (c *Core) Unseal(key []byte) (bool, error) {
return false, &ErrInvalidKey{fmt.Sprintf("key is longer than maximum %d bytes", max)}
}
// Get the barrier seal configuration
config, err := c.seal.BarrierConfig(ctx)
if err != nil {
return false, err
}
// Check if already unsealed
if !c.Sealed() {
return true, nil
}
masterKey, err := c.unsealPart(ctx, config, key, false)
if err != nil {
return false, err
}
if masterKey != nil {
return c.unsealInternal(ctx, masterKey)
sealToUse := c.seal
if c.migrationSeal != nil {
sealToUse = c.migrationSeal
}
return false, nil
}
// UnsealWithRecoveryKeys is used to provide one of the recovery key shares to
// unseal the Vault.
func (c *Core) UnsealWithRecoveryKeys(ctx context.Context, key []byte) (bool, error) {
defer metrics.MeasureSince([]string{"core", "unseal_with_recovery_keys"}, time.Now())
c.stateLock.Lock()
defer c.stateLock.Unlock()
// Explicitly check for init status
init, err := c.Initialized(ctx)
if err != nil {
return false, err
}
if !init {
return false, ErrNotInit
}
var config *SealConfig
// If recovery keys are supported then use recovery seal config to unseal
if c.seal.RecoveryKeySupported() {
config, err = c.seal.RecoveryConfig(ctx)
if err != nil {
return false, err
}
}
// Check if already unsealed
if !c.Sealed() {
return true, nil
}
masterKey, err := c.unsealPart(ctx, config, key, true)
masterKey, err := c.unsealPart(ctx, sealToUse, key, useRecoveryKeys)
if err != nil {
return false, err
}
@ -805,7 +807,7 @@ func (c *Core) UnsealWithRecoveryKeys(ctx context.Context, key []byte) (bool, er
// unsealPart takes in a key share, and returns the master key if the threshold
// is met. If recovery keys are supported, recovery key shares may be provided.
func (c *Core) unsealPart(ctx context.Context, config *SealConfig, key []byte, useRecoveryKeys bool) ([]byte, error) {
func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecoveryKeys bool) ([]byte, error) {
// Check if we already have this piece
if c.unlockInfo != nil {
for _, existing := range c.unlockInfo.Parts {
@ -826,6 +828,17 @@ func (c *Core) unsealPart(ctx context.Context, config *SealConfig, key []byte, u
// Store this key
c.unlockInfo.Parts = append(c.unlockInfo.Parts, key)
var config *SealConfig
var err error
if seal.RecoveryKeySupported() && (useRecoveryKeys || c.migrationSeal != nil) {
config, err = seal.RecoveryConfig(ctx)
} else {
config, err = seal.BarrierConfig(ctx)
}
if err != nil {
return nil, err
}
// Check if we don't have enough keys to unlock, proceed through the rest of
// the call only if we have met the threshold
if len(c.unlockInfo.Parts) < config.SecretThreshold {
@ -846,7 +859,8 @@ func (c *Core) unsealPart(ctx context.Context, config *SealConfig, key []byte, u
// Recover the split key. recoveredKey is the shamir combined
// key, or the single provided key if the threshold is 1.
var recoveredKey []byte
var err error
var masterKey []byte
var recoveryKey []byte
if config.SecretThreshold == 1 {
recoveredKey = make([]byte, len(c.unlockInfo.Parts[0]))
copy(recoveredKey, c.unlockInfo.Parts[0])
@ -857,39 +871,114 @@ func (c *Core) unsealPart(ctx context.Context, config *SealConfig, key []byte, u
}
}
if c.seal.RecoveryKeySupported() && useRecoveryKeys {
if seal.RecoveryKeySupported() && (useRecoveryKeys || c.migrationSeal != nil) {
// Verify recovery key
if err := c.seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil {
if err := seal.VerifyRecoveryKey(ctx, recoveredKey); err != nil {
return nil, err
}
recoveryKey = recoveredKey
// Get stored keys and shamir combine into single master key. Unsealing with
// recovery keys currently does not support: 1) mixed stored and non-stored
// keys setup, nor 2) seals that support recovery keys but not stored keys.
// If insufficient shares are provided, shamir.Combine will error, and if
// no stored keys are found it will return masterKey as nil.
var masterKey []byte
if c.seal.StoredKeysSupported() {
masterKeyShares, err := c.seal.GetStoredKeys(ctx)
if seal.StoredKeysSupported() {
masterKeyShares, err := seal.GetStoredKeys(ctx)
if err != nil {
return nil, errwrap.Wrapf("unable to retrieve stored keys: {{err}}", err)
}
if len(masterKeyShares) == 1 {
return masterKeyShares[0], nil
}
masterKey, err = shamir.Combine(masterKeyShares)
if err != nil {
return nil, errwrap.Wrapf("failed to compute master key: {{err}}", err)
masterKey = masterKeyShares[0]
} else {
masterKey, err = shamir.Combine(masterKeyShares)
if err != nil {
return nil, errwrap.Wrapf("failed to compute master key: {{err}}", err)
}
}
}
return masterKey, nil
} else {
masterKey = recoveredKey
}
// If this is not a recovery key-supported seal, then the recovered key is
// the master key to be returned.
return recoveredKey, nil
// If we have a migration seal, now's the time!
if c.migrationSeal != nil {
// Unseal the barrier so we can rekey
if err := c.barrier.Unseal(ctx, masterKey); err != nil {
return nil, errwrap.Wrapf("error unsealing barrier with constructed master key: {{err}}", err)
}
defer c.barrier.Seal()
// The seal used in this function will have been the migration seal,
// and c.seal will be the opposite type, so there are two
// possibilities: Shamir to auto, and auto to Shamir.
if !seal.RecoveryKeySupported() {
// The new seal will have recovery keys; we set it to the existing
// master key, so barrier key shares -> recovery key shares
if err := c.seal.SetRecoveryKey(ctx, masterKey); err != nil {
return nil, errwrap.Wrapf("error setting new recovery key information: {{err}}", err)
}
// Generate a new master key
newMasterKey, err := c.barrier.GenerateKey()
if err != nil {
return nil, errwrap.Wrapf("error generating new master key: {{err}}", err)
}
// Rekey the barrier
if err := c.barrier.Rekey(ctx, newMasterKey); err != nil {
return nil, errwrap.Wrapf("error rekeying barrier during migration: {{err}}", err)
}
// Store the new master key
if err := c.seal.SetStoredKeys(ctx, [][]byte{newMasterKey}); err != nil {
return nil, errwrap.Wrapf("error storing new master key: {[err}}", err)
}
// Return the new key so it can be used to unlock the barrier
masterKey = newMasterKey
} else {
// In this case we have to ensure that the recovery information was
// set properly.
if recoveryKey == nil {
return nil, errors.New("did not get expected recovery information to set new seal during migration")
}
// Auto to Shamir. We have recovery keys; we're going to use them
// as the new barrier key
if err := c.barrier.Rekey(ctx, recoveryKey); err != nil {
return nil, errwrap.Wrapf("error rekeying barrier during migration: {{err}}", err)
}
masterKey = recoveryKey
}
// At this point we've swapped things around and need to ensure we
// don't migrate again
c.migrationSeal = nil
// Ensure we populate the new values
bc, err := c.seal.BarrierConfig(ctx)
if err != nil {
return nil, errwrap.Wrapf("error fetching barrier config after migration: {{err}}", err)
}
if err := c.seal.SetBarrierConfig(ctx, bc); err != nil {
return nil, errwrap.Wrapf("error storing barrier config after migration: {{err}}", err)
}
if c.seal.RecoveryKeySupported() {
rc, err := c.seal.RecoveryConfig(ctx)
if err != nil {
return nil, errwrap.Wrapf("error fetching recovery config after migration: {{err}}", err)
}
if err := c.seal.SetRecoveryConfig(ctx, rc); err != nil {
return nil, errwrap.Wrapf("error storing recovery config after migration: {{err}}", err)
}
}
}
return masterKey, nil
}
// unsealInternal takes in the master key and attempts to unseal the barrier.
@ -1525,6 +1614,50 @@ func lastRemoteWALImpl(c *Core) uint64 {
return 0
}
func (c *Core) PhysicalSealConfigs(ctx context.Context) (*SealConfig, *SealConfig, error) {
pe, err := c.physical.Get(ctx, barrierSealConfigPath)
if err != nil {
return nil, nil, errwrap.Wrapf("failed to fetch barrier seal configuration at migration check time: {{err}}", err)
}
if pe == nil {
return nil, nil, nil
}
barrierConf := new(SealConfig)
if err := jsonutil.DecodeJSON(pe.Value, barrierConf); err != nil {
return nil, nil, errwrap.Wrapf("failed to decode barrier seal configuration at migration check time: {{err}}", err)
}
var recoveryConf *SealConfig
pe, err = c.physical.Get(ctx, recoverySealConfigPlaintextPath)
if err != nil {
return nil, nil, errwrap.Wrapf("failed to fetch seal configuration at migration check time: {{err}}", err)
}
if pe != nil {
recoveryConf = &SealConfig{}
if err := jsonutil.DecodeJSON(pe.Value, recoveryConf); err != nil {
return nil, nil, errwrap.Wrapf("failed to decode seal configuration at migration check time: {{err}}", err)
}
}
return barrierConf, recoveryConf, nil
}
func (c *Core) SetSealsForMigration(migrationSeal, newSeal Seal) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.migrationSeal = migrationSeal
c.seal = newSeal
c.logger.Warn("entering seal migration mode; Vault will not automatically unseal even if using an autoseal")
}
func (c *Core) IsInSealMigration() bool {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
return c.migrationSeal != nil
}
func (c *Core) BarrierEncryptorAccess() *BarrierEncryptorAccess {
return NewBarrierEncryptorAccess(c.barrier)
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/go-test/deep"
"github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-uuid"
@ -722,11 +723,12 @@ func TestCore_HandleLogin_Token(t *testing.T) {
TTL: time.Hour * 24,
CreationTime: te.CreationTime,
NamespaceID: namespace.RootNamespaceID,
CubbyholeID: te.CubbyholeID,
Type: logical.TokenTypeService,
}
if !reflect.DeepEqual(te, expect) {
t.Fatalf("Bad: %#v expect: %#v", te, expect)
if diff := deep.Equal(te, expect); diff != nil {
t.Fatal(diff)
}
// Check that we have a lease with default duration
@ -1025,10 +1027,11 @@ func TestCore_HandleRequest_CreateToken_Lease(t *testing.T) {
CreationTime: te.CreationTime,
TTL: time.Hour * 24 * 32,
NamespaceID: namespace.RootNamespaceID,
CubbyholeID: te.CubbyholeID,
Type: logical.TokenTypeService,
}
if !reflect.DeepEqual(te, expect) {
t.Fatalf("Bad: %#v expect: %#v", te, expect)
if diff := deep.Equal(te, expect); diff != nil {
t.Fatal(diff)
}
// Check that we have a lease with default duration
@ -1072,10 +1075,11 @@ func TestCore_HandleRequest_CreateToken_NoDefaultPolicy(t *testing.T) {
CreationTime: te.CreationTime,
TTL: time.Hour * 24 * 32,
NamespaceID: namespace.RootNamespaceID,
CubbyholeID: te.CubbyholeID,
Type: logical.TokenTypeService,
}
if !reflect.DeepEqual(te, expect) {
t.Fatalf("Bad: %#v expect: %#v", te, expect)
if diff := deep.Equal(te, expect); diff != nil {
t.Fatal(diff)
}
}

View File

@ -276,6 +276,11 @@ func (c *Core) UnsealWithStoredKeys(ctx context.Context) error {
return nil
}
// Disallow auto-unsealing when migrating
if c.IsInSealMigration() {
return nil
}
sealed := c.Sealed()
if !sealed {
return nil

View File

@ -62,11 +62,13 @@ type Seal interface {
BarrierType() string
BarrierConfig(context.Context) (*SealConfig, error)
SetBarrierConfig(context.Context, *SealConfig) error
SetCachedBarrierConfig(*SealConfig)
RecoveryKeySupported() bool
RecoveryType() string
RecoveryConfig(context.Context) (*SealConfig, error)
SetRecoveryConfig(context.Context, *SealConfig) error
SetCachedRecoveryConfig(*SealConfig)
SetRecoveryKey(context.Context, []byte) error
VerifyRecoveryKey(context.Context, []byte) error
}
@ -160,8 +162,8 @@ func (d *defaultSeal) BarrierConfig(ctx context.Context) (*SealConfig, error) {
conf.Type = d.BarrierType()
case d.BarrierType():
default:
d.core.logger.Error("barrier seal type does not match loaded type", "barrier_seal_type", conf.Type, "loaded_seal_type", d.BarrierType())
return nil, fmt.Errorf("barrier seal type of %q does not match loaded type of %q", conf.Type, d.BarrierType())
d.core.logger.Error("barrier seal type does not match expected type", "barrier_seal_type", conf.Type, "loaded_seal_type", d.BarrierType())
return nil, fmt.Errorf("barrier seal type of %q does not match expected type of %q", conf.Type, d.BarrierType())
}
// Check for a valid seal configuration
@ -210,6 +212,10 @@ func (d *defaultSeal) SetBarrierConfig(ctx context.Context, config *SealConfig)
return nil
}
func (d *defaultSeal) SetCachedBarrierConfig(config *SealConfig) {
d.config.Store(config)
}
func (d *defaultSeal) RecoveryType() string {
if d.PretendToAllowRecoveryKeys {
return RecoveryTypeShamir
@ -234,6 +240,9 @@ func (d *defaultSeal) SetRecoveryConfig(ctx context.Context, config *SealConfig)
return fmt.Errorf("recovery not supported")
}
func (d *defaultSeal) SetCachedRecoveryConfig(config *SealConfig) {
}
func (d *defaultSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error {
if d.PretendToAllowRecoveryKeys {
if subtle.ConstantTimeCompare(key, d.PretendRecoveryKey) == 1 {

View File

@ -226,6 +226,10 @@ func (d *autoSeal) SetBarrierConfig(ctx context.Context, conf *SealConfig) error
return nil
}
func (d *autoSeal) SetCachedBarrierConfig(config *SealConfig) {
d.barrierConfig.Store(config)
}
func (d *autoSeal) RecoveryType() string {
return RecoveryTypeShamir
}
@ -340,6 +344,10 @@ func (d *autoSeal) SetRecoveryConfig(ctx context.Context, conf *SealConfig) erro
return nil
}
func (d *autoSeal) SetCachedRecoveryConfig(config *SealConfig) {
d.recoveryConfig.Store(config)
}
func (d *autoSeal) VerifyRecoveryKey(ctx context.Context, key []byte) error {
if key == nil {
return fmt.Errorf("recovery key to verify is nil")

View File

@ -56,7 +56,7 @@ func performTestSealUnwrapper(t *testing.T, phys physical.Backend, logger log.Lo
// Save the original for comparison later
origBytes := make([]byte, len(entry.Value))
copy(origBytes, entry.Value)
se := &physical.SealWrapEntry{
se := &physical.EncryptedBlobInfo{
Ciphertext: entry.Value,
}
seb, err := proto.Marshal(se)

View File

@ -255,7 +255,7 @@ func TestCoreUnseal(core *Core, key []byte) (bool, error) {
}
func TestCoreUnsealWithRecoveryKeys(core *Core, key []byte) (bool, error) {
return core.UnsealWithRecoveryKeys(context.Background(), key)
return core.UnsealWithRecoveryKeys(key)
}
// TestCoreUnsealed returns a pure in-memory core that is already
@ -884,6 +884,7 @@ type TestListener struct {
type TestClusterCore struct {
*Core
CoreConfig *CoreConfig
Client *api.Client
Handler http.Handler
Listeners []*TestListener
@ -1259,28 +1260,31 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
}
cores := []*Core{}
coreConfigs := []*CoreConfig{}
for i := 0; i < numCores; i++ {
coreConfig.RedirectAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[i][0].Address.Port)
if coreConfig.ClusterAddr != "" {
coreConfig.ClusterAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[i][0].Address.Port+105)
localConfig := coreConfig.Clone()
localConfig.RedirectAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[i][0].Address.Port)
if localConfig.ClusterAddr != "" {
localConfig.ClusterAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[i][0].Address.Port+105)
}
// if opts.SealFunc is provided, use that to generate a seal for the config instead
if opts != nil && opts.SealFunc != nil {
coreConfig.Seal = opts.SealFunc()
localConfig.Seal = opts.SealFunc()
}
if opts != nil && opts.Logger != nil {
coreConfig.Logger = opts.Logger.Named(fmt.Sprintf("core%d", i))
localConfig.Logger = opts.Logger.Named(fmt.Sprintf("core%d", i))
}
coreConfig.LicensingConfig = testGetLicensingConfig(pubKey)
localConfig.LicensingConfig = testGetLicensingConfig(pubKey)
c, err := NewCore(coreConfig)
c, err := NewCore(localConfig)
if err != nil {
t.Fatalf("err: %v", err)
}
cores = append(cores, c)
coreConfigs = append(coreConfigs, localConfig)
if opts != nil && opts.HandlerFunc != nil {
handlers[i] = opts.HandlerFunc(&HandlerProperties{
Core: c,
@ -1288,6 +1292,12 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
})
servers[i].Handler = handlers[i]
}
// Set this in case the Seal was manually set before the core was
// created
if localConfig.Seal != nil {
localConfig.Seal.SetCore(c)
}
}
//
@ -1304,8 +1314,8 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
return ret
}
if numCores > 1 {
for i := 1; i < numCores; i++ {
for i := 0; i < numCores; i++ {
if coreConfigs[i].ClusterAddr != "" {
cores[i].SetClusterListenerAddrs(clusterAddrGen(listeners[i]))
cores[i].SetClusterHandler(handlers[i])
}
@ -1444,6 +1454,7 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
for i := 0; i < numCores; i++ {
tcc := &TestClusterCore{
Core: cores[i],
CoreConfig: coreConfigs[i],
ServerKey: certInfoSlice[i].key,
ServerKeyPEM: certInfoSlice[i].keyPEM,
ServerCert: certInfoSlice[i].cert,