mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-04 20:06:27 +02:00
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:
parent
40486da446
commit
d7f528a768
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user