diff --git a/api/sys_seal.go b/api/sys_seal.go index b80e33a940..97a49aeb44 100644 --- a/api/sys_seal.go +++ b/api/sys_seal.go @@ -53,6 +53,7 @@ type SealStatusResponse struct { T int `json:"t"` N int `json:"n"` Progress int `json:"progress"` + Nonce string `json:"nonce"` Version string `json:"version"` ClusterName string `json:"cluster_name,omitempty"` ClusterID string `json:"cluster_id,omitempty"` diff --git a/command/status.go b/command/status.go index 6babe27c76..f413f88781 100644 --- a/command/status.go +++ b/command/status.go @@ -40,11 +40,13 @@ func (c *StatusCommand) Run(args []string) int { "Key Shares: %d\n"+ "Key Threshold: %d\n"+ "Unseal Progress: %d\n"+ + "Unseal Nonce: %v"+ "Version: %s", sealStatus.Sealed, sealStatus.N, sealStatus.T, sealStatus.Progress, + sealStatus.Nonce, sealStatus.Version) if sealStatus.ClusterName != "" && sealStatus.ClusterID != "" { diff --git a/command/unseal.go b/command/unseal.go index ae56182335..722d6b0005 100644 --- a/command/unseal.go +++ b/command/unseal.go @@ -84,11 +84,13 @@ func (c *UnsealCommand) Run(args []string) int { "Sealed: %v\n"+ "Key Shares: %d\n"+ "Key Threshold: %d\n"+ - "Unseal Progress: %d", + "Unseal Progress: %d\n"+ + "Unseal Nonce: %v", sealStatus.Sealed, sealStatus.N, sealStatus.T, sealStatus.Progress, + sealStatus.Nonce, )) return 0 diff --git a/http/sys_seal.go b/http/sys_seal.go index eabf6f0752..27e3434ece 100644 --- a/http/sys_seal.go +++ b/http/sys_seal.go @@ -186,11 +186,14 @@ func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Req clusterID = cluster.ID } + progress, nonce := core.SecretProgress() + respondOk(w, &SealStatusResponse{ Sealed: sealed, T: sealConfig.SecretThreshold, N: sealConfig.SecretShares, - Progress: core.SecretProgress(), + Progress: progress, + Nonce: nonce, Version: version.GetVersion().VersionNumber(), ClusterName: clusterName, ClusterID: clusterID, @@ -202,6 +205,7 @@ type SealStatusResponse struct { T int `json:"t"` N int `json:"n"` Progress int `json:"progress"` + Nonce string `json:"nonce"` Version string `json:"version"` ClusterName string `json:"cluster_name,omitempty"` ClusterID string `json:"cluster_id,omitempty"` diff --git a/http/sys_seal_test.go b/http/sys_seal_test.go index 83fd3198b5..86651435ab 100644 --- a/http/sys_seal_test.go +++ b/http/sys_seal_test.go @@ -29,6 +29,7 @@ func TestSysSealStatus(t *testing.T) { "t": json.Number("1"), "n": json.Number("1"), "progress": json.Number("0"), + "nonce": "", } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) @@ -115,6 +116,7 @@ func TestSysUnseal(t *testing.T) { "t": json.Number("1"), "n": json.Number("1"), "progress": json.Number("0"), + "nonce": "", } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) @@ -189,6 +191,10 @@ func TestSysUnseal_Reset(t *testing.T) { t.Fatalf("expected version information") } expected["version"] = actual["version"] + if actual["nonce"] == "" && expected["sealed"].(bool) { + t.Fatalf("expected a nonce") + } + expected["nonce"] = actual["nonce"] if actual["cluster_name"] == nil { delete(expected, "cluster_name") } else { @@ -221,6 +227,7 @@ func TestSysUnseal_Reset(t *testing.T) { t.Fatalf("expected version information") } expected["version"] = actual["version"] + expected["nonce"] = actual["nonce"] if actual["cluster_name"] == nil { delete(expected, "cluster_name") } else { diff --git a/vault/core.go b/vault/core.go index 048080d994..f9e4b8159e 100644 --- a/vault/core.go +++ b/vault/core.go @@ -124,6 +124,11 @@ type activeAdvertisement struct { ClusterKeyParams *clusterKeyParams `json:"cluster_key_params,omitempty"` } +type unlockInformation struct { + Parts [][]byte + Nonce string +} + // Core is used as the central manager of Vault activity. It is the primary point of // interface for API handlers and is responsible for managing the logical and physical // backends, router, security barrier, and audit trails. @@ -167,9 +172,8 @@ type Core struct { standbyStopCh chan struct{} manualStepDownCh chan struct{} - // unlockParts has the keys provided to Unseal until - // the threshold number of parts is available. - unlockParts [][]byte + // unlockInfo has the keys provided to Unseal until the threshold number of parts is available, as well as the operation nonce + unlockInfo *unlockInformation // generateRootProgress holds the shares until we reach enough // to verify the master key @@ -735,10 +739,15 @@ func (c *Core) Leader() (isLeader bool, leaderAddr string, err error) { } // SecretProgress returns the number of keys provided so far -func (c *Core) SecretProgress() int { +func (c *Core) SecretProgress() (int, string) { c.stateLock.RLock() defer c.stateLock.RUnlock() - return len(c.unlockParts) + switch c.unlockInfo { + case nil: + return 0, "" + default: + return len(c.unlockInfo.Parts), c.unlockInfo.Nonce + } } // ResetUnsealProcess removes the current unlock parts from memory, to reset @@ -749,7 +758,7 @@ func (c *Core) ResetUnsealProcess() { if !c.sealed { return } - c.unlockParts = nil + c.unlockInfo = nil } // Unseal is used to provide one of the key parts to unseal the Vault. @@ -790,19 +799,29 @@ func (c *Core) Unseal(key []byte) (bool, error) { } // Check if we already have this piece - for _, existing := range c.unlockParts { - if bytes.Equal(existing, key) { - return false, nil + if c.unlockInfo != nil { + for _, existing := range c.unlockInfo.Parts { + if bytes.Equal(existing, key) { + return false, nil + } + } + } else { + uuid, err := uuid.GenerateUUID() + if err != nil { + return false, err + } + c.unlockInfo = &unlockInformation{ + Nonce: uuid, } } // Store this key - c.unlockParts = append(c.unlockParts, key) + c.unlockInfo.Parts = append(c.unlockInfo.Parts, key) // Check if we don't have enough keys to unlock - if len(c.unlockParts) < config.SecretThreshold { + if len(c.unlockInfo.Parts) < config.SecretThreshold { if c.logger.IsDebug() { - c.logger.Debug("core: cannot unseal, not enough keys", "keys", len(c.unlockParts), "threshold", config.SecretThreshold) + c.logger.Debug("core: cannot unseal, not enough keys", "keys", len(c.unlockInfo.Parts), "threshold", config.SecretThreshold, "nonce", c.unlockInfo.Nonce) } return false, nil } @@ -810,11 +829,11 @@ func (c *Core) Unseal(key []byte) (bool, error) { // Recover the master key var masterKey []byte if config.SecretThreshold == 1 { - masterKey = c.unlockParts[0] - c.unlockParts = nil + masterKey = c.unlockInfo.Parts[0] + c.unlockInfo = nil } else { - masterKey, err = shamir.Combine(c.unlockParts) - c.unlockParts = nil + masterKey, err = shamir.Combine(c.unlockInfo.Parts) + c.unlockInfo = nil if err != nil { return false, fmt.Errorf("failed to compute master key: %v", err) } diff --git a/vault/core_test.go b/vault/core_test.go index c52bc7b34f..5f53b38cf8 100644 --- a/vault/core_test.go +++ b/vault/core_test.go @@ -72,7 +72,7 @@ func TestCore_Unseal_MultiShare(t *testing.T) { t.Fatalf("should be sealed") } - if prog := c.SecretProgress(); prog != 0 { + if prog, _ := c.SecretProgress(); prog != 0 { t.Fatalf("bad progress: %d", prog) } @@ -91,14 +91,14 @@ func TestCore_Unseal_MultiShare(t *testing.T) { if !unseal { t.Fatalf("should be unsealed") } - if prog := c.SecretProgress(); prog != 0 { + if prog, _ := c.SecretProgress(); prog != 0 { t.Fatalf("bad progress: %d", prog) } } else { if unseal { t.Fatalf("should not be unsealed") } - if prog := c.SecretProgress(); prog != i+1 { + if prog, _ := c.SecretProgress(); prog != i+1 { t.Fatalf("bad progress: %d", prog) } } @@ -160,7 +160,7 @@ func TestCore_Unseal_Single(t *testing.T) { t.Fatalf("should be sealed") } - if prog := c.SecretProgress(); prog != 0 { + if prog, _ := c.SecretProgress(); prog != 0 { t.Fatalf("bad progress: %d", prog) } @@ -172,7 +172,7 @@ func TestCore_Unseal_Single(t *testing.T) { if !unseal { t.Fatalf("should be unsealed") } - if prog := c.SecretProgress(); prog != 0 { + if prog, _ := c.SecretProgress(); prog != 0 { t.Fatalf("bad progress: %d", prog) }