Add reset support to the unseal command.

Reset clears the provided unseal keys, allowing the process to be begun
again. Includes documentation and unit test changes.

Fixes #695
This commit is contained in:
Jeff Mitchell 2015-10-28 15:59:39 -04:00
parent 40486da446
commit d7f528a768
7 changed files with 158 additions and 54 deletions

View File

@ -11,6 +11,8 @@ IMPROVEMENTS:
* api: API client now uses a 30 second timeout instead of indefinite [GH-681]
* core: The physical storage read cache can now be disabled via
"disable_cache" [GH-674]
* core: The unsealing process can now be reset midway through (this feature
was documented before, but not enabled) [GH-695]
* core: Tokens can now renew themselves [GH-455]
* core: Base64-encoded PGP keys can be used with the CLI for `init` and
`rekey` operations [GH-653]

View File

@ -2,15 +2,7 @@ package api
func (c *Sys) SealStatus() (*SealStatusResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/seal-status")
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result SealStatusResponse
err = resp.DecodeJSON(&result)
return &result, err
return sealStatusRequest(c, r)
}
func (c *Sys) Seal() error {
@ -22,6 +14,17 @@ func (c *Sys) Seal() error {
return err
}
func (c *Sys) ResetUnsealProcess() (*SealStatusResponse, error) {
body := map[string]interface{}{"reset": true}
r := c.c.NewRequest("PUT", "/v1/sys/unseal")
if err := r.SetJSONBody(body); err != nil {
return nil, err
}
return sealStatusRequest(c, r)
}
func (c *Sys) Unseal(shard string) (*SealStatusResponse, error) {
body := map[string]interface{}{"key": shard}
@ -30,6 +33,10 @@ func (c *Sys) Unseal(shard string) (*SealStatusResponse, error) {
return nil, err
}
return sealStatusRequest(c, r)
}
func sealStatusRequest(c *Sys, r *Request) (*SealStatusResponse, error) {
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err

View File

@ -46,33 +46,36 @@ func (c *UnsealCommand) Run(args []string) int {
}
args = flags.Args()
value := c.Key
if len(args) > 0 {
value = args[0]
}
if value == "" {
fmt.Printf("Key (will be hidden): ")
value, err = password.Read(os.Stdin)
fmt.Printf("\n")
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error attempting to ask for password. The raw error message\n"+
"is shown below, but the most common reason for this error is\n"+
"that you attempted to pipe a value into unseal or you're\n"+
"executing `vault unseal` from outside of a terminal.\n\n"+
"You should use `vault unseal` from a terminal for maximum\n"+
"security. If this isn't an option, the unseal key can be passed\n"+
"in using the first parameter.\n\n"+
"Raw error: %s", err))
return 1
if reset {
sealStatus, err = client.Sys().ResetUnsealProcess()
} else {
value := c.Key
if len(args) > 0 {
value = args[0]
}
if value == "" {
fmt.Printf("Key (will be hidden): ")
value, err = password.Read(os.Stdin)
fmt.Printf("\n")
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error attempting to ask for password. The raw error message\n"+
"is shown below, but the most common reason for this error is\n"+
"that you attempted to pipe a value into unseal or you're\n"+
"executing `vault unseal` from outside of a terminal.\n\n"+
"You should use `vault unseal` from a terminal for maximum\n"+
"security. If this isn't an option, the unseal key can be passed\n"+
"in using the first parameter.\n\n"+
"Raw error: %s", err))
return 1
}
}
sealStatus, err = client.Sys().Unseal(strings.TrimSpace(value))
}
status, err := client.Sys().Unseal(strings.TrimSpace(value))
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error attempting unseal: %s", err))
"Error: %s", err))
return 1
}
@ -81,10 +84,10 @@ func (c *UnsealCommand) Run(args []string) int {
"Key Shares: %d\n"+
"Key Threshold: %d\n"+
"Unseal Progress: %d",
status.Sealed,
status.N,
status.T,
status.Progress,
sealStatus.Sealed,
sealStatus.N,
sealStatus.T,
sealStatus.Progress,
))
return 0

View File

@ -50,30 +50,43 @@ func handleSysUnseal(core *vault.Core) http.Handler {
respondError(w, http.StatusBadRequest, err)
return
}
if req.Key == "" {
if !req.Reset && req.Key == "" {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must specified in request body as JSON"))
errors.New("'key' must specified in request body as JSON, or 'reset' set to true"))
return
}
// Decode the key, which is hex encoded
key, err := hex.DecodeString(req.Key)
if err != nil {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be a valid hex-string"))
return
}
// Attempt the unseal
if _, err := core.Unseal(key); err != nil {
// Ignore ErrInvalidKey because its a user error that we
// mask away. We just show them the seal status.
if !errwrap.ContainsType(err, new(vault.ErrInvalidKey)) {
if req.Reset {
sealed, err := core.Sealed()
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
}
if !sealed {
respondError(w, http.StatusBadRequest, errors.New("vault is unsealed"))
return
}
core.ResetUnsealProcess()
} else {
// Decode the key, which is hex encoded
key, err := hex.DecodeString(req.Key)
if err != nil {
respondError(
w, http.StatusBadRequest,
errors.New("'key' must be a valid hex-string"))
return
}
// Attempt the unseal
if _, err := core.Unseal(key); err != nil {
// Ignore ErrInvalidKey because its a user error that we
// mask away. We just show them the seal status.
if !errwrap.ContainsType(err, new(vault.ErrInvalidKey)) {
respondError(w, http.StatusInternalServerError, err)
return
}
}
}
// Return the seal status
@ -126,5 +139,6 @@ type SealStatusResponse struct {
}
type UnsealRequest struct {
Key string
Key string
Reset bool
}

View File

@ -129,3 +129,62 @@ func TestSysUnseal_badKey(t *testing.T) {
t.Fatalf("bad: %#v", actual)
}
}
func TestSysUnseal_Reset(t *testing.T) {
core := vault.TestCore(t)
ln, addr := TestServer(t, core)
defer ln.Close()
thresh := 3
resp := testHttpPut(t, "", addr+"/v1/sys/init", map[string]interface{}{
"secret_shares": 5,
"secret_threshold": thresh,
})
var actual map[string]interface{}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
keysRaw, ok := actual["keys"]
if !ok {
t.Fatalf("no keys: %#v", actual)
}
for i, key := range keysRaw.([]interface{}) {
if i > thresh-2 {
break
}
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"key": key.(string),
})
var actual map[string]interface{}
expected := map[string]interface{}{
"sealed": true,
"t": float64(3),
"n": float64(5),
"progress": float64(i + 1),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected:\n%#v\nactual:\n%#v\n", expected, actual)
}
}
resp = testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
"reset": true,
})
actual = map[string]interface{}{}
expected := map[string]interface{}{
"sealed": true,
"t": float64(3),
"n": float64(5),
"progress": float64(0),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("\nexpected:\n%#v\nactual:\n%#v\n", expected, actual)
}
}

View File

@ -957,6 +957,17 @@ func (c *Core) SecretProgress() int {
return len(c.unlockParts)
}
// ResetUnsealProcess removes the current unlock parts from memory, to reset
// the unsealing process
func (c *Core) ResetUnsealProcess() {
c.stateLock.Lock()
defer c.stateLock.Unlock()
if !c.sealed {
return
}
c.unlockParts = nil
}
// Unseal is used to provide one of the key parts to unseal the Vault.
//
// They key given as a parameter will automatically be zerod after

View File

@ -14,7 +14,9 @@ description: |-
Enter a single master key share to progress the unsealing of the Vault.
If the threshold number of master key shares is reached, Vault
will attempt to unseal the Vault. Otherwise, this API must be
called multiple times until that threshold is met.
called multiple times until that threshold is met.<br/><br/>Either
the `key` or `reset` parameter must be provided; if both are provided,
`reset` takes precedence.
</dd>
<dt>Method</dt>
@ -25,9 +27,15 @@ description: |-
<ul>
<li>
<span class="param">key</span>
<span class="param-flags">required</span>
<span class="param-flags">optional</span>
A single master share key.
</li>
<li>
<span class="param">reset</span>
<span class="param-flags">optional</span>
A boolean; if true, the previously-provided unseal keys are discarded
from memory and the unseal process is reset.
</li>
</ul>
</dd>
<dt>Returns</dt>