654 lines
18 KiB
Go
654 lines
18 KiB
Go
package command
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/fatih/structs"
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/hashicorp/vault/helper/password"
|
|
"github.com/hashicorp/vault/helper/pgpkeys"
|
|
"github.com/mitchellh/cli"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
// Ensure we are implementing the right interfaces.
|
|
var _ cli.Command = (*RekeyCommand)(nil)
|
|
var _ cli.CommandAutocomplete = (*RekeyCommand)(nil)
|
|
|
|
// RekeyCommand is a Command that rekeys the vault.
|
|
type RekeyCommand struct {
|
|
*BaseCommand
|
|
|
|
// 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
|
|
|
|
// Whether to use the recovery key instead of barrier key, if available
|
|
RecoveryKey bool
|
|
|
|
flagCancel bool
|
|
flagInit bool
|
|
flagKeyShares int
|
|
flagKeyThreshold int
|
|
flagNonce string
|
|
flagPGPKeys []string
|
|
flagStatus bool
|
|
flagTarget string
|
|
|
|
// Backup options
|
|
flagBackup bool
|
|
flagBackupDelete bool
|
|
flagBackupRetrieve bool
|
|
|
|
// Deprecations
|
|
// TODO: remove in 0.9.0
|
|
flagDelete bool
|
|
flagRecoveryKey bool
|
|
flagRetrieve bool
|
|
|
|
testStdin io.Reader // for tests
|
|
}
|
|
|
|
func (c *RekeyCommand) Synopsis() string {
|
|
return "Generates new unseal keys"
|
|
}
|
|
|
|
func (c *RekeyCommand) Help() string {
|
|
helpText := `
|
|
Usage: vault rekey [options] [KEY]
|
|
|
|
Generates a new set of unseal keys. This can optionally change the total
|
|
number of key shares or the required threshold of those key shares to
|
|
reconstruct the master key. This operation is zero downtime, but it requires
|
|
the Vault is unsealed and a quorum of existing unseal keys are provided.
|
|
|
|
An unseal key may be provided directly on the command line as an argument to
|
|
the command. If key is specified as "-", the command will read from stdin. If
|
|
a TTY is available, the command will prompt for text.
|
|
|
|
Initialize a rekey:
|
|
|
|
$ vault rekey \
|
|
-init \
|
|
-key-shares=15 \
|
|
-key-threshold=9
|
|
|
|
Rekey and encrypt the resulting unseal keys with PGP:
|
|
|
|
$ vault rekey \
|
|
-init \
|
|
-key-shares=3 \
|
|
-key-threshold=2 \
|
|
-pgp-keys="keybase:hashicorp,keybase:jefferai,keybase:sethvargo"
|
|
|
|
Store encrypted PGP keys in Vault's core:
|
|
|
|
$ vault rekey \
|
|
-init \
|
|
-pgp-keys="..." \
|
|
-backup
|
|
|
|
Retrieve backed-up unseal keys:
|
|
|
|
$ vault rekey -backup-retrieve
|
|
|
|
Delete backed-up unseal keys:
|
|
|
|
$ vault rekey -backup-delete
|
|
|
|
For a full list of examples, please see the documentation.
|
|
|
|
` + c.Flags().Help()
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *RekeyCommand) Flags() *FlagSets {
|
|
set := c.flagSet(FlagSetHTTP)
|
|
|
|
f := set.NewFlagSet("Command Options")
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "init",
|
|
Target: &c.flagInit,
|
|
Default: false,
|
|
Usage: "Initialize the rekeying operation. This can only be done if no " +
|
|
"rekeying operation is in progress. Customize the new number of key " +
|
|
"shares and key threshold using the -key-shares and -key-threshold " +
|
|
"flags.",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "cancel",
|
|
Target: &c.flagCancel,
|
|
Default: false,
|
|
Usage: "Reset the rekeying progress. This will discard any submitted " +
|
|
"unseal keys or configuration.",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "status",
|
|
Target: &c.flagStatus,
|
|
Default: false,
|
|
Usage: "Print the status of the current attempt without provding an " +
|
|
"unseal key.",
|
|
})
|
|
|
|
f.IntVar(&IntVar{
|
|
Name: "key-shares",
|
|
Aliases: []string{"n"},
|
|
Target: &c.flagKeyShares,
|
|
Default: 5,
|
|
Completion: complete.PredictAnything,
|
|
Usage: "Number of key shares to split the generated master key into. " +
|
|
"This is the number of \"unseal keys\" to generate.",
|
|
})
|
|
|
|
f.IntVar(&IntVar{
|
|
Name: "key-threshold",
|
|
Aliases: []string{"t"},
|
|
Target: &c.flagKeyThreshold,
|
|
Default: 3,
|
|
Completion: complete.PredictAnything,
|
|
Usage: "Number of key shares required to reconstruct the master key. " +
|
|
"This must be less than or equal to -key-shares.",
|
|
})
|
|
|
|
f.StringVar(&StringVar{
|
|
Name: "nonce",
|
|
Target: &c.flagNonce,
|
|
Default: "",
|
|
EnvVar: "",
|
|
Completion: complete.PredictAnything,
|
|
Usage: "Nonce value provided at initialization. The same nonce value " +
|
|
"must be provided with each unseal key.",
|
|
})
|
|
|
|
f.StringVar(&StringVar{
|
|
Name: "target",
|
|
Target: &c.flagTarget,
|
|
Default: "barrier",
|
|
EnvVar: "",
|
|
Completion: complete.PredictSet("barrier", "recovery"),
|
|
Usage: "Target for rekeying. \"recovery\" only applies when HSM support " +
|
|
"is enabled.",
|
|
})
|
|
|
|
f.VarFlag(&VarFlag{
|
|
Name: "pgp-keys",
|
|
Value: (*pgpkeys.PubKeyFilesFlag)(&c.flagPGPKeys),
|
|
Completion: complete.PredictAnything,
|
|
Usage: "Comma-separated list of paths to files on disk containing " +
|
|
"public GPG keys OR a comma-separated list of Keybase usernames using " +
|
|
"the format \"keybase:<username>\". When supplied, the generated " +
|
|
"unseal keys will be encrypted and base64-encoded in the order " +
|
|
"specified in this list. The number of entires must match -key-shares, " +
|
|
"unless -store-shares are used.",
|
|
})
|
|
|
|
f = set.NewFlagSet("Backup Options")
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "backup",
|
|
Target: &c.flagBackup,
|
|
Default: false,
|
|
Usage: "Store a backup of the current PGP encrypted unseal keys in " +
|
|
"Vault's core. The encrypted values can be recovered in the event of " +
|
|
"failure or discarded after success. See the -backup-delete and " +
|
|
"-backup-retrieve options for more information. This option only " +
|
|
"applies when the existing unseal keys were PGP encrypted.",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "backup-delete",
|
|
Target: &c.flagBackupDelete,
|
|
Default: false,
|
|
Usage: "Delete any stored backup unseal keys.",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "backup-retrieve",
|
|
Target: &c.flagBackupRetrieve,
|
|
Default: false,
|
|
Usage: "Retrieve the backed-up unseal keys. This option is only avaiable " +
|
|
"if the PGP keys were provided and the backup has not been deleted.",
|
|
})
|
|
|
|
// Deprecations
|
|
// TODO: remove in 0.9.0
|
|
f.BoolVar(&BoolVar{
|
|
Name: "delete", // prefer -backup-delete
|
|
Target: &c.flagDelete,
|
|
Default: false,
|
|
Hidden: true,
|
|
Usage: "",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "retrieve", // prefer -backup-retrieve
|
|
Target: &c.flagRetrieve,
|
|
Default: false,
|
|
Hidden: true,
|
|
Usage: "",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "recovery-key", // prefer -target=recovery
|
|
Target: &c.flagRecoveryKey,
|
|
Default: false,
|
|
Hidden: true,
|
|
Usage: "",
|
|
})
|
|
|
|
return set
|
|
}
|
|
|
|
func (c *RekeyCommand) AutocompleteArgs() complete.Predictor {
|
|
return complete.PredictAnything
|
|
}
|
|
|
|
func (c *RekeyCommand) AutocompleteFlags() complete.Flags {
|
|
return c.Flags().Completions()
|
|
}
|
|
|
|
func (c *RekeyCommand) Run(args []string) int {
|
|
f := c.Flags()
|
|
|
|
if err := f.Parse(args); err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
args = f.Args()
|
|
if len(args) > 1 {
|
|
c.UI.Error(fmt.Sprintf("Too many arguments (expected 0-1, got %d)", len(args)))
|
|
return 1
|
|
}
|
|
|
|
// Deprecations
|
|
// TODO: remove in 0.9.0
|
|
if c.flagDelete {
|
|
c.UI.Warn(wrapAtLength(
|
|
"WARNING! The -delete flag is deprecated. Please use -backup-delete " +
|
|
"instead. This flag will be removed in the next major release of " +
|
|
"Vault."))
|
|
c.flagBackupDelete = true
|
|
}
|
|
if c.flagRetrieve {
|
|
c.UI.Warn(wrapAtLength(
|
|
"WARNING! The -retrieve flag is deprecated. Please use -backup-retrieve " +
|
|
"instead. This flag will be removed in the next major release of " +
|
|
"Vault."))
|
|
c.flagBackupRetrieve = true
|
|
}
|
|
if c.flagRecoveryKey {
|
|
c.UI.Warn(wrapAtLength(
|
|
"WARNING! The -recovery-key flag is deprecated. Please use -target=recovery " +
|
|
"instead. This flag will be removed in the next major release of " +
|
|
"Vault."))
|
|
c.flagTarget = "recovery"
|
|
}
|
|
|
|
// Create the client
|
|
client, err := c.Client()
|
|
if err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 2
|
|
}
|
|
|
|
switch {
|
|
case c.flagBackupDelete:
|
|
return c.backupDelete(client)
|
|
case c.flagBackupRetrieve:
|
|
return c.backupRetrieve(client)
|
|
case c.flagCancel:
|
|
return c.cancel(client)
|
|
case c.flagInit:
|
|
return c.init(client)
|
|
case c.flagStatus:
|
|
return c.status(client)
|
|
default:
|
|
// If there are no other flags, prompt for an unseal key.
|
|
key := ""
|
|
if len(args) > 0 {
|
|
key = strings.TrimSpace(args[0])
|
|
}
|
|
return c.provide(client, key)
|
|
}
|
|
}
|
|
|
|
// init starts the rekey process.
|
|
func (c *RekeyCommand) init(client *api.Client) int {
|
|
// Handle the different API requests
|
|
var fn func(*api.RekeyInitRequest) (*api.RekeyStatusResponse, error)
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
fn = client.Sys().RekeyInit
|
|
case "recovery", "hsm":
|
|
fn = client.Sys().RekeyRecoveryKeyInit
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
|
|
return 1
|
|
}
|
|
|
|
// Make the request
|
|
status, err := fn(&api.RekeyInitRequest{
|
|
SecretShares: c.flagKeyShares,
|
|
SecretThreshold: c.flagKeyThreshold,
|
|
PGPKeys: c.flagPGPKeys,
|
|
Backup: c.flagBackup,
|
|
})
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error initializing rekey: %s", err))
|
|
return 2
|
|
}
|
|
|
|
// Print warnings about recovery, etc.
|
|
if len(c.flagPGPKeys) == 0 {
|
|
c.UI.Warn(wrapAtLength(
|
|
"WARNING! If you lose the keys after they are returned, there is no " +
|
|
"recovery. Consider canceling this operation and re-initializing " +
|
|
"with the -pgp-keys flag to protect the returned unseal keys along " +
|
|
"with -backup to allow recovery of the encrypted keys in case of " +
|
|
"emergency. You can delete the stored keys later using the -delete " +
|
|
"flag."))
|
|
c.UI.Output("")
|
|
}
|
|
if len(c.flagPGPKeys) > 0 && !c.flagBackup {
|
|
c.UI.Warn(wrapAtLength(
|
|
"WARNING! You are using PGP keys for encrypted the resulting unseal " +
|
|
"keys, but you did not enable the option to backup the keys to " +
|
|
"Vault's core. If you lose the encrypted keys after they are " +
|
|
"returned, you will not be able to recover them. Consider canceling " +
|
|
"this operation and re-running with -backup to allow recovery of the " +
|
|
"encrypted unseal keys in case of emergency. You can delete the " +
|
|
"stored keys later using the -delete flag."))
|
|
c.UI.Output("")
|
|
}
|
|
|
|
// Provide the current status
|
|
return c.printStatus(status)
|
|
}
|
|
|
|
// cancel is used to abort the rekey process.
|
|
func (c *RekeyCommand) cancel(client *api.Client) int {
|
|
// Handle the different API requests
|
|
var fn func() error
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
fn = client.Sys().RekeyCancel
|
|
case "recovery", "hsm":
|
|
fn = client.Sys().RekeyRecoveryKeyCancel
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
|
|
return 1
|
|
}
|
|
|
|
// Make the request
|
|
if err := fn(); err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error canceling rekey: %s", err))
|
|
return 2
|
|
}
|
|
|
|
c.UI.Output("Success! Canceled rekeying (if it was started)")
|
|
return 0
|
|
}
|
|
|
|
// provide prompts the user for the seal key and posts it to the update root
|
|
// endpoint. If this is the last unseal, this function outputs it.
|
|
func (c *RekeyCommand) provide(client *api.Client, key string) int {
|
|
var statusFn func() (*api.RekeyStatusResponse, error)
|
|
var updateFn func(string, string) (*api.RekeyUpdateResponse, error)
|
|
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
statusFn = client.Sys().RekeyStatus
|
|
updateFn = client.Sys().RekeyUpdate
|
|
case "recovery", "hsm":
|
|
statusFn = client.Sys().RekeyRecoveryKeyStatus
|
|
updateFn = client.Sys().RekeyRecoveryKeyUpdate
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
|
|
return 1
|
|
}
|
|
|
|
status, err := statusFn()
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error getting rekey status: %s", err))
|
|
return 2
|
|
}
|
|
|
|
// Verify a root token generation is in progress. If there is not one in
|
|
// progress, return an error instructing the user to start one.
|
|
if !status.Started {
|
|
c.UI.Error(wrapAtLength(
|
|
"No rekey is in progress. Start a rekey process by running " +
|
|
"\"vault rekey -init\"."))
|
|
return 1
|
|
}
|
|
|
|
var nonce string
|
|
|
|
switch key {
|
|
case "-": // Read from stdin
|
|
nonce = c.flagNonce
|
|
|
|
// Pull our fake stdin if needed
|
|
stdin := (io.Reader)(os.Stdin)
|
|
if c.testStdin != nil {
|
|
stdin = c.testStdin
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if _, err := io.Copy(&buf, stdin); err != nil {
|
|
c.UI.Error(fmt.Sprintf("Failed to read from stdin: %s", err))
|
|
return 1
|
|
}
|
|
|
|
key = buf.String()
|
|
case "": // Prompt using the tty
|
|
// Nonce value is not required if we are prompting via the terminal
|
|
nonce = status.Nonce
|
|
|
|
w := getWriterFromUI(c.UI)
|
|
fmt.Fprintf(w, "Rekey operation nonce: %s\n", nonce)
|
|
fmt.Fprintf(w, "Unseal Key (will be hidden): ")
|
|
key, err = password.Read(os.Stdin)
|
|
fmt.Fprintf(w, "\n")
|
|
if err != nil {
|
|
if err == password.ErrInterrupted {
|
|
c.UI.Error("user canceled")
|
|
return 1
|
|
}
|
|
|
|
c.UI.Error(wrapAtLength(fmt.Sprintf("An error occurred attempting to "+
|
|
"ask for the unseal key. The raw error message is shown below, but "+
|
|
"usually this is because you attempted to pipe a value into the "+
|
|
"command or you are executing outside of a terminal (tty). If you "+
|
|
"want to pipe the value, pass \"-\" as the argument to read from "+
|
|
"stdin. The raw error was: %s", err)))
|
|
return 1
|
|
}
|
|
default: // Supplied directly as an arg
|
|
nonce = c.flagNonce
|
|
}
|
|
|
|
// Trim any whitespace from they key, especially since we might have
|
|
// prompted the user for it.
|
|
key = strings.TrimSpace(key)
|
|
|
|
// Verify we have a nonce value
|
|
if nonce == "" {
|
|
c.UI.Error("Missing nonce value: specify it via the -nonce flag")
|
|
return 1
|
|
}
|
|
|
|
// Provide the key, this may potentially complete the update
|
|
resp, err := updateFn(key, nonce)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error posting unseal key: %s", err))
|
|
return 2
|
|
}
|
|
|
|
if !resp.Complete {
|
|
return c.status(client)
|
|
}
|
|
|
|
return c.printUnsealKeys(status, resp)
|
|
}
|
|
|
|
// status is used just to fetch and dump the status.
|
|
func (c *RekeyCommand) status(client *api.Client) int {
|
|
// Handle the different API requests
|
|
var fn func() (*api.RekeyStatusResponse, error)
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
fn = client.Sys().RekeyStatus
|
|
case "recovery", "hsm":
|
|
fn = client.Sys().RekeyRecoveryKeyStatus
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
|
|
return 1
|
|
}
|
|
|
|
// Make the request
|
|
status, err := fn()
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error reading rekey status: %s", err))
|
|
return 2
|
|
}
|
|
|
|
return c.printStatus(status)
|
|
}
|
|
|
|
// backupRetrieve retrieves the stored backup keys.
|
|
func (c *RekeyCommand) backupRetrieve(client *api.Client) int {
|
|
// Handle the different API requests
|
|
var fn func() (*api.RekeyRetrieveResponse, error)
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
fn = client.Sys().RekeyRetrieveBackup
|
|
case "recovery", "hsm":
|
|
fn = client.Sys().RekeyRetrieveRecoveryBackup
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
|
|
return 1
|
|
}
|
|
|
|
// Make the request
|
|
storedKeys, err := fn()
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error retrieving rekey stored keys: %s", err))
|
|
return 2
|
|
}
|
|
|
|
secret := &api.Secret{
|
|
Data: structs.New(storedKeys).Map(),
|
|
}
|
|
|
|
return OutputSecret(c.UI, "table", secret)
|
|
}
|
|
|
|
// backupDelete deletes the stored backup keys.
|
|
func (c *RekeyCommand) backupDelete(client *api.Client) int {
|
|
// Handle the different API requests
|
|
var fn func() error
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
fn = client.Sys().RekeyDeleteBackup
|
|
case "recovery", "hsm":
|
|
fn = client.Sys().RekeyDeleteRecoveryBackup
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
|
|
return 1
|
|
}
|
|
|
|
// Make the request
|
|
if err := fn(); err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error deleting rekey stored keys: %s", err))
|
|
return 2
|
|
}
|
|
|
|
c.UI.Output("Success! Delete stored keys (if they existed)")
|
|
return 0
|
|
}
|
|
|
|
// printStatus dumps the status to output
|
|
func (c *RekeyCommand) printStatus(status *api.RekeyStatusResponse) int {
|
|
out := []string{}
|
|
out = append(out, fmt.Sprintf("Nonce | %s", status.Nonce))
|
|
out = append(out, fmt.Sprintf("Started | %t", status.Started))
|
|
|
|
if status.Started {
|
|
out = append(out, fmt.Sprintf("Progress | %d/%d", status.Progress, status.Required))
|
|
out = append(out, fmt.Sprintf("New Shares | %d", status.N))
|
|
out = append(out, fmt.Sprintf("New Threshold | %d", status.T))
|
|
}
|
|
|
|
if len(status.PGPFingerprints) > 0 {
|
|
out = append(out, fmt.Sprintf("PGP Fingerprints | %s", status.PGPFingerprints))
|
|
out = append(out, fmt.Sprintf("Backup | %t", status.Backup))
|
|
}
|
|
|
|
output := columnOutput(out)
|
|
c.UI.Output(output)
|
|
return 0
|
|
}
|
|
|
|
func (c *RekeyCommand) printUnsealKeys(status *api.RekeyStatusResponse, resp *api.RekeyUpdateResponse) int {
|
|
// Space between the key prompt, if any, and the output
|
|
c.UI.Output("")
|
|
|
|
// Provide the keys
|
|
var haveB64 bool
|
|
if resp.KeysB64 != nil && len(resp.KeysB64) == len(resp.Keys) {
|
|
haveB64 = true
|
|
}
|
|
for i, key := range resp.Keys {
|
|
if len(resp.PGPFingerprints) > 0 {
|
|
if haveB64 {
|
|
c.UI.Output(fmt.Sprintf("Key %d fingerprint: %s; value: %s", i+1, resp.PGPFingerprints[i], resp.KeysB64[i]))
|
|
} else {
|
|
c.UI.Output(fmt.Sprintf("Key %d fingerprint: %s; value: %s", i+1, resp.PGPFingerprints[i], key))
|
|
}
|
|
} else {
|
|
if haveB64 {
|
|
c.UI.Output(fmt.Sprintf("Key %d: %s", i+1, resp.KeysB64[i]))
|
|
} else {
|
|
c.UI.Output(fmt.Sprintf("Key %d: %s", i+1, key))
|
|
}
|
|
}
|
|
}
|
|
|
|
c.UI.Output("")
|
|
c.UI.Output(fmt.Sprintf("Operation nonce: %s", resp.Nonce))
|
|
|
|
if len(resp.PGPFingerprints) > 0 && resp.Backup {
|
|
c.UI.Output("")
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"The encrypted unseal keys are backed up to \"core/unseal-keys-backup\"" +
|
|
"in the physical backend. Remove these keys at any time using " +
|
|
"\"vault rekey -delete-backup\". Vault does not automatically remove " +
|
|
"these keys.",
|
|
)))
|
|
}
|
|
|
|
c.UI.Output("")
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"Vault rekeyed with %d key shares an a key threshold of %d. Please "+
|
|
"securely distributed the key shares printed above. When the Vault is "+
|
|
"re-sealed, restarted, or stopped, you must supply at least %d of "+
|
|
"these keys to unseal it before it can start servicing requests.",
|
|
status.N,
|
|
status.T,
|
|
status.T)))
|
|
|
|
return 0
|
|
}
|