From a094eedce239d1a5cac843125325cbe7506dc4a6 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 16 Dec 2015 16:56:15 -0500 Subject: [PATCH] Add rekey nonce/backup. --- api/sys_rekey.go | 56 +++++++-- command/init_test.go | 13 +- command/pgp_test.go | 41 +++++- command/rekey.go | 139 +++++++++++++++++---- command/rekey_test.go | 63 +++++++++- helper/pgpkeys/encryptshares.go | 66 +++++++--- helper/pgpkeys/flag.go | 42 ++++--- http/handler.go | 1 + http/sys_rekey.go | 45 +++++-- http/sys_rekey_test.go | 92 ++++++++++---- vault/core.go | 125 ++++++++++++++++-- vault/core_test.go | 81 ++++++++++-- vault/logical_system.go | 49 ++++++++ website/source/docs/http/sys-rekey.html.md | 109 ++++++++++++++-- 14 files changed, 791 insertions(+), 131 deletions(-) diff --git a/api/sys_rekey.go b/api/sys_rekey.go index 4bfbf3805..c6ff20f34 100644 --- a/api/sys_rekey.go +++ b/api/sys_rekey.go @@ -35,8 +35,11 @@ func (c *Sys) RekeyCancel() error { return err } -func (c *Sys) RekeyUpdate(shard string) (*RekeyUpdateResponse, error) { - body := map[string]interface{}{"key": shard} +func (c *Sys) RekeyUpdate(shard, nonce string) (*RekeyUpdateResponse, error) { + body := map[string]interface{}{ + "key": shard, + "nonce": nonce, + } r := c.c.NewRequest("PUT", "/v1/sys/rekey/update") if err := r.SetJSONBody(body); err != nil { @@ -54,21 +57,56 @@ func (c *Sys) RekeyUpdate(shard string) (*RekeyUpdateResponse, error) { return &result, err } +func (c *Sys) RekeyRetrieveStored() (*RekeyRetrieveResponse, error) { + r := c.c.NewRequest("GET", "/v1/sys/rekey/stored") + 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) RekeyDeleteStored() error { + r := c.c.NewRequest("DELETE", "/v1/sys/rekey/stored") + 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"` PGPKeys []string `json:"pgp_keys"` + Backup bool } type RekeyStatusResponse struct { - Started bool - T int - N int - Progress int - Required int + Nonce string + Started bool + T int + N int + Progress int + Required int + PGPFingerprints []string `json:"pgp_fingerprints"` + Backup bool } type RekeyUpdateResponse struct { - Complete bool - Keys []string + Nonce string + Complete bool + Keys []string + PGPFingerprints []string `json:"pgp_fingerprints"` + Backup bool +} + +type RekeyRetrieveResponse struct { + Nonce string + Keys map[string]string } diff --git a/command/init_test.go b/command/init_test.go index e28769ecd..52c36037b 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -6,6 +6,7 @@ import ( "regexp" "testing" + "github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/vault" "github.com/mitchellh/cli" @@ -171,9 +172,19 @@ func TestInit_PGP(t *testing.T) { t.Fatalf("err: %s", err) } + pgpKeys := []string{} + for _, pubFile := range pubFiles { + pub, err := pgpkeys.ReadPGPFile(pubFile) + if err != nil { + t.Fatalf("bad: %v", err) + } + pgpKeys = append(pgpKeys, pub) + } + expected := &vault.SealConfig{ SecretShares: 3, SecretThreshold: 2, + PGPKeys: pgpKeys, } if !reflect.DeepEqual(expected, sealConf) { t.Fatalf("bad:\nexpected: %#v\ngot: %#v", expected, sealConf) @@ -190,5 +201,5 @@ func TestInit_PGP(t *testing.T) { rootToken := matches[0][1] - parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), rootToken, core) + parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), rootToken, false, nil, core) } diff --git a/command/pgp_test.go b/command/pgp_test.go index 77a7dd9ff..bc8d054b0 100644 --- a/command/pgp_test.go +++ b/command/pgp_test.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/hex" "io/ioutil" + "reflect" "regexp" "testing" @@ -54,7 +55,11 @@ func getPubKeyFiles(t *testing.T) (string, []string, error) { return tempDir, pubFiles, nil } -func parseDecryptAndTestUnsealKeys(t *testing.T, input, rootToken string, core *vault.Core) { +func parseDecryptAndTestUnsealKeys(t *testing.T, + input, rootToken string, + fingerprints bool, + backupKeys map[string]string, + core *vault.Core) { decoder := base64.StdEncoding priv1Bytes, err := decoder.DecodeString(privKey1) if err != nil { @@ -75,7 +80,12 @@ func parseDecryptAndTestUnsealKeys(t *testing.T, input, rootToken string, core * priv3Bytes, } - re, err := regexp.Compile("\\s*Key\\s+\\d+:\\s+(.*)") + var re *regexp.Regexp + if fingerprints { + re, err = regexp.Compile("\\s*Key\\s+\\d+\\s+fingerprint:\\s+([0-9a-fA-F]+);\\s+value:\\s+(.*)") + } else { + re, err = regexp.Compile("\\s*Key\\s+\\d+:\\s+(.*)") + } if err != nil { t.Fatalf("Error compiling regex: %s", err) } @@ -85,11 +95,30 @@ func parseDecryptAndTestUnsealKeys(t *testing.T, input, rootToken string, core * } encodedKeys := []string{} - for _, pair := range matches { - if len(pair) != 2 { - t.Fatalf("Key not found: %#v", pair) + matchedFingerprints := []string{} + for _, tuple := range matches { + if fingerprints { + if len(tuple) != 3 { + t.Fatalf("Key not found: %#v", tuple) + } + matchedFingerprints = append(matchedFingerprints, tuple[1]) + encodedKeys = append(encodedKeys, tuple[2]) + } else { + if len(tuple) != 2 { + t.Fatalf("Key not found: %#v", tuple) + } + encodedKeys = append(encodedKeys, tuple[1]) + } + } + + if backupKeys != nil && len(matchedFingerprints) != 0 { + testMap := map[string]string{} + for i, v := range matchedFingerprints { + testMap[v] = encodedKeys[i] + } + if !reflect.DeepEqual(testMap, backupKeys) { + t.Fatalf("test map and backup map do not match, test map is\n%#v\nbackup map is\n%#v", testMap, backupKeys) } - encodedKeys = append(encodedKeys, pair[1]) } unsealKeys := []string{} diff --git a/command/rekey.go b/command/rekey.go index ac612a160..7189a4648 100644 --- a/command/rekey.go +++ b/command/rekey.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "github.com/fatih/structs" "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/helper/password" "github.com/hashicorp/vault/helper/pgpkeys" @@ -17,24 +18,36 @@ type RekeyCommand struct { // Key can be used to pre-seed the key. If it is set, it will not // be asked with the `password` helper. Key string + + // The nonce for the rekey request to send along + Nonce string } func (c *RekeyCommand) Run(args []string) int { - var init, cancel, status bool + var init, cancel, status, delete, retrieve, backup bool var shares, threshold int + var nonce string var pgpKeys pgpkeys.PubKeyFilesFlag flags := c.Meta.FlagSet("rekey", FlagSetDefault) flags.BoolVar(&init, "init", false, "") flags.BoolVar(&cancel, "cancel", false, "") flags.BoolVar(&status, "status", false, "") + flags.BoolVar(&delete, "delete", false, "") + flags.BoolVar(&retrieve, "retrieve", false, "") + flags.BoolVar(&backup, "backup", false, "") flags.IntVar(&shares, "key-shares", 5, "") flags.IntVar(&threshold, "key-threshold", 3, "") + flags.StringVar(&nonce, "nonce", "", "") flags.Var(&pgpKeys, "pgp-keys", "") flags.Usage = func() { c.Ui.Error(c.Help()) } if err := flags.Parse(args); err != nil { return 1 } + if nonce != "" { + c.Nonce = nonce + } + client, err := c.Client() if err != nil { c.Ui.Error(fmt.Sprintf( @@ -43,12 +56,17 @@ func (c *RekeyCommand) Run(args []string) int { } // Check if we are running doing any restricted variants - if init { - return c.initRekey(client, shares, threshold, pgpKeys) - } else if cancel { + switch { + case init: + return c.initRekey(client, shares, threshold, pgpKeys, backup) + case cancel: return c.cancelRekey(client) - } else if status { + case status: return c.rekeyStatus(client) + case retrieve: + return c.rekeyRetrieveStored(client) + case delete: + return c.rekeyDeleteStored(client) } // Check if the rekey is started @@ -69,27 +87,29 @@ func (c *RekeyCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err)) return 1 } - } else { - shares = rekeyStatus.N - threshold = rekeyStatus.T - c.Ui.Output(fmt.Sprintf( - "Rekey already in progress\n"+ - "Key Shares: %d\n"+ - "Key Threshold: %d\n", - shares, - threshold, - )) + rekeyStatus, err = client.Sys().RekeyStatus() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error reading rekey status: %s", err)) + return 1 + } + c.Nonce = rekeyStatus.Nonce } + shares = rekeyStatus.N + threshold = rekeyStatus.T + serverNonce := rekeyStatus.Nonce + // Get the unseal key args = flags.Args() - value := c.Key + key := c.Key if len(args) > 0 { - value = args[0] + key = args[0] } - if value == "" { + if key == "" { + c.Nonce = serverNonce + fmt.Printf("Rekey operation nonce: %s\n", serverNonce) fmt.Printf("Key (will be hidden): ") - value, err = password.Read(os.Stdin) + key, err = password.Read(os.Stdin) fmt.Printf("\n") if err != nil { c.Ui.Error(fmt.Sprintf( @@ -106,7 +126,7 @@ func (c *RekeyCommand) Run(args []string) int { } // Provide the key, this may potentially complete the update - result, err := client.Sys().RekeyUpdate(strings.TrimSpace(value)) + 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 @@ -119,7 +139,22 @@ func (c *RekeyCommand) Run(args []string) int { // Provide the keys for i, key := range result.Keys { - c.Ui.Output(fmt.Sprintf("Key %d: %s", i+1, key)) + if len(result.PGPFingerprints) > 0 { + c.Ui.Output(fmt.Sprintf("Key %d fingerprint: %s; value: %s", i+1, result.PGPFingerprints[i], key)) + } else { + c.Ui.Output(fmt.Sprintf("Key %d: %s", i+1, key)) + } + } + + c.Ui.Output(fmt.Sprintf("\nOperation nonce: %s", result.Nonce)) + + if len(result.PGPFingerprints) > 0 && result.Backup { + c.Ui.Output(fmt.Sprintf( + "\n" + + "The encrypted unseal keys have been backed up to \"core/unseal-keys\n" + + "in your physical backend. It is your responsibility to remove these\n" + + "if and when desired.", + )) } c.Ui.Output(fmt.Sprintf( @@ -140,12 +175,16 @@ func (c *RekeyCommand) Run(args []string) int { } // initRekey is used to start the rekey process -func (c *RekeyCommand) initRekey(client *api.Client, shares, threshold int, pgpKeys pgpkeys.PubKeyFilesFlag) int { +func (c *RekeyCommand) initRekey(client *api.Client, + shares, threshold int, + pgpKeys pgpkeys.PubKeyFilesFlag, + backup bool) int { // Start the rekey err := client.Sys().RekeyInit(&api.RekeyInitRequest{ SecretShares: shares, SecretThreshold: threshold, PGPKeys: pgpKeys, + Backup: backup, }) if err != nil { c.Ui.Error(fmt.Sprintf("Error initializing rekey: %s", err)) @@ -177,18 +216,49 @@ func (c *RekeyCommand) rekeyStatus(client *api.Client) int { } // Dump the status - c.Ui.Output(fmt.Sprintf( - "Started: %v\n"+ + statString := fmt.Sprintf( + "Nonce: %s\n"+ + "Started: %v\n"+ "Key Shares: %d\n"+ "Key Threshold: %d\n"+ "Rekey Progress: %d\n"+ "Required Keys: %d", + status.Nonce, status.Started, status.N, status.T, status.Progress, status.Required, - )) + ) + if len(status.PGPFingerprints) != 0 { + statString = fmt.Sprintf("\nPGP Key Fingerprints: %s", status.PGPFingerprints) + statString = fmt.Sprintf("\nBackup Storage: %t", status.Backup) + } + c.Ui.Output(statString) + return 0 +} + +func (c *RekeyCommand) rekeyRetrieveStored(client *api.Client) int { + storedKeys, err := client.Sys().RekeyRetrieveStored() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error retrieving stored keys: %s", err)) + return 1 + } + + secret := &api.Secret{ + Data: structs.New(storedKeys).Map(), + } + + return OutputSecret(c.Ui, "table", secret) +} + +func (c *RekeyCommand) rekeyDeleteStored(client *api.Client) int { + err := client.Sys().RekeyDeleteStored() + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to delete stored keys: %s", err)) + return 1 + } + c.Ui.Output("Stored keys deleted.") return 0 } @@ -212,7 +282,7 @@ General Options: ` + generalOptionsUsage() + ` -Unseal Options: +Rekey Options: -init Initialize the rekey operation by setting the desired number of shares and the key threshold. This can only be @@ -225,12 +295,23 @@ Unseal Options: This can be used to see the status without attempting to provide an unseal key. + -retrieve Retrieve backed-up keys. Only available if the PGP keys + were provided and the backup has not been deleted. + + -delete Delete any backed-up keys. + -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. + -nonce=abcd The nonce provided at rekey initialization time. This + same nonce value must be provided with each unseal + key. If the unseal key is not being passed in via the + the command line the nonce parameter is not required, + and will instead be displayed with the key prompt. + -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 @@ -240,6 +321,12 @@ Unseal Options: 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. + + -backup=false If true, and if the key shares are PGP-encrypted, a + plaintext backup of the PGP-encrypted keys will be + stored at "core/unseal-keys-backup" in your physical + storage. You can retrieve or delete them via the + 'sys/rekey/backup' endpoint. ` return strings.TrimSpace(helpText) } diff --git a/command/rekey_test.go b/command/rekey_test.go index cb0f9d21e..46178025b 100644 --- a/command/rekey_test.go +++ b/command/rekey_test.go @@ -5,8 +5,10 @@ import ( "os" "strings" "testing" + "time" "github.com/hashicorp/vault/http" + "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/vault" "github.com/mitchellh/cli" ) @@ -77,7 +79,12 @@ func TestRekey_init(t *testing.T) { }, } - args := []string{"-address", addr, "-init", "-key-threshold=10", "-key-shares=10"} + args := []string{ + "-address", addr, + "-init", + "-key-threshold", "10", + "-key-shares", "10", + } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } @@ -159,6 +166,15 @@ func TestRekey_init_pgp(t *testing.T) { ln, addr := http.TestServer(t, core) defer ln.Close() + bc := &logical.BackendConfig{ + Logger: nil, + System: logical.StaticSystemView{ + DefaultLeaseTTLVal: time.Hour * 24, + MaxLeaseTTLVal: time.Hour * 24 * 30, + }, + } + sysBackend := vault.NewSystemBackend(core, bc) + ui := new(cli.MockUi) c := &RekeyCommand{ Key: hex.EncodeToString(key), @@ -179,6 +195,7 @@ func TestRekey_init_pgp(t *testing.T) { "-key-shares", "3", "-pgp-keys", pubFiles[0] + ",@" + pubFiles[1] + "," + pubFiles[2], "-key-threshold", "2", + "-backup", "true", } if code := c.Run(args); code != 0 { @@ -196,6 +213,8 @@ func TestRekey_init_pgp(t *testing.T) { t.Fatal("should rekey") } + c.Nonce = config.Nonce + args = []string{ "-address", addr, } @@ -203,5 +222,45 @@ func TestRekey_init_pgp(t *testing.T) { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } - parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), token, core) + type backupStruct struct { + Keys map[string]string + } + backupVals := &backupStruct{} + + req := logical.TestRequest(t, logical.ReadOperation, "rekey/backup") + resp, err := sysBackend.HandleRequest(req) + if err != nil { + t.Fatalf("error running backed-up unseal key fetch: %v", err) + } + if resp == nil { + t.Fatalf("got nil resp with unseal key fetch") + } + if resp.Data["keys"] == nil { + t.Fatalf("could not retrieve unseal keys from token") + } + if resp.Data["nonce"] != config.Nonce { + t.Fatalf("nonce mismatch between rekey and backed-up keys") + } + + backupVals.Keys = resp.Data["keys"].(map[string]string) + + // Now delete and try again; the values should be inaccessible + req = logical.TestRequest(t, logical.DeleteOperation, "rekey/backup") + resp, err = sysBackend.HandleRequest(req) + if err != nil { + t.Fatalf("error running backed-up unseal key delete: %v", err) + } + req = logical.TestRequest(t, logical.ReadOperation, "rekey/backup") + resp, err = sysBackend.HandleRequest(req) + if err != nil { + t.Fatalf("error running backed-up unseal key fetch: %v", err) + } + if resp == nil { + t.Fatalf("got nil resp with unseal key fetch") + } + if resp.Data["keys"] != nil { + t.Fatalf("keys found when they should have been deleted") + } + + parseDecryptAndTestUnsealKeys(t, ui.OutputWriter.String(), token, true, backupVals.Keys, core) } diff --git a/helper/pgpkeys/encryptshares.go b/helper/pgpkeys/encryptshares.go index 3cc1eda8b..882de1094 100644 --- a/helper/pgpkeys/encryptshares.go +++ b/helper/pgpkeys/encryptshares.go @@ -16,12 +16,56 @@ import ( // // Note: There is no corresponding test function; this functionality is // thoroughly tested in the init and rekey command unit tests -func EncryptShares(secretShares [][]byte, pgpKeys []string) ([][]byte, error) { +func EncryptShares(secretShares [][]byte, pgpKeys []string) ([]string, [][]byte, error) { if len(secretShares) != len(pgpKeys) { - return nil, fmt.Errorf("Mismatch between number of generated shares and number of PGP keys") + return nil, nil, fmt.Errorf("Mismatch between number of generated shares and number of PGP keys") } - encryptedShares := [][]byte{} - for i, keystring := range pgpKeys { + encryptedShares := make([][]byte, 0, len(pgpKeys)) + entities, err := GetEntities(pgpKeys) + if err != nil { + return nil, nil, err + } + for i, entity := range entities { + ctBuf := bytes.NewBuffer(nil) + pt, err := openpgp.Encrypt(ctBuf, []*openpgp.Entity{entity}, nil, nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("Error setting up encryption for PGP message: %s", err) + } + _, err = pt.Write([]byte(hex.EncodeToString(secretShares[i]))) + if err != nil { + return nil, nil, fmt.Errorf("Error encrypting PGP message: %s", err) + } + pt.Close() + encryptedShares = append(encryptedShares, ctBuf.Bytes()) + } + + fingerprints, err := GetFingerprints(nil, entities) + if err != nil { + return nil, nil, err + } + + return fingerprints, encryptedShares, nil +} + +func GetFingerprints(pgpKeys []string, entities []*openpgp.Entity) ([]string, error) { + if entities == nil { + var err error + entities, err = GetEntities(pgpKeys) + + if err != nil { + return nil, err + } + } + ret := make([]string, 0, len(entities)) + for _, entity := range entities { + ret = append(ret, fmt.Sprintf("%x", entity.PrimaryKey.Fingerprint)) + } + return ret, nil +} + +func GetEntities(pgpKeys []string) ([]*openpgp.Entity, error) { + ret := make([]*openpgp.Entity, 0, len(pgpKeys)) + for _, keystring := range pgpKeys { data, err := base64.StdEncoding.DecodeString(keystring) if err != nil { return nil, fmt.Errorf("Error decoding given PGP key: %s", err) @@ -30,17 +74,7 @@ func EncryptShares(secretShares [][]byte, pgpKeys []string) ([][]byte, error) { if err != nil { return nil, fmt.Errorf("Error parsing given PGP key: %s", err) } - ctBuf := bytes.NewBuffer(nil) - pt, err := openpgp.Encrypt(ctBuf, []*openpgp.Entity{entity}, nil, nil, nil) - if err != nil { - return nil, fmt.Errorf("Error setting up encryption for PGP message: %s", err) - } - _, err = pt.Write([]byte(hex.EncodeToString(secretShares[i]))) - if err != nil { - return nil, fmt.Errorf("Error encrypting PGP message: %s", err) - } - pt.Close() - encryptedShares = append(encryptedShares, ctBuf.Bytes()) + ret = append(ret, entity) } - return encryptedShares, nil + return ret, nil } diff --git a/helper/pgpkeys/flag.go b/helper/pgpkeys/flag.go index 32735799d..0bc2e2070 100644 --- a/helper/pgpkeys/flag.go +++ b/helper/pgpkeys/flag.go @@ -40,26 +40,36 @@ func (p *PubKeyFilesFlag) Set(value string) error { *p = append(*p, key) continue } - if keyfile[0] == '@' { - keyfile = keyfile[1:] - } - f, err := os.Open(keyfile) - if err != nil { - return err - } - defer f.Close() - buf := bytes.NewBuffer(nil) - _, err = buf.ReadFrom(f) + + pgpStr, err := ReadPGPFile(keyfile) if err != nil { return err } - _, err = base64.StdEncoding.DecodeString(buf.String()) - if err == nil { - *p = append(*p, buf.String()) - } else { - *p = append(*p, base64.StdEncoding.EncodeToString(buf.Bytes())) - } + *p = append(*p, pgpStr) } return nil } + +func ReadPGPFile(path string) (string, error) { + if path[0] == '@' { + path = path[1:] + } + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + buf := bytes.NewBuffer(nil) + _, err = buf.ReadFrom(f) + if err != nil { + return "", err + } + + _, err = base64.StdEncoding.DecodeString(buf.String()) + if err == nil { + return buf.String(), nil + } + return base64.StdEncoding.EncodeToString(buf.Bytes()), nil + +} diff --git a/http/handler.go b/http/handler.go index 9747380ae..df1b367bd 100644 --- a/http/handler.go +++ b/http/handler.go @@ -42,6 +42,7 @@ func Handler(core *vault.Core) http.Handler { mux.Handle("/v1/sys/rotate", proxySysRequest(core)) mux.Handle("/v1/sys/key-status", proxySysRequest(core)) mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core)) + mux.Handle("/v1/sys/rekey/backup", proxySysRequest(core)) mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core)) mux.Handle("/v1/", handleLogical(core, false)) diff --git a/http/sys_rekey.go b/http/sys_rekey.go index 6e71373a5..74e1317d0 100644 --- a/http/sys_rekey.go +++ b/http/sys_rekey.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" + "github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/vault" ) @@ -60,9 +61,18 @@ func handleSysRekeyInitGet(core *vault.Core, w http.ResponseWriter, r *http.Requ Required: sealConfig.SecretThreshold, } if rekeyConf != nil { + status.Nonce = rekeyConf.Nonce status.Started = true status.T = rekeyConf.SecretThreshold status.N = rekeyConf.SecretShares + if rekeyConf.PGPKeys != nil && len(rekeyConf.PGPKeys) != 0 { + pgpFingerprints, err := pgpkeys.GetFingerprints(rekeyConf.PGPKeys, nil) + if err != nil { + respondError(w, http.StatusInternalServerError, err) + } + status.PGPFingerprints = pgpFingerprints + status.Backup = rekeyConf.Backup + } } respondOk(w, status) } @@ -75,11 +85,16 @@ func handleSysRekeyInitPut(core *vault.Core, w http.ResponseWriter, r *http.Requ return } + if req.Backup && len(req.PGPKeys) == 0 { + respondError(w, http.StatusBadRequest, fmt.Errorf("cannot request a backup of the new keys without providing PGP keys for encryption")) + } + // Initialize the rekey err := core.RekeyInit(&vault.SealConfig{ SecretShares: req.SecretShares, SecretThreshold: req.SecretThreshold, PGPKeys: req.PGPKeys, + Backup: req.Backup, }) if err != nil { respondError(w, http.StatusBadRequest, err) @@ -127,7 +142,7 @@ func handleSysRekeyUpdate(core *vault.Core) http.Handler { } // Use the key to make progress on rekey - result, err := core.RekeyUpdate(key) + result, err := core.RekeyUpdate(key, req.Nonce) if err != nil { respondError(w, http.StatusBadRequest, err) return @@ -137,6 +152,7 @@ func handleSysRekeyUpdate(core *vault.Core) http.Handler { resp := &RekeyUpdateResponse{} if result != nil { resp.Complete = true + resp.Nonce = req.Nonce // Encode the keys keys := make([]string, 0, len(result.SecretShares)) @@ -144,6 +160,9 @@ func handleSysRekeyUpdate(core *vault.Core) http.Handler { keys = append(keys, hex.EncodeToString(k)) } resp.Keys = keys + + resp.Backup = result.Backup + resp.PGPFingerprints = result.PGPFingerprints } respondOk(w, resp) }) @@ -153,21 +172,29 @@ type RekeyRequest struct { SecretShares int `json:"secret_shares"` SecretThreshold int `json:"secret_threshold"` PGPKeys []string `json:"pgp_keys"` + Backup bool `json:"backup"` } type RekeyStatusResponse struct { - Started bool `json:"started"` - T int `json:"t"` - N int `json:"n"` - Progress int `json:"progress"` - Required int `json:"required"` + Nonce string `json:"nonce"` + Started bool `json:"started"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` + Required int `json:"required"` + PGPFingerprints []string `json:"pgp_fingerprints"` + Backup bool `json:"backup"` } type RekeyUpdateRequest struct { - Key string + Nonce string + Key string } type RekeyUpdateResponse struct { - Complete bool `json:"complete"` - Keys []string `json:"keys"` + Nonce string `json:"nonce"` + Complete bool `json:"complete"` + Keys []string `json:"keys"` + PGPFingerprints []string `json:"pgp_fingerprints"` + Backup bool `json:"backup"` } diff --git a/http/sys_rekey_test.go b/http/sys_rekey_test.go index d93080141..26e5172ed 100644 --- a/http/sys_rekey_test.go +++ b/http/sys_rekey_test.go @@ -22,16 +22,19 @@ func TestSysRekeyInit_Status(t *testing.T) { var actual map[string]interface{} expected := map[string]interface{}{ - "started": false, - "t": float64(0), - "n": float64(0), - "progress": float64(0), - "required": float64(1), + "started": false, + "t": float64(0), + "n": float64(0), + "progress": float64(0), + "required": float64(1), + "pgp_fingerprints": interface{}(nil), + "backup": false, } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) + expected["nonce"] = actual["nonce"] if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) + t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } } @@ -51,16 +54,19 @@ func TestSysRekeyInit_Setup(t *testing.T) { var actual map[string]interface{} expected := map[string]interface{}{ - "started": true, - "t": float64(3), - "n": float64(5), - "progress": float64(0), - "required": float64(1), + "started": true, + "t": float64(3), + "n": float64(5), + "progress": float64(0), + "required": float64(1), + "pgp_fingerprints": interface{}(nil), + "backup": false, } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) + expected["nonce"] = actual["nonce"] if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) + t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } } @@ -86,16 +92,19 @@ func TestSysRekeyInit_Cancel(t *testing.T) { var actual map[string]interface{} expected := map[string]interface{}{ - "started": false, - "t": float64(0), - "n": float64(0), - "progress": float64(0), - "required": float64(1), + "started": false, + "t": float64(0), + "n": float64(0), + "progress": float64(0), + "required": float64(1), + "pgp_fingerprints": interface{}(nil), + "backup": false, } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) + expected["nonce"] = actual["nonce"] if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) + t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } } @@ -123,13 +132,26 @@ func TestSysRekey_Update(t *testing.T) { }) testResponseStatus(t, resp, 204) + // We need to get the nonce first before we update + resp, err := http.Get(addr + "/v1/sys/rekey/init") + if err != nil { + t.Fatalf("err: %s", err) + } + var rekeyStatus map[string]interface{} + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &rekeyStatus) + resp = testHttpPut(t, token, addr+"/v1/sys/rekey/update", map[string]interface{}{ - "key": hex.EncodeToString(master), + "nonce": rekeyStatus["nonce"].(string), + "key": hex.EncodeToString(master), }) var actual map[string]interface{} expected := map[string]interface{}{ - "complete": true, + "complete": true, + "nonce": rekeyStatus["nonce"].(string), + "backup": false, + "pgp_fingerprints": interface{}(nil), } testResponseStatus(t, resp, 200) testResponseBody(t, resp, &actual) @@ -141,6 +163,34 @@ func TestSysRekey_Update(t *testing.T) { delete(actual, "keys") if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) + t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual) } } + +func TestSysRekey_ReInitUpdate(t *testing.T) { + core, master, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + resp := testHttpPut(t, token, addr+"/v1/sys/rekey/init", map[string]interface{}{ + "secret_shares": 5, + "secret_threshold": 3, + }) + testResponseStatus(t, resp, 204) + + resp = testHttpDelete(t, token, addr+"/v1/sys/rekey/init") + testResponseStatus(t, resp, 204) + + resp = testHttpPut(t, token, addr+"/v1/sys/rekey/init", map[string]interface{}{ + "secret_shares": 5, + "secret_threshold": 3, + }) + testResponseStatus(t, resp, 204) + + resp = testHttpPut(t, token, addr+"/v1/sys/rekey/update", map[string]interface{}{ + "key": hex.EncodeToString(master), + }) + + testResponseStatus(t, resp, 400) +} diff --git a/vault/core.go b/vault/core.go index c3ffa32be..090ea7405 100644 --- a/vault/core.go +++ b/vault/core.go @@ -3,6 +3,7 @@ package vault import ( "bytes" "encoding/base64" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -43,6 +44,11 @@ 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 @@ -96,11 +102,21 @@ type SealConfig struct { // if requested, to encrypt the output unseal tokens. If // provided, it sets the value of SecretShares. Ordering // is important. - PGPKeys []string `json:"-"` + 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"` } // Validate is used to sanity check the seal configuration @@ -151,7 +167,15 @@ type InitResult struct { // RekeyResult is used to provide the key parts back after // they are generated as part of the rekey. type RekeyResult struct { - SecretShares [][]byte + SecretShares [][]byte + PGPFingerprints []string + Backup bool +} + +// RekeyBackup stores the backup copy of PGP-encrypted keys +type RekeyBackup struct { + Nonce string + Keys map[string]string } // ErrInvalidKey is returned if there is an error with a @@ -821,7 +845,7 @@ func (c *Core) Initialize(config *SealConfig) (*InitResult, error) { } if len(config.PGPKeys) > 0 { - encryptedShares, err := pgpkeys.EncryptShares(results.SecretShares, config.PGPKeys) + _, encryptedShares, err := pgpkeys.EncryptShares(results.SecretShares, config.PGPKeys) if err != nil { return nil, err } @@ -1208,13 +1232,16 @@ func (c *Core) RekeyInit(config *SealConfig) error { // Copy the configuration c.rekeyConfig = new(SealConfig) *c.rekeyConfig = *config - c.logger.Printf("[INFO] core: rekey initialized (shares: %d, threshold: %d)", - c.rekeyConfig.SecretShares, c.rekeyConfig.SecretThreshold) + + // Initialize the nonce + c.rekeyConfig.Nonce = uuid.GenerateUUID() + c.logger.Printf("[INFO] core: rekey initialized (nonce: %s, shares: %d, threshold: %d)", + c.rekeyConfig.Nonce, c.rekeyConfig.SecretShares, c.rekeyConfig.SecretThreshold) return nil } // RekeyUpdate is used to provide a new key part -func (c *Core) RekeyUpdate(key []byte) (*RekeyResult, error) { +func (c *Core) RekeyUpdate(key []byte, nonce string) (*RekeyResult, error) { // Verify the key length min, max := c.barrier.KeyLength() max += shamir.ShareOverhead @@ -1254,6 +1281,10 @@ func (c *Core) RekeyUpdate(key []byte) (*RekeyResult, error) { 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) + } + // Check if we already have this piece for _, existing := range c.rekeyProgress { if bytes.Equal(existing, key) { @@ -1311,12 +1342,21 @@ func (c *Core) RekeyUpdate(key []byte) (*RekeyResult, error) { results.SecretShares = shares } + backupInfo := map[string]string{} if len(c.rekeyConfig.PGPKeys) > 0 { - encryptedShares, err := pgpkeys.EncryptShares(results.SecretShares, c.rekeyConfig.PGPKeys) + fingerprints, encryptedShares, err := pgpkeys.EncryptShares(results.SecretShares, c.rekeyConfig.PGPKeys) if err != nil { return nil, err } + + for i := 0; i < len(fingerprints); i++ { + encShare := bytes.NewBuffer(encryptedShares[i]) + backupInfo[fingerprints[i]] = hex.EncodeToString(encShare.Bytes()) + } + results.SecretShares = encryptedShares + results.PGPFingerprints = fingerprints + results.Backup = c.rekeyConfig.Backup } // Encode the seal configuration @@ -1343,6 +1383,34 @@ func (c *Core) RekeyUpdate(key []byte) (*RekeyResult, error) { return nil, fmt.Errorf("failed to update seal configuration: %v", err) } + if c.rekeyConfig.Backup && len(backupInfo) > 0 { + + backupVals := &RekeyBackup{ + Nonce: c.rekeyConfig.Nonce, + Keys: backupInfo, + } + buf, err = json.Marshal(backupVals) + if err != nil { + // Don't return, we need to clear the progress/config below, so just + // log the error + // FIXME: in /v2/ when sys endpoints return normal Responses, + // ensure a warning is given + c.logger.Printf("[ERR] core: failed to marshal unseal key backup: %v", err) + } else { + pe := &physical.Entry{ + Key: coreUnsealKeysBackupPath, + Value: buf, + } + if err := c.physical.Put(pe); err != nil { + // Don't return, we need to clear the progress/config below, so just + // log the error + // FIXME: in /v2/ when sys endpoints return normal Responses, + // ensure a warning is given + c.logger.Printf("[ERR] core: failed to save unseal key backup: %v", err) + } + } + } + // Done! c.rekeyProgress = nil c.rekeyConfig = nil @@ -1366,6 +1434,49 @@ func (c *Core) RekeyCancel() error { return nil } +// RekeyRetrieveBackup is used to retrieve any backed-up PGP-encrypted unseal +// keys +func (c *Core) RekeyRetrieveBackup() (*RekeyBackup, error) { + c.stateLock.RLock() + defer c.stateLock.RUnlock() + if c.sealed { + return nil, ErrSealed + } + if c.standby { + return nil, ErrStandby + } + + entry, err := c.physical.Get(coreUnsealKeysBackupPath) + if err != nil { + return nil, err + } + if entry == nil { + return nil, nil + } + + ret := &RekeyBackup{} + err = json.Unmarshal(entry.Value, ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +// RekeyDeleteBackup is used to delete any backed-up PGP-encrypted unseal keys +func (c *Core) RekeyDeleteBackup() error { + c.stateLock.RLock() + defer c.stateLock.RUnlock() + if c.sealed { + return ErrSealed + } + if c.standby { + return ErrStandby + } + + return c.physical.Delete(coreUnsealKeysBackupPath) +} + // postUnseal is invoked after the barrier is unsealed, but before // allowing any user operations. This allows us to setup any state that // requires the Vault to be unsealed such as mount tables, logical backends, diff --git a/vault/core_test.go b/vault/core_test.go index a3be0a27a..87e50f661 100644 --- a/vault/core_test.go +++ b/vault/core_test.go @@ -1781,7 +1781,7 @@ 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, ""); err == nil { t.Fatalf("no rekey in progress") } @@ -1824,6 +1824,7 @@ func TestCore_Rekey_Lifecycle(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + newConf.Nonce = conf.Nonce if !reflect.DeepEqual(conf, newConf) { t.Fatalf("bad: %v", conf) } @@ -1887,8 +1888,17 @@ func TestCore_Rekey_Update(t *testing.T) { t.Fatalf("err: %v", err) } + // Fetch new config with generated nonce + rkconf, err := c.RekeyConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if rkconf == nil { + t.Fatalf("bad: no rekey config received") + } + // Provide the master - result, err := c.RekeyUpdate(master) + result, err := c.RekeyUpdate(master, rkconf.Nonce) if err != nil { t.Fatalf("err: %v", err) } @@ -1919,8 +1929,10 @@ func TestCore_Rekey_Update(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + + newConf.Nonce = rkconf.Nonce if !reflect.DeepEqual(conf, newConf) { - t.Fatalf("bad: %#v", conf) + t.Fatalf("\nexpected: %#v\nactual: %#v\n", conf, newConf) } // Attempt unseal @@ -1948,10 +1960,19 @@ func TestCore_Rekey_Update(t *testing.T) { t.Fatalf("err: %v", err) } + // Fetch new config with generated nonce + rkconf, err = c.RekeyConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if rkconf == nil { + t.Fatalf("bad: no rekey config received") + } + // Provide the parts master oldResult := result for i := 0; i < 3; i++ { - result, err = c.RekeyUpdate(oldResult.SecretShares[i]) + result, err = c.RekeyUpdate(oldResult.SecretShares[i], rkconf.Nonce) if err != nil { t.Fatalf("err: %v", err) } @@ -1987,6 +2008,7 @@ func TestCore_Rekey_Update(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + newConf.Nonce = rkconf.Nonce if !reflect.DeepEqual(conf, newConf) { t.Fatalf("bad: %#v", conf) } @@ -2005,9 +2027,38 @@ func TestCore_Rekey_InvalidMaster(t *testing.T) { t.Fatalf("err: %v", err) } + // Fetch new config with generated nonce + rkconf, err := c.RekeyConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if rkconf == nil { + t.Fatalf("bad: no rekey config received") + } + // Provide the master (invalid) master[0]++ - _, err = c.RekeyUpdate(master) + _, err = c.RekeyUpdate(master, rkconf.Nonce) + if err == nil { + t.Fatalf("expected error") + } +} + +func TestCore_Rekey_InvalidNonce(t *testing.T) { + c, master, _ := TestCoreUnsealed(t) + + // Start a rekey + newConf := &SealConfig{ + SecretThreshold: 3, + SecretShares: 5, + } + err := c.RekeyInit(newConf) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Provide the nonce (invalid) + _, err = c.RekeyUpdate(master, "abcd") if err == nil { t.Fatalf("expected error") } @@ -2151,7 +2202,15 @@ func TestCore_Standby_Rekey(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - result, err := core.RekeyUpdate(key) + // 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) } @@ -2173,7 +2232,15 @@ func TestCore_Standby_Rekey(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - result, err = core2.RekeyUpdate(result.SecretShares[0]) + // 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) } diff --git a/vault/logical_system.go b/vault/logical_system.go index 972d1c99b..376c6e121 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -41,6 +41,20 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend }, Paths: []*framework.Path{ + &framework.Path{ + Pattern: "rekey/backup$", + + Fields: map[string]*framework.FieldSchema{}, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.handleRekeyRetrieve, + logical.DeleteOperation: b.handleRekeyDelete, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["mount_tune"][0]), + HelpDescription: strings.TrimSpace(sysHelp["mount_tune"][1]), + }, + &framework.Path{ Pattern: "mounts/(?P.+?)/tune$", @@ -383,6 +397,41 @@ type SystemBackend struct { Backend *framework.Backend } +// 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() + if err != nil { + return nil, fmt.Errorf("unable to look up backed-up keys: %v", err) + } + if backup == nil { + return logical.ErrorResponse("no backed-up keys found"), nil + } + + // Format the status + resp := &logical.Response{ + Data: map[string]interface{}{ + "nonce": backup.Nonce, + "keys": backup.Keys, + }, + } + + return resp, nil +} + +// 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() + if err != nil { + return nil, fmt.Errorf("error during deletion of backed-up keys: %v", err) + } + + return nil, nil +} + // handleMountTable handles the "mounts" endpoint to provide the mount table func (b *SystemBackend) handleMountTable( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { diff --git a/website/source/docs/http/sys-rekey.html.md b/website/source/docs/http/sys-rekey.html.md index 2f25a9cf4..3f67d4789 100644 --- a/website/source/docs/http/sys-rekey.html.md +++ b/website/source/docs/http/sys-rekey.html.md @@ -29,18 +29,24 @@ description: |-
Returns
- If a rekey is started, then "n" is the new shares to generate and "t" is - the threshold required for the new shares. The "progress" is how many unseal - keys have been provided for this rekey, where "required" must be reached to - complete. + If a rekey is started, then `n` is the new shares to generate and `t` is + the threshold required for the new shares. `progress` is how many unseal + keys have been provided for this rekey, where `required` must be reached to + complete. The `nonce` for the current rekey operation is also displayed. If + PGP keys are being used to encrypt the final shares, the key fingerprints + and whether the final keys will be backed up to physical storage will also + be displayed. ```javascript { "started": true, + "nonce": "2dbd10f1-8528-6246-09e7-82b25b8aba63", "t": 3, "n": 5, "progress": 1, - "required": 3 + "required": 3, + "pgp_fingerprints": ["abcd1234"], + "backup": true } ``` @@ -53,8 +59,8 @@ description: |-
Description
Initializes a new rekey attempt. Only a single rekey attempt can take place - at a time, and changing the parameters of a rekey requires canceling and starting - a new rekey. + at a time, and changing the parameters of a rekey requires canceling and + starting a new rekey, which will also provide a new nonce.
Method
@@ -85,6 +91,14 @@ description: |- original binary representation. The size of this array must be the same as secret_shares. +
  • + backup + optional + If using PGP-encrypted keys, whether Vault should also back them up to + a well-known location in physical storage (`core/unseal-keys-backup`). + These can then be retrieved and removed via the `sys/rekey/backup` + endpoint. +
  • @@ -117,6 +131,67 @@ description: |- +# /sys/rekey/backup + +## GET + +
    +
    Description
    +
    + Return the backup copy of PGP-encrypted unseal keys. The returned value is + the nonce of the rekey operation and a map of PGP key fingerprint to + hex-encoded PGP-encrypted key. +
    + +
    Method
    +
    GET
    + +
    URL
    +
    `/sys/rekey/backup`
    + +
    Parameters
    +
    + None +
    + +
    Returns
    +
    + + ```javascript + { + "nonce": "2dbd10f1-8528-6246-09e7-82b25b8aba63", + "keys": { + "abcd1234": "..." + } + } + ``` + +
    +
    + +## DELETE + +
    +
    Description
    +
    + Delete the backup copy of PGP-encrypted unseal keys. +
    + +
    Method
    +
    DELETE
    + +
    URL
    +
    `/sys/rekey/backup`
    + +
    Parameters
    +
    None +
    + +
    Returns
    +
    `204` response code. +
    +
    + # /sys/rekey/update ## PUT @@ -127,7 +202,8 @@ description: |- Enter a single master key share to progress the rekey of the Vault. If the threshold number of master key shares is reached, Vault will complete the rekey. Otherwise, this API must be called multiple - times until that threshold is met. + times until that threshold is met. The rekey nonce operation must be + provided with each call.
    Method
    @@ -144,18 +220,29 @@ description: |- required A single master share key. +
  • + nonce + required + The nonce of the rekey operation. +
  • Returns
    - A JSON-encoded object indicating completion and if so with the (possibly - encrypted, if pgp_keys was provided) new master keys: + A JSON-encoded object indicating the rekey operation nonce and completion + status; if completed, the new master keys are returned. If the keys are + PGP-encrypted, an array of key fingerprints will also be provided (with the + order in which the keys were used for encryption) along with whether or not + the keys were backed up to physical storage: ```javascript { "complete": true, - "keys": ["one", "two", "three"] + "keys": ["one", "two", "three"], + "nonce": "2dbd10f1-8528-6246-09e7-82b25b8aba63", + "pgp_fingerprints": ["abcd1234"], + "backup": true } ```