SealInterface

This commit is contained in:
Jeff Mitchell 2016-04-04 10:44:22 -04:00
parent b47d6dc64f
commit afae46feb7
23 changed files with 1696 additions and 755 deletions

View File

@ -31,9 +31,13 @@ func (c *Sys) Init(opts *InitRequest) (*InitResponse, error) {
}
type InitRequest struct {
SecretShares int `json:"secret_shares"`
SecretThreshold int `json:"secret_threshold"`
PGPKeys []string `json:"pgp_keys"`
SecretShares int `json:"secret_shares"`
SecretThreshold int `json:"secret_threshold"`
StoredShares int `json:"stored_shares"`
PGPKeys []string `json:"pgp_keys"`
RecoveryShares int `json:"recovery_shares"`
RecoveryThreshold int `json:"recovery_threshold"`
RecoveryPGPKeys []string `json:"recovery_pgp_keys"`
}
type InitStatusResponse struct {
@ -41,6 +45,7 @@ type InitStatusResponse struct {
}
type InitResponse struct {
Keys []string
RootToken string `json:"root_token"`
Keys []string
RecoveryKeys []string `json:"recovery_keys"`
RootToken string `json:"root_token"`
}

View File

@ -13,6 +13,19 @@ func (c *Sys) RekeyStatus() (*RekeyStatusResponse, error) {
return &result, err
}
func (c *Sys) RekeyRecoveryKeyStatus() (*RekeyStatusResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/rekey-recovery-key/init")
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result RekeyStatusResponse
err = resp.DecodeJSON(&result)
return &result, err
}
func (c *Sys) RekeyInit(config *RekeyInitRequest) (*RekeyStatusResponse, error) {
r := c.c.NewRequest("PUT", "/v1/sys/rekey/init")
if err := r.SetJSONBody(config); err != nil {
@ -30,6 +43,23 @@ func (c *Sys) RekeyInit(config *RekeyInitRequest) (*RekeyStatusResponse, error)
return &result, err
}
func (c *Sys) RekeyRecoveryKeyInit(config *RekeyInitRequest) (*RekeyStatusResponse, error) {
r := c.c.NewRequest("PUT", "/v1/sys/rekey-recovery-key/init")
if err := r.SetJSONBody(config); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result RekeyStatusResponse
err = resp.DecodeJSON(&result)
return &result, err
}
func (c *Sys) RekeyCancel() error {
r := c.c.NewRequest("DELETE", "/v1/sys/rekey/init")
resp, err := c.c.RawRequest(r)
@ -39,6 +69,15 @@ func (c *Sys) RekeyCancel() error {
return err
}
func (c *Sys) RekeyRecoveryKeyCancel() error {
r := c.c.NewRequest("DELETE", "/v1/sys/rekey-recovery-key/init")
resp, err := c.c.RawRequest(r)
if err == nil {
defer resp.Body.Close()
}
return err
}
func (c *Sys) RekeyUpdate(shard, nonce string) (*RekeyUpdateResponse, error) {
body := map[string]interface{}{
"key": shard,
@ -61,6 +100,28 @@ func (c *Sys) RekeyUpdate(shard, nonce string) (*RekeyUpdateResponse, error) {
return &result, err
}
func (c *Sys) RekeyRecoveryKeyUpdate(shard, nonce string) (*RekeyUpdateResponse, error) {
body := map[string]interface{}{
"key": shard,
"nonce": nonce,
}
r := c.c.NewRequest("PUT", "/v1/sys/rekey-recovery-key/update")
if err := r.SetJSONBody(body); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result RekeyUpdateResponse
err = resp.DecodeJSON(&result)
return &result, err
}
func (c *Sys) RekeyRetrieveBackup() (*RekeyRetrieveResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/rekey/backup")
resp, err := c.c.RawRequest(r)
@ -74,6 +135,19 @@ func (c *Sys) RekeyRetrieveBackup() (*RekeyRetrieveResponse, error) {
return &result, err
}
func (c *Sys) RekeyRetrieveRecoveryBackup() (*RekeyRetrieveResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/rekey/recovery-backup")
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result RekeyRetrieveResponse
err = resp.DecodeJSON(&result)
return &result, err
}
func (c *Sys) RekeyDeleteBackup() error {
r := c.c.NewRequest("DELETE", "/v1/sys/rekey/backup")
resp, err := c.c.RawRequest(r)
@ -84,6 +158,16 @@ func (c *Sys) RekeyDeleteBackup() error {
return err
}
func (c *Sys) RekeyDeleteRecoveryBackup() error {
r := c.c.NewRequest("DELETE", "/v1/sys/rekey/recovery-backup")
resp, err := c.c.RawRequest(r)
if err == nil {
defer resp.Body.Close()
}
return err
}
type RekeyInitRequest struct {
SecretShares int `json:"secret_shares"`
SecretThreshold int `json:"secret_threshold"`

View File

@ -15,15 +15,19 @@ type InitCommand struct {
}
func (c *InitCommand) Run(args []string) int {
var threshold, shares int
var pgpKeys pgpkeys.PubKeyFilesFlag
var threshold, shares, storedShares, recoveryThreshold, recoveryShares int
var pgpKeys, recoveryPgpKeys pgpkeys.PubKeyFilesFlag
var check bool
flags := c.Meta.FlagSet("init", meta.FlagSetDefault)
flags.Usage = func() { c.Ui.Error(c.Help()) }
flags.IntVar(&shares, "key-shares", 5, "")
flags.IntVar(&threshold, "key-threshold", 3, "")
flags.BoolVar(&check, "check", false, "")
flags.IntVar(&storedShares, "stored-shares", 0, "")
flags.Var(&pgpKeys, "pgp-keys", "")
flags.IntVar(&recoveryShares, "recovery-key-shares", 0, "")
flags.IntVar(&recoveryThreshold, "recovery-key-threshold", 0, "")
flags.Var(&recoveryPgpKeys, "recovery-key-pgp-keys", "")
flags.BoolVar(&check, "check", false, "")
if err := flags.Parse(args); err != nil {
return 1
}
@ -40,9 +44,13 @@ func (c *InitCommand) Run(args []string) int {
}
resp, err := client.Sys().Init(&api.InitRequest{
SecretShares: shares,
SecretThreshold: threshold,
PGPKeys: pgpKeys,
SecretShares: shares,
SecretThreshold: threshold,
StoredShares: storedShares,
PGPKeys: pgpKeys,
RecoveryShares: recoveryShares,
RecoveryThreshold: recoveryThreshold,
RecoveryPGPKeys: recoveryPgpKeys,
})
if err != nil {
c.Ui.Error(fmt.Sprintf(
@ -51,24 +59,43 @@ func (c *InitCommand) Run(args []string) int {
}
for i, key := range resp.Keys {
c.Ui.Output(fmt.Sprintf("Key %d: %s", i+1, key))
c.Ui.Output(fmt.Sprintf("Unseal Key %d: %s", i+1, key))
}
for i, key := range resp.RecoveryKeys {
c.Ui.Output(fmt.Sprintf("Recovery Key %d: %s", i+1, key))
}
c.Ui.Output(fmt.Sprintf("Initial Root Token: %s", resp.RootToken))
c.Ui.Output(fmt.Sprintf(
"\n"+
"Vault initialized with %d keys and a key threshold of %d. Please\n"+
"securely distribute the above keys. When the Vault is re-sealed,\n"+
"restarted, or stopped, you must provide at least %d of these keys\n"+
"to unseal it again.\n\n"+
"Vault does not store the master key. Without at least %d keys,\n"+
"your Vault will remain permanently sealed.",
shares,
threshold,
threshold,
threshold,
))
if storedShares < 1 {
c.Ui.Output(fmt.Sprintf(
"\n"+
"Vault initialized with %d keys and a key threshold of %d. Please\n"+
"securely distribute the above keys. When the Vault is re-sealed,\n"+
"restarted, or stopped, you must provide at least %d of these keys\n"+
"to unseal it again.\n\n"+
"Vault does not store the master key. Without at least %d keys,\n"+
"your Vault will remain permanently sealed.",
shares,
threshold,
threshold,
threshold,
))
} else {
c.Ui.Output(
"\n" +
"Vault initialized successfully.",
)
}
if len(resp.RecoveryKeys) > 0 {
c.Ui.Output(fmt.Sprintf(
"\n"+
"Recovery key initialized with %d keys and a key threshold of %d. Please\n"+
"securely distribute the above keys.",
shares,
threshold,
))
}
return 0
}
@ -109,27 +136,39 @@ General Options:
` + meta.GeneralOptionsUsage() + `
Init Options:
-check Don't actually initialize, just check if Vault is
already initialized. A return code of 0 means Vault
is initialized; a return code of 2 means Vault is not
initialized; a return code of 1 means an error was
encountered.
-check Don't actually initialize, just check if Vault is
already initialized. A return code of 0 means Vault
is initialized; a return code of 2 means Vault is not
initialized; a return code of 1 means an error was
encountered.
-key-shares=5 The number of key shares to split the master key
into.
-key-shares=5 The number of key shares to split the master key
into.
-key-threshold=3 The number of key shares required to reconstruct
the master key.
-key-threshold=3 The number of key shares required to reconstruct
the master key.
-pgp-keys If provided, must be a comma-separated list of
files on disk containing binary- or base64-format
public PGP keys, or Keybase usernames specified as
"keybase:<username>". The number of given entries
must match 'key-shares'. The output unseal keys will
be encrypted and hex-encoded, in order, with the
given public keys. If you want to use them with the
'vault unseal' command, you will need to hex decode
and decrypt; this will be the plaintext unseal key.
-stored-shares=0 The number of unseal keys to store. This is not
normally available.
-pgp-keys If provided, must be a comma-separated list of
files on disk containing binary- or base64-format
public PGP keys, or Keybase usernames specified as
"keybase:<username>". The number of given entries
must match 'key-shares'. The output unseal keys will
be encrypted and hex-encoded, in order, with the
given public keys. If you want to use them with the
'vault unseal' command, you will need to hex decode
and decrypt; this will be the plaintext unseal key.
-recovery-key-shares=0 The number of key shares to split the recovery key
into. This is not normally available.
-recovery-key-threshold=0 The number of key shares required to reconstruct
the recovery key. This is not normally available.
-recovery-key-pgp-keys If provided, behaves like "pgp-keys" but for the
recovery key shares. This is not normally available.
`
return strings.TrimSpace(helpText)
}

View File

@ -46,7 +46,7 @@ func TestInit(t *testing.T) {
t.Fatal("should be initialized")
}
sealConf, err := core.SealConfig()
sealConf, err := core.SealAccess().BarrierConfig()
if err != nil {
t.Fatalf("err: %s", err)
}
@ -55,7 +55,7 @@ func TestInit(t *testing.T) {
SecretThreshold: 3,
}
if !reflect.DeepEqual(expected, sealConf) {
t.Fatalf("bad: %#v", sealConf)
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, sealConf)
}
}
@ -135,7 +135,7 @@ func TestInit_custom(t *testing.T) {
t.Fatal("should be initialized")
}
sealConf, err := core.SealConfig()
sealConf, err := core.SealAccess().BarrierConfig()
if err != nil {
t.Fatalf("err: %s", err)
}
@ -144,7 +144,7 @@ func TestInit_custom(t *testing.T) {
SecretThreshold: 3,
}
if !reflect.DeepEqual(expected, sealConf) {
t.Fatalf("bad: %#v", sealConf)
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, sealConf)
}
}
@ -207,7 +207,7 @@ func TestInit_PGP(t *testing.T) {
t.Fatal("should be initialized")
}
sealConf, err := core.SealConfig()
sealConf, err := core.SealAccess().BarrierConfig()
if err != nil {
t.Fatalf("err: %s", err)
}
@ -227,7 +227,7 @@ func TestInit_PGP(t *testing.T) {
PGPKeys: pgpKeys,
}
if !reflect.DeepEqual(expected, sealConf) {
t.Fatalf("bad:\nexpected: %#v\ngot: %#v", expected, sealConf)
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, sealConf)
}
re, err := regexp.Compile("\\s+Initial Root Token:\\s+(.*)")

View File

@ -22,10 +22,13 @@ type RekeyCommand struct {
// The nonce for the rekey request to send along
Nonce string
// Whether to use the recovery key instead of barrier key, if available
RecoveryKey bool
}
func (c *RekeyCommand) Run(args []string) int {
var init, cancel, status, delete, retrieve, backup bool
var init, cancel, status, delete, retrieve, backup, recoveryKey bool
var shares, threshold int
var nonce string
var pgpKeys pgpkeys.PubKeyFilesFlag
@ -36,6 +39,7 @@ func (c *RekeyCommand) Run(args []string) int {
flags.BoolVar(&delete, "delete", false, "")
flags.BoolVar(&retrieve, "retrieve", false, "")
flags.BoolVar(&backup, "backup", false, "")
flags.BoolVar(&recoveryKey, "recovery-key", c.RecoveryKey, "")
flags.IntVar(&shares, "key-shares", 5, "")
flags.IntVar(&threshold, "key-threshold", 3, "")
flags.StringVar(&nonce, "nonce", "", "")
@ -59,19 +63,24 @@ func (c *RekeyCommand) Run(args []string) int {
// Check if we are running doing any restricted variants
switch {
case init:
return c.initRekey(client, shares, threshold, pgpKeys, backup)
return c.initRekey(client, shares, threshold, pgpKeys, backup, recoveryKey)
case cancel:
return c.cancelRekey(client)
return c.cancelRekey(client, recoveryKey)
case status:
return c.rekeyStatus(client)
return c.rekeyStatus(client, recoveryKey)
case retrieve:
return c.rekeyRetrieveStored(client)
return c.rekeyRetrieveStored(client, recoveryKey)
case delete:
return c.rekeyDeleteStored(client)
return c.rekeyDeleteStored(client, recoveryKey)
}
// Check if the rekey is started
rekeyStatus, err := client.Sys().RekeyStatus()
var rekeyStatus *api.RekeyStatusResponse
if recoveryKey {
rekeyStatus, err = client.Sys().RekeyRecoveryKeyStatus()
} else {
rekeyStatus, err = client.Sys().RekeyStatus()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading rekey status: %s", err))
return 1
@ -79,11 +88,19 @@ func (c *RekeyCommand) Run(args []string) int {
// Start the rekey process if not started
if !rekeyStatus.Started {
rekeyStatus, err = client.Sys().RekeyInit(&api.RekeyInitRequest{
SecretShares: shares,
SecretThreshold: threshold,
PGPKeys: pgpKeys,
})
if recoveryKey {
rekeyStatus, err = client.Sys().RekeyRecoveryKeyInit(&api.RekeyInitRequest{
SecretShares: shares,
SecretThreshold: threshold,
PGPKeys: pgpKeys,
})
} else {
rekeyStatus, err = client.Sys().RekeyInit(&api.RekeyInitRequest{
SecretShares: shares,
SecretThreshold: threshold,
PGPKeys: pgpKeys,
})
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err))
return 1
@ -122,7 +139,12 @@ func (c *RekeyCommand) Run(args []string) int {
}
// Provide the key, this may potentially complete the update
result, err := client.Sys().RekeyUpdate(strings.TrimSpace(key), c.Nonce)
var result *api.RekeyUpdateResponse
if recoveryKey {
result, err = client.Sys().RekeyRecoveryKeyUpdate(strings.TrimSpace(key), c.Nonce)
} else {
result, err = client.Sys().RekeyUpdate(strings.TrimSpace(key), c.Nonce)
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error attempting rekey update: %s", err))
return 1
@ -130,7 +152,7 @@ func (c *RekeyCommand) Run(args []string) int {
// If we are not complete, then dump the status
if !result.Complete {
return c.rekeyStatus(client)
return c.rekeyStatus(client, recoveryKey)
}
// Space between the key prompt, if any, and the output
@ -176,14 +198,21 @@ func (c *RekeyCommand) Run(args []string) int {
func (c *RekeyCommand) initRekey(client *api.Client,
shares, threshold int,
pgpKeys pgpkeys.PubKeyFilesFlag,
backup bool) int {
backup, recoveryKey bool) int {
// Start the rekey
status, err := client.Sys().RekeyInit(&api.RekeyInitRequest{
request := &api.RekeyInitRequest{
SecretShares: shares,
SecretThreshold: threshold,
PGPKeys: pgpKeys,
Backup: backup,
})
}
var status *api.RekeyStatusResponse
var err error
if recoveryKey {
status, err = client.Sys().RekeyRecoveryKeyInit(request)
} else {
status, err = client.Sys().RekeyInit(request)
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err))
return 1
@ -214,8 +243,13 @@ be deleted at a later time with 'vault rekey -delete'.
}
// cancelRekey is used to abort the rekey process
func (c *RekeyCommand) cancelRekey(client *api.Client) int {
err := client.Sys().RekeyCancel()
func (c *RekeyCommand) cancelRekey(client *api.Client, recovery bool) int {
var err error
if recovery {
err = client.Sys().RekeyRecoveryKeyCancel()
} else {
err = client.Sys().RekeyCancel()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to cancel rekey: %s", err))
return 1
@ -225,9 +259,15 @@ func (c *RekeyCommand) cancelRekey(client *api.Client) int {
}
// rekeyStatus is used just to fetch and dump the status
func (c *RekeyCommand) rekeyStatus(client *api.Client) int {
func (c *RekeyCommand) rekeyStatus(client *api.Client, recovery bool) int {
// Check the status
status, err := client.Sys().RekeyStatus()
var status *api.RekeyStatusResponse
var err error
if recovery {
status, err = client.Sys().RekeyRecoveryKeyStatus()
} else {
status, err = client.Sys().RekeyStatus()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading rekey status: %s", err))
return 1
@ -240,7 +280,7 @@ func (c *RekeyCommand) dumpRekeyStatus(status *api.RekeyStatusResponse) int {
// Dump the status
statString := fmt.Sprintf(
"Nonce: %s\n"+
"Started: %v\n"+
"Started: %t\n"+
"Key Shares: %d\n"+
"Key Threshold: %d\n"+
"Rekey Progress: %d\n"+
@ -260,8 +300,14 @@ func (c *RekeyCommand) dumpRekeyStatus(status *api.RekeyStatusResponse) int {
return 0
}
func (c *RekeyCommand) rekeyRetrieveStored(client *api.Client) int {
storedKeys, err := client.Sys().RekeyRetrieveBackup()
func (c *RekeyCommand) rekeyRetrieveStored(client *api.Client, recovery bool) int {
var storedKeys *api.RekeyRetrieveResponse
var err error
if recovery {
storedKeys, err = client.Sys().RekeyRetrieveRecoveryBackup()
} else {
storedKeys, err = client.Sys().RekeyRetrieveBackup()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error retrieving stored keys: %s", err))
return 1
@ -274,8 +320,13 @@ func (c *RekeyCommand) rekeyRetrieveStored(client *api.Client) int {
return OutputSecret(c.Ui, "table", secret)
}
func (c *RekeyCommand) rekeyDeleteStored(client *api.Client) int {
err := client.Sys().RekeyDeleteBackup()
func (c *RekeyCommand) rekeyDeleteStored(client *api.Client, recovery bool) int {
var err error
if recovery {
err = client.Sys().RekeyDeleteRecoveryBackup()
} else {
err = client.Sys().RekeyDeleteBackup()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to delete stored keys: %s", err))
return 1
@ -347,6 +398,9 @@ Rekey Options:
stored at "core/unseal-keys-backup" in your physical
storage. You can retrieve or delete them via the
'sys/rekey/backup' endpoint.
-recovery-key=false Whether to rekey the recovery key instead of the
barrier key. This is not normally available.
`
return strings.TrimSpace(helpText)
}

View File

@ -22,7 +22,8 @@ func TestRekey(t *testing.T) {
ui := new(cli.MockUi)
c := &RekeyCommand{
Key: hex.EncodeToString(key),
Key: hex.EncodeToString(key),
RecoveryKey: false,
Meta: meta.Meta{
Ui: ui,
},
@ -33,7 +34,7 @@ func TestRekey(t *testing.T) {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
config, err := core.SealConfig()
config, err := core.SealAccess().BarrierConfig()
if err != nil {
t.Fatalf("err: %s", err)
}
@ -49,6 +50,7 @@ func TestRekey_arg(t *testing.T) {
ui := new(cli.MockUi)
c := &RekeyCommand{
RecoveryKey: false,
Meta: meta.Meta{
Ui: ui,
},
@ -59,7 +61,7 @@ func TestRekey_arg(t *testing.T) {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
config, err := core.SealConfig()
config, err := core.SealAccess().BarrierConfig()
if err != nil {
t.Fatalf("err: %s", err)
}
@ -91,7 +93,7 @@ func TestRekey_init(t *testing.T) {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
config, err := core.RekeyConfig()
config, err := core.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %s", err)
}
@ -126,7 +128,7 @@ func TestRekey_cancel(t *testing.T) {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
config, err := core.RekeyConfig()
config, err := core.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %s", err)
}
@ -204,7 +206,7 @@ func TestRekey_init_pgp(t *testing.T) {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
config, err := core.RekeyConfig()
config, err := core.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %s", err)
}

View File

@ -17,6 +17,7 @@ import (
"time"
"github.com/armon/go-metrics"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/logutils"
"github.com/hashicorp/vault/audit"
@ -161,6 +162,7 @@ func (c *ServerCommand) Run(args []string) int {
Physical: backend,
AdvertiseAddr: config.Backend.AdvertiseAddr,
HAPhysical: nil,
Seal: &vault.DefaultSeal{},
AuditBackends: c.AuditBackends,
CredentialBackends: c.CredentialBackends,
LogicalBackends: c.LogicalBackends,
@ -217,10 +219,12 @@ func (c *ServerCommand) Run(args []string) int {
}
// Initialize the core
core, err := vault.NewCore(coreConfig)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing core: %s", err))
return 1
core, newCoreError := vault.NewCore(coreConfig)
if newCoreError != nil {
if !errwrap.ContainsType(newCoreError, new(vault.NonFatalError)) {
c.Ui.Error(fmt.Sprintf("Error initializing core: %s", newCoreError))
return 1
}
}
// If we're in dev mode, then initialize the core
@ -341,6 +345,11 @@ func (c *ServerCommand) Run(args []string) int {
go server.Serve(ln)
}
if newCoreError != nil {
c.Ui.Output("==> Warning:\n\nNon-fatal error during initialization; check the logs for more information.")
c.Ui.Output("")
}
// Output the header that the server has started
c.Ui.Output("==> Vault server started! Log data will stream in below:\n")
@ -372,7 +381,7 @@ func (c *ServerCommand) enableDev(core *vault.Core, rootTokenID string) (*vault.
init, err := core.Initialize(&vault.SealConfig{
SecretShares: 1,
SecretThreshold: 1,
})
}, nil)
if err != nil {
return nil, err
}

View File

@ -31,8 +31,10 @@ func Handler(core *vault.Core) http.Handler {
mux.Handle("/v1/sys/health", handleSysHealth(core))
mux.Handle("/v1/sys/generate-root/attempt", handleSysGenerateRootAttempt(core))
mux.Handle("/v1/sys/generate-root/update", handleSysGenerateRootUpdate(core))
mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core))
mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core))
mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core, false))
mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core, false))
mux.Handle("/v1/sys/rekey-recovery-key/init", handleSysRekeyInit(core, true))
mux.Handle("/v1/sys/rekey-recovery-key/update", handleSysRekeyUpdate(core, true))
mux.Handle("/v1/sys/capabilities-self", handleLogical(core, true, sysCapabilitiesSelfCallback))
mux.Handle("/v1/sys/", handleLogical(core, true, nil))
mux.Handle("/v1/", handleLogical(core, false, nil))

View File

@ -26,17 +26,26 @@ func handleSysGenerateRootAttempt(core *vault.Core) http.Handler {
func handleSysGenerateRootAttemptGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
// Get the current seal configuration
sealConfig, err := core.SealConfig()
barrierConfig, err := core.SealAccess().BarrierConfig()
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
if sealConfig == nil {
if barrierConfig == nil {
respondError(w, http.StatusBadRequest, fmt.Errorf(
"server is not yet initialized"))
return
}
sealConfig := barrierConfig
if core.SealAccess().RecoveryKeySupported() {
sealConfig, err = core.SealAccess().RecoveryConfig()
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
}
// Get the generation configuration
generationConfig, err := core.GenerateRootConfiguration()
if err != nil {

View File

@ -4,6 +4,7 @@ import (
"encoding/hex"
"net/http"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/vault"
)
@ -41,14 +42,28 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request)
}
// Initialize
result, err := core.Initialize(&vault.SealConfig{
barrierConfig := &vault.SealConfig{
SecretShares: req.SecretShares,
SecretThreshold: req.SecretThreshold,
StoredShares: req.StoredShares,
PGPKeys: req.PGPKeys,
})
if err != nil {
respondError(w, http.StatusBadRequest, err)
return
}
recoveryConfig := &vault.SealConfig{
SecretShares: req.RecoveryShares,
SecretThreshold: req.RecoveryThreshold,
PGPKeys: req.RecoveryPGPKeys,
}
result, initErr := core.Initialize(barrierConfig, recoveryConfig)
if initErr != nil {
if !errwrap.ContainsType(initErr, new(vault.NonFatalError)) {
respondError(w, http.StatusBadRequest, initErr)
return
} else {
// Add a warnings field? The error will be logged in the vault log
// already.
}
}
// Encode the keys
@ -57,21 +72,35 @@ func handleSysInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request)
keys = append(keys, hex.EncodeToString(k))
}
respondOk(w, &InitResponse{
resp := &InitResponse{
Keys: keys,
RootToken: result.RootToken,
})
}
if len(result.RecoveryShares) > 0 {
resp.RecoveryKeys = make([]string, 0, len(result.RecoveryShares))
for _, k := range result.RecoveryShares {
resp.RecoveryKeys = append(resp.RecoveryKeys, hex.EncodeToString(k))
}
}
respondOk(w, resp)
}
type InitRequest struct {
SecretShares int `json:"secret_shares"`
SecretThreshold int `json:"secret_threshold"`
PGPKeys []string `json:"pgp_keys"`
SecretShares int `json:"secret_shares"`
SecretThreshold int `json:"secret_threshold"`
StoredShares int `json:"stored_shares"`
PGPKeys []string `json:"pgp_keys"`
RecoveryShares int `json:"recovery_shares"`
RecoveryThreshold int `json:"recovery_threshold"`
RecoveryPGPKeys []string `json:"recovery_pgp_keys"`
}
type InitResponse struct {
Keys []string `json:"keys"`
RootToken string `json:"root_token"`
Keys []string `json:"keys"`
RecoveryKeys []string `json:"recovery_keys,omitempty"`
RootToken string `json:"root_token"`
}
type InitStatusResponse struct {

View File

@ -10,55 +10,61 @@ import (
"github.com/hashicorp/vault/vault"
)
func handleSysRekeyInit(core *vault.Core) http.Handler {
func handleSysRekeyInit(core *vault.Core, recovery bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
handleSysRekeyInitGet(core, w, r)
case "POST", "PUT":
handleSysRekeyInitPut(core, w, r)
case "DELETE":
handleSysRekeyInitDelete(core, w, r)
switch {
case recovery && !core.SealAccess().RecoveryKeySupported():
respondError(w, http.StatusBadRequest, fmt.Errorf("recovery rekeying not supported"))
case r.Method == "GET":
handleSysRekeyInitGet(core, recovery, w, r)
case r.Method == "POST" || r.Method == "PUT":
handleSysRekeyInitPut(core, recovery, w, r)
case r.Method == "DELETE":
handleSysRekeyInitDelete(core, recovery, w, r)
default:
respondError(w, http.StatusMethodNotAllowed, nil)
}
})
}
func handleSysRekeyInitGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
// Get the current configuration
sealConfig, err := core.SealConfig()
func handleSysRekeyInitGet(core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
barrierConfig, err := core.SealAccess().BarrierConfig()
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
if sealConfig == nil {
if barrierConfig == nil {
respondError(w, http.StatusBadRequest, fmt.Errorf(
"server is not yet initialized"))
return
}
// Get the rekey configuration
rekeyConf, err := core.RekeyConfig()
rekeyConf, err := core.RekeyConfig(recovery)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
// Get the progress
progress, err := core.RekeyProgress()
progress, err := core.RekeyProgress(recovery)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
sealThreshold, err := core.RekeyThreshold(recovery)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
}
// Format the status
status := &RekeyStatusResponse{
Started: false,
T: 0,
N: 0,
Progress: progress,
Required: sealConfig.SecretThreshold,
Required: sealThreshold,
}
if rekeyConf != nil {
status.Nonce = rekeyConf.Nonce
@ -77,7 +83,7 @@ func handleSysRekeyInitGet(core *vault.Core, w http.ResponseWriter, r *http.Requ
respondOk(w, status)
}
func handleSysRekeyInitPut(core *vault.Core, w http.ResponseWriter, r *http.Request) {
func handleSysRekeyInitPut(core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
// Parse the request
var req RekeyRequest
if err := parseRequest(r, &req); err != nil {
@ -93,19 +99,20 @@ func handleSysRekeyInitPut(core *vault.Core, w http.ResponseWriter, r *http.Requ
err := core.RekeyInit(&vault.SealConfig{
SecretShares: req.SecretShares,
SecretThreshold: req.SecretThreshold,
StoredShares: req.StoredShares,
PGPKeys: req.PGPKeys,
Backup: req.Backup,
})
}, recovery)
if err != nil {
respondError(w, http.StatusBadRequest, err)
return
}
handleSysRekeyInitGet(core, w, r)
handleSysRekeyInitGet(core, recovery, w, r)
}
func handleSysRekeyInitDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) {
err := core.RekeyCancel()
func handleSysRekeyInitDelete(core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
err := core.RekeyCancel(recovery)
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
@ -113,7 +120,7 @@ func handleSysRekeyInitDelete(core *vault.Core, w http.ResponseWriter, r *http.R
respondOk(w, nil)
}
func handleSysRekeyUpdate(core *vault.Core) http.Handler {
func handleSysRekeyUpdate(core *vault.Core, recovery bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Parse the request
var req RekeyUpdateRequest
@ -138,7 +145,7 @@ func handleSysRekeyUpdate(core *vault.Core) http.Handler {
}
// Use the key to make progress on rekey
result, err := core.RekeyUpdate(key, req.Nonce)
result, err := core.RekeyUpdate(key, req.Nonce, recovery)
if err != nil {
respondError(w, http.StatusBadRequest, err)
return
@ -167,6 +174,7 @@ func handleSysRekeyUpdate(core *vault.Core) http.Handler {
type RekeyRequest struct {
SecretShares int `json:"secret_shares"`
SecretThreshold int `json:"secret_threshold"`
StoredShares int `json:"stored_shares"`
PGPKeys []string `json:"pgp_keys"`
Backup bool `json:"backup"`
}

View File

@ -135,7 +135,7 @@ func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Req
return
}
sealConfig, err := core.SealConfig()
sealConfig, err := core.SealAccess().BarrierConfig()
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return

View File

@ -146,7 +146,7 @@ func Test(t TestT, c TestCase) {
init, err := core.Initialize(&vault.SealConfig{
SecretShares: 1,
SecretThreshold: 1,
})
}, nil)
if err != nil {
t.Fatal("error initializing core: ", err)
}

View File

@ -2,9 +2,6 @@ package vault
import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"log"
@ -15,28 +12,18 @@ import (
"sync"
"time"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
"github.com/armon/go-metrics"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/mlock"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical"
"github.com/hashicorp/vault/shamir"
)
const (
// coreSealConfigPath is the path used to store our seal configuration.
// This value is stored in plaintext, since we must be able to read
// it even with the Vault sealed. This is required so that we know
// how many secret parts must be used to reconstruct the master key.
coreSealConfigPath = "core/seal-config"
// coreLockPath is the path used to acquire a coordinating lock
// for a highly-available deploy.
coreLockPath = "core/lock"
@ -45,11 +32,6 @@ const (
// the currently elected leader.
coreLeaderPrefix = "core/leader/"
// coreUnsealKeysBackupPath is the path used to back upencrypted unseal
// keys if specified during a rekey operation. This is outside of the
// barrier.
coreUnsealKeysBackupPath = "core/unseal-keys-backup"
// lockRetryInterval is the interval we re-attempt to acquire the
// HA lock if an error is encountered
lockRetryInterval = 10 * time.Second
@ -97,76 +79,18 @@ var (
ErrHANotEnabled = errors.New("Vault is not configured for highly-available mode")
)
// SealConfig is used to describe the seal configuration
type SealConfig struct {
// SecretShares is the number of shares the secret is
// split into. This is the N value of Shamir.
SecretShares int `json:"secret_shares"`
// 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"`
// SecretThreshold is the number of parts required
// to open the vault. This is the T value of Shamir
SecretThreshold int `json:"secret_threshold"`
// 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"`
// Backup indicates whether or not a backup of PGP-encrypted unseal keys
// should be stored at coreUnsealKeysBackupPath after successful rekeying.
Backup bool `json:"backup"`
// NonFatalError is an error that can be returned during NewCore that should be
// displayed but not cause a program exit
type NonFatalError struct {
Err error
}
// Validate is used to sanity check the seal configuration
func (s *SealConfig) Validate() error {
if s.SecretShares < 1 {
return fmt.Errorf("secret shares must be at least one")
}
if s.SecretThreshold < 1 {
return fmt.Errorf("secret threshold must be at least one")
}
if s.SecretShares > 1 && s.SecretThreshold == 1 {
return fmt.Errorf("secret threshold must be greater than one for multiple shares")
}
if s.SecretShares > 255 {
return fmt.Errorf("secret shares must be less than 256")
}
if s.SecretThreshold > 255 {
return fmt.Errorf("secret threshold must be less than 256")
}
if s.SecretThreshold > s.SecretShares {
return fmt.Errorf("secret threshold cannot be larger than secret shares")
}
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: %s", err)
}
_, err = openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(data)))
if err != nil {
return fmt.Errorf("Error parsing given PGP key: %s", err)
}
}
}
return nil
func (e *NonFatalError) WrappedErrors() []error {
return []error{e.Err}
}
// InitResult is used to provide the key parts back after
// they are generated as part of the initialization.
type InitResult struct {
SecretShares [][]byte
RootToken string
func (e *NonFatalError) Error() string {
return e.Err.Error()
}
// ErrInvalidKey is returned if there is an error with a
@ -192,6 +116,9 @@ type Core struct {
// physical backend is the un-trusted backend with durable data
physical physical.Backend
// Our Seal, for seal configuration information
seal Seal
// barrier is the security barrier wrapping the physical backend
barrier SecurityBarrier
@ -226,11 +153,14 @@ type Core struct {
generateRootProgress [][]byte
generateRootLock sync.Mutex
// rekeyProgress holds the shares we have until we reach enough
// to verify the master key.
rekeyConfig *SealConfig
rekeyProgress [][]byte
rekeyLock sync.Mutex
// These variables holds the config and shares we have until we reach
// enough to verify the appropriate master key. Note that the same lock is
// used; this isn't time-critical so this shouldn't be a problem.
barrierRekeyConfig *SealConfig
barrierRekeyProgress [][]byte
recoveryRekeyConfig *SealConfig
recoveryRekeyProgress [][]byte
rekeyLock sync.RWMutex
// mounts is loaded after unseal since it is a protected
// configuration
@ -296,6 +226,7 @@ type CoreConfig struct {
AuditBackends map[string]audit.Factory
Physical physical.Backend
HAPhysical physical.HABackend // May be nil, which disables HA operations
Seal Seal
Logger *log.Logger
DisableCache bool // Disables the LRU cache on the physical backend
DisableMlock bool // Disables mlock syscall
@ -375,6 +306,7 @@ func NewCore(conf *CoreConfig) (*Core, error) {
ha: conf.HAPhysical,
advertiseAddr: conf.AdvertiseAddr,
physical: conf.Physical,
seal: conf.Seal,
barrier: barrier,
router: NewRouter(),
sealed: true,
@ -413,7 +345,17 @@ func NewCore(conf *CoreConfig) (*Core, error) {
auditBackends[k] = f
}
c.auditBackends = auditBackends
return c, nil
if c.seal == nil {
c.seal = &DefaultSeal{}
}
c.seal.SetCore(c)
// Attempt unsealing with stored keys; if there are no stored keys this
// returns nil, otherwise returns nil or an error
storedKeyErr := c.unsealWithStoredKeys()
return c, storedKeyErr
}
// Shutdown is invoked when the Vault instance is about to be terminated. It
@ -819,148 +761,6 @@ func (c *Core) checkToken(req *logical.Request) (*logical.Auth, *TokenEntry, err
return auth, te, nil
}
// Initialized checks if the Vault is already initialized
func (c *Core) Initialized() (bool, error) {
// Check the barrier first
init, err := c.barrier.Initialized()
if err != nil {
c.logger.Printf("[ERR] core: barrier init check failed: %v", err)
return false, err
}
if !init {
return false, nil
}
if !init {
c.logger.Printf("[INFO] core: security barrier not initialized")
return false, nil
}
// Verify the seal configuration
sealConf, err := c.SealConfig()
if err != nil {
return false, err
}
if sealConf == nil {
return false, nil
}
return true, nil
}
// Initialize is used to initialize the Vault with the given
// configurations.
func (c *Core) Initialize(config *SealConfig) (*InitResult, error) {
// Check if the seal configuraiton is valid
if err := config.Validate(); err != nil {
c.logger.Printf("[ERR] core: invalid seal configuration: %v", err)
return nil, fmt.Errorf("invalid seal configuration: %v", err)
}
// Avoid an initialization race
c.stateLock.Lock()
defer c.stateLock.Unlock()
// Check if we are initialized
init, err := c.Initialized()
if err != nil {
return nil, err
}
if init {
return nil, ErrAlreadyInit
}
// Encode the seal configuration
buf, err := json.Marshal(config)
if err != nil {
return nil, fmt.Errorf("failed to encode seal configuration: %v", err)
}
// Store the seal configuration
pe := &physical.Entry{
Key: coreSealConfigPath,
Value: buf,
}
if err := c.physical.Put(pe); err != nil {
c.logger.Printf("[ERR] core: failed to write seal configuration: %v", err)
return nil, fmt.Errorf("failed to write seal configuration: %v", err)
}
// Generate a master key
masterKey, err := c.barrier.GenerateKey()
if err != nil {
c.logger.Printf("[ERR] core: failed to generate master key: %v", err)
return nil, fmt.Errorf("master key generation failed: %v", err)
}
// Return the master key if only a single key part is used
results := new(InitResult)
if config.SecretShares == 1 {
results.SecretShares = append(results.SecretShares, masterKey)
} else {
// Split the master key using the Shamir algorithm
shares, err := shamir.Split(masterKey, config.SecretShares, config.SecretThreshold)
if err != nil {
c.logger.Printf("[ERR] core: failed to generate shares: %v", err)
return nil, fmt.Errorf("failed to generate shares: %v", err)
}
results.SecretShares = shares
}
if len(config.PGPKeys) > 0 {
hexEncodedShares := make([][]byte, len(results.SecretShares))
for i, _ := range results.SecretShares {
hexEncodedShares[i] = []byte(hex.EncodeToString(results.SecretShares[i]))
}
_, encryptedShares, err := pgpkeys.EncryptShares(hexEncodedShares, config.PGPKeys)
if err != nil {
return nil, err
}
results.SecretShares = encryptedShares
}
// Initialize the barrier
if err := c.barrier.Initialize(masterKey); err != nil {
c.logger.Printf("[ERR] core: failed to initialize barrier: %v", err)
return nil, fmt.Errorf("failed to initialize barrier: %v", err)
}
c.logger.Printf("[INFO] core: security barrier initialized (shares: %d, threshold %d)",
config.SecretShares, config.SecretThreshold)
// Unseal the barrier
if err := c.barrier.Unseal(masterKey); err != nil {
c.logger.Printf("[ERR] core: failed to unseal barrier: %v", err)
return nil, fmt.Errorf("failed to unseal barrier: %v", err)
}
// Ensure the barrier is re-sealed
defer func() {
if err := c.barrier.Seal(); err != nil {
c.logger.Printf("[ERR] core: failed to seal barrier: %v", err)
}
}()
// Perform initial setup
if err := c.postUnseal(); err != nil {
c.logger.Printf("[ERR] core: post-unseal setup failed: %v", err)
return nil, err
}
// Generate a new root token
rootToken, err := c.tokenStore.rootToken()
if err != nil {
c.logger.Printf("[ERR] core: root token generation failed: %v", err)
return nil, err
}
results.RootToken = rootToken.ID
c.logger.Printf("[INFO] core: root token generated")
// Prepare to re-seal
if err := c.preSeal(); err != nil {
c.logger.Printf("[ERR] core: pre-seal teardown failed: %v", err)
return nil, err
}
return results, nil
}
// Sealed checks if the Vault is current sealed
func (c *Core) Sealed() (bool, error) {
c.stateLock.RLock()
@ -1023,39 +823,6 @@ func (c *Core) Leader() (bool, string, error) {
return false, string(entry.Value), nil
}
// SealConfiguration is used to return information
// about the configuration of the Vault and it's current
// status.
func (c *Core) SealConfig() (*SealConfig, error) {
// Fetch the core configuration
pe, err := c.physical.Get(coreSealConfigPath)
if err != nil {
c.logger.Printf("[ERR] core: failed to read seal configuration: %v", err)
return nil, fmt.Errorf("failed to check seal configuration: %v", err)
}
// If the seal configuration is missing, we are not initialized
if pe == nil {
c.logger.Printf("[INFO] core: seal configuration missing, not initialized")
return nil, nil
}
// Decode the barrier entry
var conf SealConfig
if err := json.Unmarshal(pe.Value, &conf); err != nil {
c.logger.Printf("[ERR] core: failed to decode seal configuration: %v", err)
return nil, fmt.Errorf("failed to decode seal configuration: %v", err)
}
// Check for a valid seal configuration
if err := conf.Validate(); err != nil {
c.logger.Printf("[ERR] core: invalid seal configuration: %v", err)
return nil, fmt.Errorf("seal validation failed: %v", err)
}
return &conf, nil
}
// SecretProgress returns the number of keys provided so far
func (c *Core) SecretProgress() int {
c.stateLock.RLock()
@ -1093,7 +860,7 @@ func (c *Core) Unseal(key []byte) (bool, error) {
}
// Get the seal configuration
config, err := c.SealConfig()
config, err := c.seal.BarrierConfig()
if err != nil {
return false, err
}
@ -1385,8 +1152,10 @@ func (c *Core) preSeal() error {
c.logger.Printf("[INFO] core: pre-seal teardown starting")
// Clear any rekey progress
c.rekeyConfig = nil
c.rekeyProgress = nil
c.barrierRekeyConfig = nil
c.barrierRekeyProgress = nil
c.recoveryRekeyConfig = nil
c.recoveryRekeyProgress = nil
if c.metricsCh != nil {
close(c.metricsCh)
@ -1662,3 +1431,9 @@ func (c *Core) emitMetrics(stopCh chan struct{}) {
}
}
}
func (c *Core) SealAccess() *SealAccess {
sa := &SealAccess{}
sa.SetSeal(c.seal)
return sa
}

View File

@ -39,135 +39,6 @@ func TestSealConfig_Invalid(t *testing.T) {
}
}
func TestCore_Init(t *testing.T) {
inm := physical.NewInmem()
conf := &CoreConfig{
Physical: inm,
DisableMlock: true,
LogicalBackends: map[string]logical.Factory{
"generic": LeasedPassthroughBackendFactory,
},
}
c, err := NewCore(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
init, err := c.Initialized()
if err != nil {
t.Fatalf("err: %v", err)
}
if init {
t.Fatalf("should not be init")
}
// Check the seal configuration
outConf, err := c.SealConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if outConf != nil {
t.Fatalf("bad: %v", outConf)
}
sealConf := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
}
res, err := c.Initialize(sealConf)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(res.SecretShares) != 1 {
t.Fatalf("Bad: %v", res)
}
if res.RootToken == "" {
t.Fatalf("Bad: %v", res)
}
_, err = c.Initialize(sealConf)
if err != ErrAlreadyInit {
t.Fatalf("err: %v", err)
}
init, err = c.Initialized()
if err != nil {
t.Fatalf("err: %v", err)
}
if !init {
t.Fatalf("should be init")
}
// Check the seal configuration
outConf, err = c.SealConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(outConf, sealConf) {
t.Fatalf("bad: %v expect: %v", outConf, sealConf)
}
// New Core, same backend
c2, err := NewCore(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
_, err = c2.Initialize(sealConf)
if err != ErrAlreadyInit {
t.Fatalf("err: %v", err)
}
init, err = c2.Initialized()
if err != nil {
t.Fatalf("err: %v", err)
}
if !init {
t.Fatalf("should be init")
}
// Check the seal configuration
outConf, err = c2.SealConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(outConf, sealConf) {
t.Fatalf("bad: %v expect: %v", outConf, sealConf)
}
}
func TestCore_Init_MultiShare(t *testing.T) {
c := TestCore(t)
sealConf := &SealConfig{
SecretShares: 5,
SecretThreshold: 3,
}
res, err := c.Initialize(sealConf)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(res.SecretShares) != 5 {
t.Fatalf("Bad: %v", res)
}
if res.RootToken == "" {
t.Fatalf("Bad: %v", res)
}
// Check the seal configuration
outConf, err := c.SealConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(outConf, sealConf) {
t.Fatalf("bad: %v expect: %v", outConf, sealConf)
}
}
func TestCore_Unseal_MultiShare(t *testing.T) {
c := TestCore(t)
@ -180,7 +51,7 @@ func TestCore_Unseal_MultiShare(t *testing.T) {
SecretShares: 5,
SecretThreshold: 3,
}
res, err := c.Initialize(sealConf)
res, err := c.Initialize(sealConf, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -265,7 +136,7 @@ func TestCore_Unseal_Single(t *testing.T) {
SecretShares: 1,
SecretThreshold: 1,
}
res, err := c.Initialize(sealConf)
res, err := c.Initialize(sealConf, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -320,7 +191,7 @@ func TestCore_Route_Sealed(t *testing.T) {
t.Fatalf("err: %v", err)
}
res, err := c.Initialize(sealConf)
res, err := c.Initialize(sealConf, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -2125,96 +1996,3 @@ func TestCore_Standby_Rotate(t *testing.T) {
t.Fatalf("bad: %#v", resp)
}
}
func TestCore_Standby_Rekey(t *testing.T) {
// Create the first core and initialize it
inm := physical.NewInmem()
inmha := physical.NewInmemHA()
advertiseOriginal := "http://127.0.0.1:8200"
core, err := NewCore(&CoreConfig{
Physical: inm,
HAPhysical: inmha,
AdvertiseAddr: advertiseOriginal,
DisableMlock: true,
})
if err != nil {
t.Fatalf("err: %v", err)
}
key, root := TestCoreInit(t, core)
if _, err := core.Unseal(TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
// Wait for core to become active
testWaitActive(t, core)
// Create a second core, attached to same in-memory store
advertiseOriginal2 := "http://127.0.0.1:8500"
core2, err := NewCore(&CoreConfig{
Physical: inm,
HAPhysical: inmha,
AdvertiseAddr: advertiseOriginal2,
DisableMlock: true,
})
if err != nil {
t.Fatalf("err: %v", err)
}
if _, err := core2.Unseal(TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
// Rekey the master key
newConf := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
}
err = core.RekeyInit(newConf)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err := core.RekeyConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
result, err := core.RekeyUpdate(key, rkconf.Nonce)
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("rekey failed")
}
// Seal the first core, should step down
err = core.Seal(root)
if err != nil {
t.Fatalf("err: %v", err)
}
// Wait for core2 to become active
testWaitActive(t, core2)
// Rekey the master key again
err = core2.RekeyInit(newConf)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err = core2.RekeyConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
result, err = core2.RekeyUpdate(result.SecretShares[0], rkconf.Nonce)
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("rekey failed")
}
}

View File

@ -146,9 +146,18 @@ func (c *Core) GenerateRootUpdate(key []byte, nonce string) (*GenerateRootResult
}
// Get the seal configuration
config, err := c.SealConfig()
if err != nil {
return nil, err
var config *SealConfig
var err error
if c.seal.RecoveryKeySupported() {
config, err = c.seal.RecoveryConfig()
if err != nil {
return nil, err
}
} else {
config, err = c.seal.BarrierConfig()
if err != nil {
return nil, err
}
}
// Ensure the barrier is initialized
@ -214,9 +223,16 @@ func (c *Core) GenerateRootUpdate(key []byte, nonce string) (*GenerateRootResult
}
// Verify the master key
if err := c.barrier.VerifyMaster(masterKey); err != nil {
c.logger.Printf("[ERR] core: root generation aborted, master key verification failed: %v", err)
return nil, err
if c.seal.RecoveryKeySupported() {
if err := c.seal.VerifyRecoveryKey(masterKey); err != nil {
c.logger.Printf("[ERR] core: root generation aborted, recovery key verification failed: %v", err)
return nil, err
}
} else {
if err := c.barrier.VerifyMaster(masterKey); err != nil {
c.logger.Printf("[ERR] core: root generation aborted, master key verification failed: %v", err)
return nil, err
}
}
te, err := c.tokenStore.rootToken()

282
vault/init.go Normal file
View File

@ -0,0 +1,282 @@
package vault
import (
"encoding/hex"
"fmt"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/shamir"
)
// InitResult is used to provide the key parts back after
// they are generated as part of the initialization.
type InitResult struct {
SecretShares [][]byte
RecoveryShares [][]byte
RootToken string
}
// Initialized checks if the Vault is already initialized
func (c *Core) Initialized() (bool, error) {
// Check the barrier first
init, err := c.barrier.Initialized()
if err != nil {
c.logger.Printf("[ERR] core: barrier init check failed: %v", err)
return false, err
}
if !init {
c.logger.Printf("[INFO] core: security barrier not initialized")
return false, nil
}
// Verify the seal configuration
sealConf, err := c.seal.BarrierConfig()
if err != nil {
return false, err
}
if sealConf == nil {
return false, nil
}
return true, nil
}
func (c *Core) generateShares(sc *SealConfig) ([]byte, [][]byte, error) {
// Generate a master key
masterKey, err := c.barrier.GenerateKey()
if err != nil {
return nil, nil, fmt.Errorf("key generation failed: %v", err)
}
// Return the master key if only a single key part is used
var unsealKeys [][]byte
if sc.SecretShares == 1 {
unsealKeys = append(unsealKeys, masterKey)
} else {
// Split the master key using the Shamir algorithm
shares, err := shamir.Split(masterKey, sc.SecretShares, sc.SecretThreshold)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate shares: %v", err)
}
unsealKeys = shares
}
// If we have PGP keys, perform the encryption
if len(sc.PGPKeys) > 0 {
hexEncodedShares := make([][]byte, len(unsealKeys))
for i, _ := range unsealKeys {
hexEncodedShares[i] = []byte(hex.EncodeToString(unsealKeys[i]))
}
_, encryptedShares, err := pgpkeys.EncryptShares(hexEncodedShares, sc.PGPKeys)
if err != nil {
return nil, nil, err
}
unsealKeys = encryptedShares
}
return masterKey, unsealKeys, nil
}
// Initialize is used to initialize the Vault with the given
// configurations.
func (c *Core) Initialize(barrierConfig, recoveryConfig *SealConfig) (*InitResult, error) {
if c.seal.RecoveryKeySupported() {
if recoveryConfig == nil {
return nil, fmt.Errorf("recovery configuration must be supplied")
}
if recoveryConfig.SecretShares == 0 {
return nil, fmt.Errorf("recovery configuration must specify a positive number of shares, or a negative number to disable")
}
if recoveryConfig.SecretShares > 0 {
// Check if the seal configuraiton is valid
if err := recoveryConfig.Validate(); err != nil {
c.logger.Printf("[ERR] core: invalid recovery configuration: %v", err)
return nil, fmt.Errorf("invalid recovery configuration: %v", err)
}
}
}
if c.seal.StoredKeysSupported() {
if barrierConfig.SecretShares != 1 {
return nil, fmt.Errorf("secret shares must be 1")
}
if barrierConfig.SecretThreshold != barrierConfig.SecretShares {
return nil, fmt.Errorf("secret threshold must be same as secret shares")
}
if barrierConfig.StoredShares != barrierConfig.SecretShares {
return nil, fmt.Errorf("stored shares must be same as secret shares")
}
if barrierConfig.PGPKeys != nil && len(barrierConfig.PGPKeys) > 0 {
return nil, fmt.Errorf("PGP keys not supported when storing shares")
}
} else {
if barrierConfig.StoredShares > 0 {
return nil, fmt.Errorf("stored keys are not supported")
}
}
// Check if the seal configuraiton is valid
if err := barrierConfig.Validate(); err != nil {
c.logger.Printf("[ERR] core: invalid seal configuration: %v", err)
return nil, fmt.Errorf("invalid seal configuration: %v", err)
}
// We defer this now because the unseal operation locks the state lock, and
// defer operations are LIFO
defer c.unsealWithStoredKeys()
// Avoid an initialization race
c.stateLock.Lock()
defer c.stateLock.Unlock()
// Check if we are initialized
init, err := c.Initialized()
if err != nil {
return nil, err
}
if init {
return nil, ErrAlreadyInit
}
err = c.seal.Init()
if err != nil {
c.logger.Printf("[ERR] core: failed to initialize seal: %v", err)
return nil, fmt.Errorf("error initializing seal: %v", err)
}
err = c.seal.SetBarrierConfig(barrierConfig)
if err != nil {
c.logger.Printf("[ERR] core: failed to save barrier configuration: %v", err)
return nil, fmt.Errorf("barrier configuration saving failed: %v", err)
}
barrierKey, barrierUnsealKeys, err := c.generateShares(barrierConfig)
if err != nil {
c.logger.Printf("[ERR] core: %v", err)
return nil, err
}
// If we are storing shares, pop them out of the returned results and push
// them through the seal
if barrierConfig.StoredShares > 0 {
var keysToStore [][]byte
for i := 0; i < barrierConfig.StoredShares; i++ {
keysToStore = append(keysToStore, barrierUnsealKeys[0])
barrierUnsealKeys = barrierUnsealKeys[1:]
}
if err := c.seal.SetStoredKeys(keysToStore); err != nil {
c.logger.Printf("[ERR] core: failed to store keys: %v", err)
return nil, fmt.Errorf("failed to store keys: %v", err)
}
}
results := &InitResult{
SecretShares: barrierUnsealKeys,
}
// Save the configuration regardless, but only generate a key if it's not
// disabled
if c.seal.RecoveryKeySupported() {
err = c.seal.SetRecoveryConfig(recoveryConfig)
if err != nil {
c.logger.Printf("[ERR] core: failed to save recovery configuration: %v", err)
return nil, fmt.Errorf("recovery configuration saving failed: %v", err)
}
if recoveryConfig.SecretShares > 0 {
recoveryKey, recoveryUnsealKeys, err := c.generateShares(recoveryConfig)
if err != nil {
c.logger.Printf("[ERR] core: %v", err)
return nil, err
}
err = c.seal.SetRecoveryKey(recoveryKey)
if err != nil {
return nil, err
}
results.RecoveryShares = recoveryUnsealKeys
}
}
// Initialize the barrier
if err := c.barrier.Initialize(barrierKey); err != nil {
c.logger.Printf("[ERR] core: failed to initialize barrier: %v", err)
return nil, fmt.Errorf("failed to initialize barrier: %v", err)
}
c.logger.Printf("[INFO] core: security barrier initialized (shares: %d, threshold %d)",
barrierConfig.SecretShares, barrierConfig.SecretThreshold)
// Unseal the barrier
if err := c.barrier.Unseal(barrierKey); err != nil {
c.logger.Printf("[ERR] core: failed to unseal barrier: %v", err)
return nil, fmt.Errorf("failed to unseal barrier: %v", err)
}
// Ensure the barrier is re-sealed
defer func() {
if err := c.barrier.Seal(); err != nil {
c.logger.Printf("[ERR] core: failed to seal barrier: %v", err)
}
}()
// Perform initial setup
if err := c.postUnseal(); err != nil {
c.logger.Printf("[ERR] core: post-unseal setup failed: %v", err)
return nil, err
}
// Generate a new root token
rootToken, err := c.tokenStore.rootToken()
if err != nil {
c.logger.Printf("[ERR] core: root token generation failed: %v", err)
return nil, err
}
results.RootToken = rootToken.ID
c.logger.Printf("[INFO] core: root token generated")
// Prepare to re-seal
if err := c.preSeal(); err != nil {
c.logger.Printf("[ERR] core: pre-seal teardown failed: %v", err)
return nil, err
}
return results, nil
}
func (c *Core) unsealWithStoredKeys() error {
if !c.seal.StoredKeysSupported() {
return nil
}
c.logger.Printf("[INFO] core: stored unseal keys supported, attempting fetch")
keys, err := c.seal.GetStoredKeys()
if err != nil {
c.logger.Printf("[ERR] core: fetching stored unseal keys failed: %v", err)
return &NonFatalError{Err: fmt.Errorf("fetching stored unseal keys failed: %v", err)}
}
if len(keys) == 0 {
c.logger.Printf("[WARN] core: stored unseal key(s) supported but none found")
} else {
unsealed := false
keysUsed := 0
for _, key := range keys {
unsealed, err = c.Unseal(key)
if err != nil {
c.logger.Printf("[ERR] core: unseal with stored unseal key failed: %v", err)
return &NonFatalError{Err: fmt.Errorf("unseal with stored key failed: %v", err)}
}
keysUsed += 1
if unsealed {
break
}
}
if !unsealed {
c.logger.Printf("[WARN] core: %d stored unseal key(s) used but Vault not unsealed yet", keysUsed)
} else {
c.logger.Printf("[INFO] core: successfully unsealed with %d stored key(s)", keysUsed)
}
}
return nil
}

110
vault/init_test.go Normal file
View File

@ -0,0 +1,110 @@
package vault
import (
"reflect"
"testing"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/physical"
)
func TestCore_Init(t *testing.T) {
inm := physical.NewInmem()
conf := &CoreConfig{
Physical: inm,
DisableMlock: true,
LogicalBackends: map[string]logical.Factory{
"generic": LeasedPassthroughBackendFactory,
},
}
c, err := NewCore(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
init, err := c.Initialized()
if err != nil {
t.Fatalf("err: %v", err)
}
if init {
t.Fatalf("should not be init")
}
// Check the seal configuration
outConf, err := c.seal.BarrierConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if outConf != nil {
t.Fatalf("bad: %v", outConf)
}
sealConf := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
}
res, err := c.Initialize(sealConf, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(res.SecretShares) != 1 {
t.Fatalf("Bad: %v", res)
}
if res.RootToken == "" {
t.Fatalf("Bad: %v", res)
}
_, err = c.Initialize(sealConf, nil)
if err != ErrAlreadyInit {
t.Fatalf("err: %v", err)
}
init, err = c.Initialized()
if err != nil {
t.Fatalf("err: %v", err)
}
if !init {
t.Fatalf("should be init")
}
// Check the seal configuration
outConf, err = c.seal.BarrierConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(outConf, sealConf) {
t.Fatalf("bad: %v expect: %v", outConf, sealConf)
}
// New Core, same backend
c2, err := NewCore(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
_, err = c2.Initialize(sealConf, nil)
if err != ErrAlreadyInit {
t.Fatalf("err: %v", err)
}
init, err = c2.Initialized()
if err != nil {
t.Fatalf("err: %v", err)
}
if !init {
t.Fatalf("should be init")
}
// Check the seal configuration
outConf, err = c2.seal.BarrierConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(outConf, sealConf) {
t.Fatalf("bad: %v expect: %v", outConf, sealConf)
}
}

View File

@ -111,8 +111,22 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend
Fields: map[string]*framework.FieldSchema{},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleRekeyRetrieve,
logical.DeleteOperation: b.handleRekeyDelete,
logical.ReadOperation: b.handleRekeyRetrieveBarrier,
logical.DeleteOperation: b.handleRekeyDeleteBarrier,
},
HelpSynopsis: strings.TrimSpace(sysHelp["rekey_backup"][0]),
HelpDescription: strings.TrimSpace(sysHelp["rekey_backup"][0]),
},
&framework.Path{
Pattern: "rekey/recovery-key-backup$",
Fields: map[string]*framework.FieldSchema{},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleRekeyRetrieveRecovery,
logical.DeleteOperation: b.handleRekeyDeleteRecovery,
},
HelpSynopsis: strings.TrimSpace(sysHelp["rekey_backup"][0]),
@ -522,8 +536,10 @@ func (b *SystemBackend) handleCapabilitiesAccessor(req *logical.Request, d *fram
// handleRekeyRetrieve returns backed-up, PGP-encrypted unseal keys from a
// rekey operation
func (b *SystemBackend) handleRekeyRetrieve(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
backup, err := b.Core.RekeyRetrieveBackup()
req *logical.Request,
data *framework.FieldData,
recovery bool) (*logical.Response, error) {
backup, err := b.Core.RekeyRetrieveBackup(recovery)
if err != nil {
return nil, fmt.Errorf("unable to look up backed-up keys: %v", err)
}
@ -542,17 +558,38 @@ func (b *SystemBackend) handleRekeyRetrieve(
return resp, nil
}
func (b *SystemBackend) handleRekeyRetrieveBarrier(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return b.handleRekeyRetrieve(req, data, false)
}
func (b *SystemBackend) handleRekeyRetrieveRecovery(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return b.handleRekeyRetrieve(req, data, true)
}
// handleRekeyDelete deletes backed-up, PGP-encrypted unseal keys from a rekey
// operation
func (b *SystemBackend) handleRekeyDelete(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
err := b.Core.RekeyDeleteBackup()
req *logical.Request,
data *framework.FieldData,
recovery bool) (*logical.Response, error) {
err := b.Core.RekeyDeleteBackup(recovery)
if err != nil {
return nil, fmt.Errorf("error during deletion of backed-up keys: %v", err)
}
return nil, nil
}
func (b *SystemBackend) handleRekeyDeleteBarrier(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return b.handleRekeyDelete(req, data, false)
}
func (b *SystemBackend) handleRekeyDeleteRecovery(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return b.handleRekeyDelete(req, data, true)
}
// handleMountTable handles the "mounts" endpoint to provide the mount table
func (b *SystemBackend) handleMountTable(

View File

@ -12,12 +12,25 @@ import (
"github.com/hashicorp/vault/shamir"
)
const (
// coreUnsealKeysBackupPath is the path used to back upencrypted unseal
// keys if specified during a rekey operation. This is outside of the
// barrier.
coreBarrierUnsealKeysBackupPath = "core/unseal-keys-backup"
// coreRecoveryUnsealKeysBackupPath is the path used to back upencrypted
// recovery keys if specified during a rekey operation. This is outside of
// the barrier.
coreRecoveryUnsealKeysBackupPath = "core/recovery-keys-backup"
)
// RekeyResult is used to provide the key parts back after
// they are generated as part of the rekey.
type RekeyResult struct {
SecretShares [][]byte
PGPFingerprints []string
Backup bool
RecoveryKey bool
}
// RekeyBackup stores the backup copy of PGP-encrypted keys
@ -26,8 +39,7 @@ type RekeyBackup struct {
Keys map[string][]string
}
// RekeyProgress is used to return the rekey progress (num shares)
func (c *Core) RekeyProgress() (int, error) {
func (c *Core) RekeyThreshold(recovery bool) (int, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
@ -37,13 +49,45 @@ func (c *Core) RekeyProgress() (int, error) {
return 0, ErrStandby
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
return len(c.rekeyProgress), nil
c.rekeyLock.RLock()
defer c.rekeyLock.RUnlock()
var config *SealConfig
var err error
if recovery {
config, err = c.seal.RecoveryConfig()
} else {
config, err = c.seal.BarrierConfig()
}
if err != nil {
return 0, err
}
return config.SecretThreshold, nil
}
// RekeyProgress is used to return the rekey progress (num shares)
func (c *Core) RekeyProgress(recovery bool) (int, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return 0, ErrSealed
}
if c.standby {
return 0, ErrStandby
}
c.rekeyLock.RLock()
defer c.rekeyLock.RUnlock()
if recovery {
return len(c.recoveryRekeyProgress), nil
}
return len(c.barrierRekeyProgress), nil
}
// RekeyConfig is used to read the rekey configuration
func (c *Core) RekeyConfig() (*SealConfig, error) {
func (c *Core) RekeyConfig(recovery bool) (*SealConfig, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
@ -58,15 +102,47 @@ func (c *Core) RekeyConfig() (*SealConfig, error) {
// Copy the seal config if any
var conf *SealConfig
if c.rekeyConfig != nil {
conf = new(SealConfig)
*conf = *c.rekeyConfig
if recovery {
if c.recoveryRekeyConfig != nil {
conf = c.recoveryRekeyConfig.Clone()
}
} else {
if c.barrierRekeyConfig != nil {
conf = c.barrierRekeyConfig.Clone()
}
}
return conf, nil
}
// RekeyInit is used to initialize the rekey settings
func (c *Core) RekeyInit(config *SealConfig) error {
func (c *Core) RekeyInit(config *SealConfig, recovery bool) error {
if recovery {
return c.RecoveryRekeyInit(config)
}
return c.BarrierRekeyInit(config)
}
// BarrierRekeyInit is used to initialize the rekey settings for the barrier key
func (c *Core) BarrierRekeyInit(config *SealConfig) error {
// Right now we don't support this, but the rest of the code is ready for
// when we do, hence the check below for this to be false if
// config.StoredShares is greater than zero
if c.seal.StoredKeysSupported() {
return fmt.Errorf("rekeying of barrier not supported when stored key support is available")
}
if config.StoredShares > 0 {
if !c.seal.StoredKeysSupported() {
return fmt.Errorf("storing keys not supported by barrier seal")
}
if len(config.PGPKeys) > 0 {
return fmt.Errorf("PGP key encryption not supported when using stored keys")
}
if config.Backup {
return fmt.Errorf("key backup not supported when using stored keys")
}
}
// Check if the seal configuraiton is valid
if err := config.Validate(); err != nil {
c.logger.Printf("[ERR] core: invalid rekey seal configuration: %v", err)
@ -82,51 +158,88 @@ func (c *Core) RekeyInit(config *SealConfig) error {
return ErrStandby
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Prevent multiple concurrent re-keys
if c.rekeyConfig != nil {
if c.barrierRekeyConfig != nil {
return fmt.Errorf("rekey already in progress")
}
// Copy the configuration
c.rekeyConfig = new(SealConfig)
*c.rekeyConfig = *config
c.barrierRekeyConfig = config.Clone()
// Initialize the nonce
nonce, err := uuid.GenerateUUID()
if err != nil {
c.rekeyConfig = nil
c.barrierRekeyConfig = nil
return err
}
c.rekeyConfig.Nonce = nonce
c.barrierRekeyConfig.Nonce = nonce
c.logger.Printf("[INFO] core: rekey initialized (nonce: %s, shares: %d, threshold: %d)",
c.rekeyConfig.Nonce, c.rekeyConfig.SecretShares, c.rekeyConfig.SecretThreshold)
c.barrierRekeyConfig.Nonce, c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold)
return nil
}
// RekeyUpdate is used to provide a new key part
func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
if len(key) < min {
return nil, &ErrInvalidKey{fmt.Sprintf("key is shorter than minimum %d bytes", min)}
}
if len(key) > max {
return nil, &ErrInvalidKey{fmt.Sprintf("key is longer than maximum %d bytes", max)}
// RecoveryRekeyInit is used to initialize the rekey settings for the recovery key
func (c *Core) RecoveryRekeyInit(config *SealConfig) error {
if config.StoredShares > 0 {
return fmt.Errorf("stored shares not supported by recovery key")
}
// Get the seal configuration
config, err := c.SealConfig()
// Check if the seal configuraiton is valid
if err := config.Validate(); err != nil {
c.logger.Printf("[ERR] core: invalid recovery configuration: %v", err)
return fmt.Errorf("invalid recovery configuration: %v", err)
}
if !c.seal.RecoveryKeySupported() {
return fmt.Errorf("recovery keys not supported")
}
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
}
if c.standby {
return ErrStandby
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Prevent multiple concurrent re-keys
if c.recoveryRekeyConfig != nil {
return fmt.Errorf("rekey already in progress")
}
// Copy the configuration
c.recoveryRekeyConfig = config.Clone()
// Initialize the nonce
nonce, err := uuid.GenerateUUID()
if err != nil {
return nil, err
c.recoveryRekeyConfig = nil
return err
}
c.recoveryRekeyConfig.Nonce = nonce
// Ensure the barrier is initialized
if config == nil {
return nil, ErrNotInit
c.logger.Printf("[INFO] core: rekey initialized (nonce: %s, shares: %d, threshold: %d)",
c.recoveryRekeyConfig.Nonce, c.recoveryRekeyConfig.SecretShares, c.recoveryRekeyConfig.SecretThreshold)
return nil
}
func (c *Core) RekeyUpdate(key []byte, nonce string, recovery bool) (*RekeyResult, error) {
if recovery {
return c.RecoveryRekeyUpdate(key, nonce)
}
return c.BarrierRekeyUpdate(key, nonce)
}
// BarrierRekeyUpdate is used to provide a new key part
func (c *Core) BarrierRekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
// Ensure we are already unsealed
c.stateLock.RLock()
defer c.stateLock.RUnlock()
@ -137,49 +250,69 @@ func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
return nil, ErrStandby
}
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
if len(key) < min {
return nil, &ErrInvalidKey{fmt.Sprintf("key is shorter than minimum %d bytes", min)}
}
if len(key) > max {
return nil, &ErrInvalidKey{fmt.Sprintf("key is longer than maximum %d bytes", max)}
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Get the seal configuration
existingConfig, err := c.seal.BarrierConfig()
if err != nil {
return nil, err
}
// Ensure the barrier is initialized
if existingConfig == nil {
return nil, ErrNotInit
}
// Ensure a rekey is in progress
if c.rekeyConfig == nil {
if c.barrierRekeyConfig == nil {
return nil, fmt.Errorf("no rekey in progress")
}
if nonce != c.rekeyConfig.Nonce {
return nil, fmt.Errorf("incorrect nonce supplied; nonce for this rekey operation is %s", c.rekeyConfig.Nonce)
if nonce != c.barrierRekeyConfig.Nonce {
return nil, fmt.Errorf("incorrect nonce supplied; nonce for this rekey operation is %s", c.barrierRekeyConfig.Nonce)
}
// Check if we already have this piece
for _, existing := range c.rekeyProgress {
for _, existing := range c.barrierRekeyProgress {
if bytes.Equal(existing, key) {
return nil, nil
}
}
// Store this key
c.rekeyProgress = append(c.rekeyProgress, key)
c.barrierRekeyProgress = append(c.barrierRekeyProgress, key)
// Check if we don't have enough keys to unlock
if len(c.rekeyProgress) < config.SecretThreshold {
if len(c.barrierRekeyProgress) < existingConfig.SecretThreshold {
c.logger.Printf("[DEBUG] core: cannot rekey, have %d of %d keys",
len(c.rekeyProgress), config.SecretThreshold)
len(c.barrierRekeyProgress), existingConfig.SecretThreshold)
return nil, nil
}
// Recover the master key
var masterKey []byte
if config.SecretThreshold == 1 {
masterKey = c.rekeyProgress[0]
c.rekeyProgress = nil
if existingConfig.SecretThreshold == 1 {
masterKey = c.barrierRekeyProgress[0]
c.barrierRekeyProgress = nil
} else {
masterKey, err = shamir.Combine(c.rekeyProgress)
c.rekeyProgress = nil
masterKey, err = shamir.Combine(c.barrierRekeyProgress)
c.barrierRekeyProgress = nil
if err != nil {
return nil, fmt.Errorf("failed to compute master key: %v", err)
}
}
// Verify the master key
if err := c.barrier.VerifyMaster(masterKey); err != nil {
c.logger.Printf("[ERR] core: rekey aborted, master key verification failed: %v", err)
return nil, err
@ -194,14 +327,14 @@ func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
// Return the master key if only a single key part is used
results := &RekeyResult{
Backup: c.rekeyConfig.Backup,
Backup: c.barrierRekeyConfig.Backup,
}
if c.rekeyConfig.SecretShares == 1 {
if c.barrierRekeyConfig.SecretShares == 1 {
results.SecretShares = append(results.SecretShares, newMasterKey)
} else {
// Split the master key using the Shamir algorithm
shares, err := shamir.Split(newMasterKey, c.rekeyConfig.SecretShares, c.rekeyConfig.SecretThreshold)
shares, err := shamir.Split(newMasterKey, c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold)
if err != nil {
c.logger.Printf("[ERR] core: failed to generate shares: %v", err)
return nil, fmt.Errorf("failed to generate shares: %v", err)
@ -209,17 +342,27 @@ func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
results.SecretShares = shares
}
if len(c.rekeyConfig.PGPKeys) > 0 {
// If we are storing any shares, add them to the shares to store and remove
// from the returned keys
var keysToStore [][]byte
if c.barrierRekeyConfig.StoredShares > 0 {
for i := 0; i < c.barrierRekeyConfig.StoredShares; i++ {
keysToStore = append(keysToStore, results.SecretShares[0])
results.SecretShares = results.SecretShares[1:]
}
}
if len(c.barrierRekeyConfig.PGPKeys) > 0 {
hexEncodedShares := make([][]byte, len(results.SecretShares))
for i, _ := range results.SecretShares {
hexEncodedShares[i] = []byte(hex.EncodeToString(results.SecretShares[i]))
}
results.PGPFingerprints, results.SecretShares, err = pgpkeys.EncryptShares(hexEncodedShares, c.rekeyConfig.PGPKeys)
results.PGPFingerprints, results.SecretShares, err = pgpkeys.EncryptShares(hexEncodedShares, c.barrierRekeyConfig.PGPKeys)
if err != nil {
return nil, err
}
if c.rekeyConfig.Backup {
if c.barrierRekeyConfig.Backup {
backupInfo := map[string][]string{}
for i := 0; i < len(results.PGPFingerprints); i++ {
encShare := bytes.NewBuffer(results.SecretShares[i])
@ -231,7 +374,7 @@ func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
}
backupVals := &RekeyBackup{
Nonce: c.rekeyConfig.Nonce,
Nonce: c.barrierRekeyConfig.Nonce,
Keys: backupInfo,
}
buf, err := json.Marshal(backupVals)
@ -240,7 +383,7 @@ func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
return nil, fmt.Errorf("failed to marshal unseal key backup: %v", err)
}
pe := &physical.Entry{
Key: coreUnsealKeysBackupPath,
Key: coreBarrierUnsealKeysBackupPath,
Value: buf,
}
if err = c.physical.Put(pe); err != nil {
@ -250,10 +393,11 @@ func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
}
}
// Encode the seal configuration
buf, err := json.Marshal(c.rekeyConfig)
if err != nil {
return nil, fmt.Errorf("failed to encode seal configuration: %v", err)
if keysToStore != nil {
if err := c.seal.SetStoredKeys(keysToStore); err != nil {
c.logger.Printf("[ERR] core: failed to store keys: %v", err)
return nil, fmt.Errorf("failed to store keys: %v", err)
}
}
// Rekey the barrier
@ -262,44 +406,22 @@ func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
return nil, fmt.Errorf("failed to rekey barrier: %v", err)
}
c.logger.Printf("[INFO] core: security barrier rekeyed (shares: %d, threshold: %d)",
c.rekeyConfig.SecretShares, c.rekeyConfig.SecretThreshold)
c.barrierRekeyConfig.SecretShares, c.barrierRekeyConfig.SecretThreshold)
// Store the seal configuration
pe := &physical.Entry{
Key: coreSealConfigPath,
Value: buf,
}
if err := c.physical.Put(pe); err != nil {
c.logger.Printf("[ERR] core: failed to update seal configuration: %v", err)
return nil, fmt.Errorf("failed to update seal configuration: %v", err)
if err := c.seal.SetBarrierConfig(c.barrierRekeyConfig); err != nil {
c.logger.Printf("[ERR] core: error saving rekey seal configuration: %v", err)
return nil, fmt.Errorf("failed to save rekey seal configuration: %v", err)
}
// Done!
c.rekeyProgress = nil
c.rekeyConfig = nil
c.barrierRekeyProgress = nil
c.barrierRekeyConfig = nil
return results, nil
}
// RekeyCancel is used to cancel an inprogress rekey
func (c *Core) RekeyCancel() error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
}
if c.standby {
return ErrStandby
}
// Clear any progress or config
c.rekeyConfig = nil
c.rekeyProgress = nil
return nil
}
// RekeyRetrieveBackup is used to retrieve any backed-up PGP-encrypted unseal
// keys
func (c *Core) RekeyRetrieveBackup() (*RekeyBackup, error) {
// RecoveryRekeyUpdate is used to provide a new key part
func (c *Core) RecoveryRekeyUpdate(key []byte, nonce string) (*RekeyResult, error) {
// Ensure we are already unsealed
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
@ -309,7 +431,208 @@ func (c *Core) RekeyRetrieveBackup() (*RekeyBackup, error) {
return nil, ErrStandby
}
entry, err := c.physical.Get(coreUnsealKeysBackupPath)
// Verify the key length
min, max := c.barrier.KeyLength()
max += shamir.ShareOverhead
if len(key) < min {
return nil, &ErrInvalidKey{fmt.Sprintf("key is shorter than minimum %d bytes", min)}
}
if len(key) > max {
return nil, &ErrInvalidKey{fmt.Sprintf("key is longer than maximum %d bytes", max)}
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Get the seal configuration
barrierConfig, err := c.seal.BarrierConfig()
if err != nil {
return nil, err
}
// Ensure the barrier is initialized
if barrierConfig == nil {
return nil, ErrNotInit
}
existingConfig, err := c.seal.RecoveryConfig()
if err != nil {
return nil, err
}
// Ensure a rekey is in progress
if c.recoveryRekeyConfig == nil {
return nil, fmt.Errorf("no rekey in progress")
}
if nonce != c.recoveryRekeyConfig.Nonce {
return nil, fmt.Errorf("incorrect nonce supplied; nonce for this rekey operation is %s", c.recoveryRekeyConfig.Nonce)
}
// Check if we already have this piece
for _, existing := range c.recoveryRekeyProgress {
if bytes.Equal(existing, key) {
return nil, nil
}
}
// Store this key
c.recoveryRekeyProgress = append(c.recoveryRekeyProgress, key)
// Check if we don't have enough keys to unlock
if len(c.recoveryRekeyProgress) < existingConfig.SecretThreshold {
c.logger.Printf("[DEBUG] core: cannot rekey, have %d of %d keys",
len(c.recoveryRekeyProgress), existingConfig.SecretThreshold)
return nil, nil
}
// Recover the master key
var masterKey []byte
if existingConfig.SecretThreshold == 1 {
masterKey = c.recoveryRekeyProgress[0]
c.recoveryRekeyProgress = nil
} else {
masterKey, err = shamir.Combine(c.recoveryRekeyProgress)
c.recoveryRekeyProgress = nil
if err != nil {
return nil, fmt.Errorf("failed to compute recovery key: %v", err)
}
}
// Verify the recovery key
if err := c.seal.VerifyRecoveryKey(masterKey); err != nil {
c.logger.Printf("[ERR] core: rekey aborted, recovery key verification failed: %v", err)
return nil, err
}
// Generate a new master key
newMasterKey, err := c.barrier.GenerateKey()
if err != nil {
c.logger.Printf("[ERR] core: failed to generate recovery key: %v", err)
return nil, fmt.Errorf("recovery key generation failed: %v", err)
}
// Return the master key if only a single key part is used
results := &RekeyResult{
Backup: c.recoveryRekeyConfig.Backup,
}
if c.recoveryRekeyConfig.SecretShares == 1 {
results.SecretShares = append(results.SecretShares, newMasterKey)
} else {
// Split the master key using the Shamir algorithm
shares, err := shamir.Split(newMasterKey, c.recoveryRekeyConfig.SecretShares, c.recoveryRekeyConfig.SecretThreshold)
if err != nil {
c.logger.Printf("[ERR] core: failed to generate shares: %v", err)
return nil, fmt.Errorf("failed to generate shares: %v", err)
}
results.SecretShares = shares
}
if len(c.recoveryRekeyConfig.PGPKeys) > 0 {
hexEncodedShares := make([][]byte, len(results.SecretShares))
for i, _ := range results.SecretShares {
hexEncodedShares[i] = []byte(hex.EncodeToString(results.SecretShares[i]))
}
results.PGPFingerprints, results.SecretShares, err = pgpkeys.EncryptShares(hexEncodedShares, c.recoveryRekeyConfig.PGPKeys)
if err != nil {
return nil, err
}
if c.recoveryRekeyConfig.Backup {
backupInfo := map[string][]string{}
for i := 0; i < len(results.PGPFingerprints); i++ {
encShare := bytes.NewBuffer(results.SecretShares[i])
if backupInfo[results.PGPFingerprints[i]] == nil {
backupInfo[results.PGPFingerprints[i]] = []string{hex.EncodeToString(encShare.Bytes())}
} else {
backupInfo[results.PGPFingerprints[i]] = append(backupInfo[results.PGPFingerprints[i]], hex.EncodeToString(encShare.Bytes()))
}
}
backupVals := &RekeyBackup{
Nonce: c.recoveryRekeyConfig.Nonce,
Keys: backupInfo,
}
buf, err := json.Marshal(backupVals)
if err != nil {
c.logger.Printf("[ERR] core: failed to marshal recovery key backup: %v", err)
return nil, fmt.Errorf("failed to marshal recovery key backup: %v", err)
}
pe := &physical.Entry{
Key: coreRecoveryUnsealKeysBackupPath,
Value: buf,
}
if err = c.physical.Put(pe); err != nil {
c.logger.Printf("[ERR] core: failed to save unseal key backup: %v", err)
return nil, fmt.Errorf("failed to save unseal key backup: %v", err)
}
}
}
if err := c.seal.SetRecoveryKey(newMasterKey); err != nil {
c.logger.Printf("[ERR] core: failed to set recovery key: %v", err)
return nil, fmt.Errorf("failed to set recovery key: %v", err)
}
if err := c.seal.SetRecoveryConfig(c.recoveryRekeyConfig); err != nil {
c.logger.Printf("[ERR] core: error saving rekey seal configuration: %v", err)
return nil, fmt.Errorf("failed to save rekey seal configuration: %v", err)
}
// Done!
c.recoveryRekeyProgress = nil
c.recoveryRekeyConfig = nil
return results, nil
}
// RekeyCancel is used to cancel an inprogress rekey
func (c *Core) RekeyCancel(recovery bool) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return ErrSealed
}
if c.standby {
return ErrStandby
}
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
// Clear any progress or config
if recovery {
c.recoveryRekeyConfig = nil
c.recoveryRekeyProgress = nil
} else {
c.barrierRekeyConfig = nil
c.barrierRekeyProgress = nil
}
return nil
}
// RekeyRetrieveBackup is used to retrieve any backed-up PGP-encrypted unseal
// keys
func (c *Core) RekeyRetrieveBackup(recovery bool) (*RekeyBackup, error) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return nil, ErrSealed
}
if c.standby {
return nil, ErrStandby
}
c.rekeyLock.RLock()
defer c.rekeyLock.RUnlock()
var entry *physical.Entry
var err error
if recovery {
entry, err = c.physical.Get(coreRecoveryUnsealKeysBackupPath)
} else {
entry, err = c.physical.Get(coreBarrierUnsealKeysBackupPath)
}
if err != nil {
return nil, err
}
@ -327,7 +650,7 @@ func (c *Core) RekeyRetrieveBackup() (*RekeyBackup, error) {
}
// RekeyDeleteBackup is used to delete any backed-up PGP-encrypted unseal keys
func (c *Core) RekeyDeleteBackup() error {
func (c *Core) RekeyDeleteBackup(recovery bool) error {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
@ -337,5 +660,11 @@ func (c *Core) RekeyDeleteBackup() error {
return ErrStandby
}
return c.physical.Delete(coreUnsealKeysBackupPath)
c.rekeyLock.Lock()
defer c.rekeyLock.Unlock()
if recovery {
return c.physical.Delete(coreRecoveryUnsealKeysBackupPath)
}
return c.physical.Delete(coreBarrierUnsealKeysBackupPath)
}

View File

@ -3,18 +3,20 @@ package vault
import (
"reflect"
"testing"
"github.com/hashicorp/vault/physical"
)
func TestCore_Rekey_Lifecycle(t *testing.T) {
c, master, _ := TestCoreUnsealed(t)
// Verify update not allowed
if _, err := c.RekeyUpdate(master, ""); err == nil {
if _, err := c.RekeyUpdate(master, "", false); err == nil {
t.Fatalf("no rekey in progress")
}
// Should be no progress
num, err := c.RekeyProgress()
num, err := c.RekeyProgress(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -23,7 +25,7 @@ func TestCore_Rekey_Lifecycle(t *testing.T) {
}
// Should be no config
conf, err := c.RekeyConfig()
conf, err := c.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -32,7 +34,7 @@ func TestCore_Rekey_Lifecycle(t *testing.T) {
}
// Cancel should be idempotent
err = c.RekeyCancel()
err = c.RekeyCancel(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -42,13 +44,13 @@ func TestCore_Rekey_Lifecycle(t *testing.T) {
SecretThreshold: 3,
SecretShares: 5,
}
err = c.RekeyInit(newConf)
err = c.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should get config
conf, err = c.RekeyConfig()
conf, err = c.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -58,13 +60,13 @@ func TestCore_Rekey_Lifecycle(t *testing.T) {
}
// Cancel should be clear
err = c.RekeyCancel()
err = c.RekeyCancel(false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should be no config
conf, err = c.RekeyConfig()
conf, err = c.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -81,7 +83,7 @@ func TestCore_Rekey_Init(t *testing.T) {
SecretThreshold: 5,
SecretShares: 1,
}
err := c.RekeyInit(badConf)
err := c.RekeyInit(badConf, false)
if err == nil {
t.Fatalf("should fail")
}
@ -91,13 +93,13 @@ func TestCore_Rekey_Init(t *testing.T) {
SecretThreshold: 3,
SecretShares: 5,
}
err = c.RekeyInit(newConf)
err = c.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Second should fail
err = c.RekeyInit(newConf)
err = c.RekeyInit(newConf, false)
if err == nil {
t.Fatalf("should fail")
}
@ -111,13 +113,13 @@ func TestCore_Rekey_Update(t *testing.T) {
SecretThreshold: 3,
SecretShares: 5,
}
err := c.RekeyInit(newConf)
err := c.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err := c.RekeyConfig()
rkconf, err := c.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -126,7 +128,7 @@ func TestCore_Rekey_Update(t *testing.T) {
}
// Provide the master
result, err := c.RekeyUpdate(master, rkconf.Nonce)
result, err := c.RekeyUpdate(master, rkconf.Nonce, false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -135,7 +137,7 @@ func TestCore_Rekey_Update(t *testing.T) {
}
// Should be no progress
num, err := c.RekeyProgress()
num, err := c.RekeyProgress(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -144,7 +146,7 @@ func TestCore_Rekey_Update(t *testing.T) {
}
// Should be no config
conf, err := c.RekeyConfig()
conf, err := c.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -153,14 +155,17 @@ func TestCore_Rekey_Update(t *testing.T) {
}
// SealConfig should update
conf, err = c.SealConfig()
sealConf, err := c.seal.BarrierConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
if sealConf == nil {
t.Fatal("seal configuration is nil")
}
newConf.Nonce = rkconf.Nonce
if !reflect.DeepEqual(conf, newConf) {
t.Fatalf("\nexpected: %#v\nactual: %#v\n", conf, newConf)
if !reflect.DeepEqual(sealConf, newConf) {
t.Fatalf("\nexpected: %#v\nactual: %#v\n", sealConf, newConf)
}
// Attempt unseal
@ -183,13 +188,13 @@ func TestCore_Rekey_Update(t *testing.T) {
SecretThreshold: 1,
SecretShares: 1,
}
err = c.RekeyInit(newConf)
err = c.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err = c.RekeyConfig()
rkconf, err = c.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -200,13 +205,13 @@ func TestCore_Rekey_Update(t *testing.T) {
// Provide the parts master
oldResult := result
for i := 0; i < 3; i++ {
result, err = c.RekeyUpdate(oldResult.SecretShares[i], rkconf.Nonce)
result, err = c.RekeyUpdate(oldResult.SecretShares[i], rkconf.Nonce, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Should be progress
num, err := c.RekeyProgress()
num, err := c.RekeyProgress(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -232,13 +237,14 @@ func TestCore_Rekey_Update(t *testing.T) {
}
// SealConfig should update
conf, err = c.SealConfig()
sealConf, err = c.seal.BarrierConfig()
if err != nil {
t.Fatalf("err: %v", err)
}
newConf.Nonce = rkconf.Nonce
if !reflect.DeepEqual(conf, newConf) {
t.Fatalf("bad: %#v", conf)
if !reflect.DeepEqual(sealConf, newConf) {
t.Fatalf("bad: %#v", sealConf)
}
}
@ -250,13 +256,13 @@ func TestCore_Rekey_InvalidMaster(t *testing.T) {
SecretThreshold: 3,
SecretShares: 5,
}
err := c.RekeyInit(newConf)
err := c.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err := c.RekeyConfig()
rkconf, err := c.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -266,7 +272,7 @@ func TestCore_Rekey_InvalidMaster(t *testing.T) {
// Provide the master (invalid)
master[0]++
_, err = c.RekeyUpdate(master, rkconf.Nonce)
_, err = c.RekeyUpdate(master, rkconf.Nonce, false)
if err == nil {
t.Fatalf("expected error")
}
@ -280,14 +286,107 @@ func TestCore_Rekey_InvalidNonce(t *testing.T) {
SecretThreshold: 3,
SecretShares: 5,
}
err := c.RekeyInit(newConf)
err := c.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Provide the nonce (invalid)
_, err = c.RekeyUpdate(master, "abcd")
_, err = c.RekeyUpdate(master, "abcd", false)
if err == nil {
t.Fatalf("expected error")
}
}
func TestCore_Standby_Rekey(t *testing.T) {
// Create the first core and initialize it
inm := physical.NewInmem()
inmha := physical.NewInmemHA()
advertiseOriginal := "http://127.0.0.1:8200"
core, err := NewCore(&CoreConfig{
Physical: inm,
HAPhysical: inmha,
AdvertiseAddr: advertiseOriginal,
DisableMlock: true,
})
if err != nil {
t.Fatalf("err: %v", err)
}
key, root := TestCoreInit(t, core)
if _, err := core.Unseal(TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
// Wait for core to become active
testWaitActive(t, core)
// Create a second core, attached to same in-memory store
advertiseOriginal2 := "http://127.0.0.1:8500"
core2, err := NewCore(&CoreConfig{
Physical: inm,
HAPhysical: inmha,
AdvertiseAddr: advertiseOriginal2,
DisableMlock: true,
})
if err != nil {
t.Fatalf("err: %v", err)
}
if _, err := core2.Unseal(TestKeyCopy(key)); err != nil {
t.Fatalf("unseal err: %s", err)
}
// Rekey the master key
newConf := &SealConfig{
SecretShares: 1,
SecretThreshold: 1,
}
err = core.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err := core.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
result, err := core.RekeyUpdate(key, rkconf.Nonce, false)
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("rekey failed")
}
// Seal the first core, should step down
err = core.Seal(root)
if err != nil {
t.Fatalf("err: %v", err)
}
// Wait for core2 to become active
testWaitActive(t, core2)
// Rekey the master key again
err = core2.RekeyInit(newConf, false)
if err != nil {
t.Fatalf("err: %v", err)
}
// Fetch new config with generated nonce
rkconf, err = core2.RekeyConfig(false)
if err != nil {
t.Fatalf("err: %v", err)
}
if rkconf == nil {
t.Fatalf("bad: no rekey config received")
}
result, err = core2.RekeyUpdate(result.SecretShares[0], rkconf.Nonce, false)
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("rekey failed")
}
}

274
vault/seal.go Normal file
View File

@ -0,0 +1,274 @@
package vault
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/hashicorp/vault/physical"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
const (
// barrierSealConfigPath is the path used to store our seal configuration.
// This value is stored in plaintext, since we must be able to read it even
// with the Vault sealed. This is required so that we know how many secret
// parts must be used to reconstruct the master key.
barrierSealConfigPath = "core/seal-config"
// recoverySealConfigPath is the path to the recovery key seal
// configuration. It is inside the barrier.
recoverySealConfigPath = "core/recovery-seal-config"
// recoveryKeyPath is the path to the recovery key
recoveryKeyPath = "core/recovery-key"
)
type Seal interface {
SetCore(*Core)
Init() error
StoredKeysSupported() bool
SetStoredKeys([][]byte) error
GetStoredKeys() ([][]byte, error)
BarrierConfig() (*SealConfig, error)
SetBarrierConfig(*SealConfig) error
RecoveryKeySupported() bool
RecoveryConfig() (*SealConfig, error)
SetRecoveryConfig(*SealConfig) error
SetRecoveryKey([]byte) error
VerifyRecoveryKey([]byte) error
}
type DefaultSeal struct {
config *SealConfig
core *Core
}
func (d *DefaultSeal) checkCore() error {
if d.core == nil {
return fmt.Errorf("seal does not have a core set")
}
return nil
}
func (d *DefaultSeal) SetCore(core *Core) {
d.core = core
}
func (d *DefaultSeal) Init() error {
return nil
}
func (d *DefaultSeal) StoredKeysSupported() bool {
return false
}
func (d *DefaultSeal) RecoveryKeySupported() bool {
return false
}
func (d *DefaultSeal) SetStoredKeys(keys [][]byte) error {
return fmt.Errorf("[ERR] core: stored keys are not supported")
}
func (d *DefaultSeal) GetStoredKeys() ([][]byte, error) {
return nil, fmt.Errorf("[ERR] core: stored keys are not supported")
}
func (d *DefaultSeal) BarrierConfig() (*SealConfig, error) {
if d.config != nil {
return d.config.Clone(), nil
}
if err := d.checkCore(); err != nil {
return nil, err
}
// Fetch the core configuration
pe, err := d.core.physical.Get(barrierSealConfigPath)
if err != nil {
d.core.logger.Printf("[ERR] core: failed to read seal configuration: %v", err)
return nil, fmt.Errorf("failed to check seal configuration: %v", err)
}
// If the seal configuration is missing, we are not initialized
if pe == nil {
d.core.logger.Printf("[INFO] core: seal configuration missing, not initialized")
return nil, nil
}
var conf SealConfig
// Decode the barrier entry
if err := json.Unmarshal(pe.Value, &conf); err != nil {
d.core.logger.Printf("[ERR] core: failed to decode seal configuration: %v", err)
return nil, fmt.Errorf("failed to decode seal configuration: %v", err)
}
// Check for a valid seal configuration
if err := conf.Validate(); err != nil {
d.core.logger.Printf("[ERR] core: invalid seal configuration: %v", err)
return nil, fmt.Errorf("seal validation failed: %v", err)
}
d.config = &conf
return d.config.Clone(), nil
}
func (d *DefaultSeal) SetBarrierConfig(config *SealConfig) error {
if err := d.checkCore(); err != nil {
return err
}
// Encode the seal configuration
buf, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("failed to encode seal configuration: %v", err)
}
// Store the seal configuration
pe := &physical.Entry{
Key: barrierSealConfigPath,
Value: buf,
}
if err := d.core.physical.Put(pe); err != nil {
d.core.logger.Printf("[ERR] core: failed to write seal configuration: %v", err)
return fmt.Errorf("failed to write seal configuration: %v", err)
}
d.config = config.Clone()
return nil
}
func (d *DefaultSeal) RecoveryConfig() (*SealConfig, error) {
return nil, fmt.Errorf("recovery not supported")
}
func (d *DefaultSeal) SetRecoveryConfig(config *SealConfig) error {
return fmt.Errorf("recovery not supported")
}
func (d *DefaultSeal) VerifyRecoveryKey([]byte) error {
return fmt.Errorf("recovery not supported")
}
func (d *DefaultSeal) SetRecoveryKey(key []byte) error {
return fmt.Errorf("recovery not supported")
}
// SealConfig is used to describe the seal configuration
type SealConfig struct {
// SecretShares is the number of shares the secret is split into. This is
// the N value of Shamir.
SecretShares int `json:"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"`
// 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"`
// 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"`
// Backup indicates whether or not a backup of PGP-encrypted unseal keys
// should be stored at coreUnsealKeysBackupPath after successful rekeying.
Backup bool `json:"backup"`
// How many keys to store, for seals that support storage.
StoredShares int `json:"stored_shares"`
}
// 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 > s.SecretShares {
return fmt.Errorf("stored keys cannot be larger than shares")
}
if len(s.PGPKeys) > 0 && len(s.PGPKeys) != s.SecretShares-s.StoredShares {
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: %s", err)
}
_, err = openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(data)))
if err != nil {
return fmt.Errorf("Error parsing given PGP key: %s", err)
}
}
}
return nil
}
func (s *SealConfig) Clone() *SealConfig {
ret := &SealConfig{
SecretShares: s.SecretShares,
SecretThreshold: s.SecretThreshold,
Nonce: s.Nonce,
Backup: s.Backup,
StoredShares: s.StoredShares,
}
if len(s.PGPKeys) > 0 {
ret.PGPKeys = make([]string, len(s.PGPKeys))
copy(ret.PGPKeys, s.PGPKeys)
}
return ret
}
type SealAccess struct {
seal Seal
}
func (s *SealAccess) SetSeal(seal Seal) {
s.seal = seal
}
func (s *SealAccess) StoredKeysSupported() bool {
return s.seal.StoredKeysSupported()
}
func (s *SealAccess) BarrierConfig() (*SealConfig, error) {
return s.seal.BarrierConfig()
}
func (s *SealAccess) RecoveryKeySupported() bool {
return s.seal.RecoveryKeySupported()
}
func (s *SealAccess) RecoveryConfig() (*SealConfig, error) {
return s.seal.RecoveryConfig()
}

View File

@ -121,7 +121,7 @@ func TestCoreInit(t *testing.T, core *Core) ([]byte, string) {
result, err := core.Initialize(&SealConfig{
SecretShares: 1,
SecretThreshold: 1,
})
}, nil)
if err != nil {
t.Fatalf("err: %s", err)
}