787 lines
23 KiB
Go
787 lines
23 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package command
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/fatih/structs"
|
|
"github.com/hashicorp/go-secure-stdlib/password"
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/hashicorp/vault/helper/pgpkeys"
|
|
"github.com/mitchellh/cli"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
var (
|
|
_ cli.Command = (*OperatorRekeyCommand)(nil)
|
|
_ cli.CommandAutocomplete = (*OperatorRekeyCommand)(nil)
|
|
)
|
|
|
|
const (
|
|
keyTypeRecovery = "Recovery"
|
|
keyTypeUnseal = "Unseal"
|
|
)
|
|
|
|
type OperatorRekeyCommand struct {
|
|
*BaseCommand
|
|
|
|
flagCancel bool
|
|
flagInit bool
|
|
flagKeyShares int
|
|
flagKeyThreshold int
|
|
flagNonce string
|
|
flagPGPKeys []string
|
|
flagStatus bool
|
|
flagTarget string
|
|
flagVerify bool
|
|
|
|
// Backup options
|
|
flagBackup bool
|
|
flagBackupDelete bool
|
|
flagBackupRetrieve bool
|
|
|
|
testStdin io.Reader // for tests
|
|
}
|
|
|
|
func (c *OperatorRekeyCommand) Synopsis() string {
|
|
return "Generates new unseal keys"
|
|
}
|
|
|
|
func (c *OperatorRekeyCommand) Help() string {
|
|
helpText := `
|
|
Usage: vault operator 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 root 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.
|
|
|
|
If the flag -target=recovery is supplied, then this operation will require a
|
|
quorum of recovery keys in order to generate a new set of recovery keys.
|
|
|
|
Initialize a rekey:
|
|
|
|
$ vault operator rekey \
|
|
-init \
|
|
-key-shares=15 \
|
|
-key-threshold=9
|
|
|
|
Rekey and encrypt the resulting unseal keys with PGP:
|
|
|
|
$ vault operator 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 operator rekey \
|
|
-init \
|
|
-pgp-keys="..." \
|
|
-backup
|
|
|
|
Retrieve backed-up unseal keys:
|
|
|
|
$ vault operator rekey -backup-retrieve
|
|
|
|
Delete backed-up unseal keys:
|
|
|
|
$ vault operator rekey -backup-delete
|
|
|
|
` + c.Flags().Help()
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *OperatorRekeyCommand) Flags() *FlagSets {
|
|
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
|
|
|
f := set.NewFlagSet("Common 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, recovery keys, or configuration.",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "status",
|
|
Target: &c.flagStatus,
|
|
Default: false,
|
|
Usage: "Print the status of the current attempt without providing an " +
|
|
"unseal or recovery 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 root key into. " +
|
|
"This is the number of \"unseal keys\" or \"recovery 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 root 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 or recovery 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.BoolVar(&BoolVar{
|
|
Name: "verify",
|
|
Target: &c.flagVerify,
|
|
Default: false,
|
|
Usage: "Indicates that the action (-status, -cancel, or providing a key " +
|
|
"share) will be affecting verification for the current rekey " +
|
|
"attempt.",
|
|
})
|
|
|
|
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 PGP keys OR a comma-separated list of Keybase usernames using " +
|
|
"the format \"keybase:<username>\". When supplied, the generated " +
|
|
"unseal or recovery keys will be encrypted and base64-encoded in the order " +
|
|
"specified in this list.",
|
|
})
|
|
|
|
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 or recovery 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 or recovery keys were PGP encrypted.",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "backup-delete",
|
|
Target: &c.flagBackupDelete,
|
|
Default: false,
|
|
Usage: "Delete any stored backup unseal or recovery keys.",
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "backup-retrieve",
|
|
Target: &c.flagBackupRetrieve,
|
|
Default: false,
|
|
Usage: "Retrieve the backed-up unseal or recovery keys. This option is only available " +
|
|
"if the PGP keys were provided and the backup has not been deleted.",
|
|
})
|
|
|
|
return set
|
|
}
|
|
|
|
func (c *OperatorRekeyCommand) AutocompleteArgs() complete.Predictor {
|
|
return complete.PredictAnything
|
|
}
|
|
|
|
func (c *OperatorRekeyCommand) AutocompleteFlags() complete.Flags {
|
|
return c.Flags().Completions()
|
|
}
|
|
|
|
func (c *OperatorRekeyCommand) 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
|
|
}
|
|
|
|
// 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 *OperatorRekeyCommand) init(client *api.Client) int {
|
|
// Handle the different API requests
|
|
var fn func(*api.RekeyInitRequest) (*api.RekeyStatusResponse, error)
|
|
keyTypeRequired := keyTypeUnseal
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
fn = client.Sys().RekeyInit
|
|
case "recovery", "hsm":
|
|
keyTypeRequired = keyTypeRecovery
|
|
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,
|
|
RequireVerification: c.flagVerify,
|
|
})
|
|
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 {
|
|
if Format(c.UI) == "table" {
|
|
c.UI.Warn(wrapAtLength(
|
|
fmt.Sprintf("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 %s 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.", strings.ToLower(keyTypeRequired))))
|
|
c.UI.Output("")
|
|
}
|
|
}
|
|
if len(c.flagPGPKeys) > 0 && !c.flagBackup {
|
|
if Format(c.UI) == "table" {
|
|
c.UI.Warn(wrapAtLength(
|
|
fmt.Sprintf("WARNING! You are using PGP keys for encrypted the resulting %s "+
|
|
"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.", strings.ToLower(keyTypeRequired))))
|
|
c.UI.Output("")
|
|
}
|
|
}
|
|
|
|
// Provide the current status
|
|
return c.printStatus(status)
|
|
}
|
|
|
|
// cancel is used to abort the rekey process.
|
|
func (c *OperatorRekeyCommand) 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
|
|
if c.flagVerify {
|
|
fn = client.Sys().RekeyVerificationCancel
|
|
}
|
|
case "recovery", "hsm":
|
|
fn = client.Sys().RekeyRecoveryKeyCancel
|
|
if c.flagVerify {
|
|
fn = client.Sys().RekeyRecoveryKeyVerificationCancel
|
|
}
|
|
|
|
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 *OperatorRekeyCommand) provide(client *api.Client, key string) int {
|
|
var statusFn func() (interface{}, error)
|
|
var updateFn func(string, string) (interface{}, error)
|
|
keyTypeRequired := keyTypeUnseal
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
statusFn = func() (interface{}, error) {
|
|
return client.Sys().RekeyStatus()
|
|
}
|
|
updateFn = func(s1 string, s2 string) (interface{}, error) {
|
|
return client.Sys().RekeyUpdate(s1, s2)
|
|
}
|
|
if c.flagVerify {
|
|
statusFn = func() (interface{}, error) {
|
|
return client.Sys().RekeyVerificationStatus()
|
|
}
|
|
updateFn = func(s1 string, s2 string) (interface{}, error) {
|
|
return client.Sys().RekeyVerificationUpdate(s1, s2)
|
|
}
|
|
}
|
|
case "recovery", "hsm":
|
|
keyTypeRequired = keyTypeRecovery
|
|
statusFn = func() (interface{}, error) {
|
|
return client.Sys().RekeyRecoveryKeyStatus()
|
|
}
|
|
updateFn = func(s1 string, s2 string) (interface{}, error) {
|
|
return client.Sys().RekeyRecoveryKeyUpdate(s1, s2)
|
|
}
|
|
if c.flagVerify {
|
|
statusFn = func() (interface{}, error) {
|
|
return client.Sys().RekeyRecoveryKeyVerificationStatus()
|
|
}
|
|
updateFn = func(s1 string, s2 string) (interface{}, error) {
|
|
return client.Sys().RekeyRecoveryKeyVerificationUpdate(s1, s2)
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
var started bool
|
|
var nonce string
|
|
|
|
switch status := status.(type) {
|
|
case *api.RekeyStatusResponse:
|
|
stat := status
|
|
started = stat.Started
|
|
nonce = stat.Nonce
|
|
case *api.RekeyVerificationStatusResponse:
|
|
stat := status
|
|
started = stat.Started
|
|
nonce = stat.Nonce
|
|
default:
|
|
c.UI.Error("Unknown status type")
|
|
return 1
|
|
}
|
|
|
|
// 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 !started {
|
|
c.UI.Error(wrapAtLength(
|
|
"No rekey is in progress. Start a rekey process by running " +
|
|
"\"vault operator rekey -init\"."))
|
|
return 1
|
|
}
|
|
|
|
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
|
|
w := getWriterFromUI(c.UI)
|
|
fmt.Fprintf(w, "Rekey operation nonce: %s\n", nonce)
|
|
fmt.Fprintf(w, "%s Key (will be hidden): ", keyTypeRequired)
|
|
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 %s 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", strings.ToLower(keyTypeRequired), 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
|
|
}
|
|
|
|
var complete bool
|
|
var mightContainUnsealKeys bool
|
|
|
|
switch resp := resp.(type) {
|
|
case *api.RekeyUpdateResponse:
|
|
complete = resp.Complete
|
|
mightContainUnsealKeys = true
|
|
case *api.RekeyVerificationUpdateResponse:
|
|
complete = resp.Complete
|
|
default:
|
|
c.UI.Error("Unknown update response type")
|
|
return 1
|
|
}
|
|
|
|
if !complete {
|
|
return c.status(client)
|
|
}
|
|
|
|
if mightContainUnsealKeys {
|
|
return c.printUnsealKeys(client, status.(*api.RekeyStatusResponse),
|
|
resp.(*api.RekeyUpdateResponse))
|
|
}
|
|
|
|
c.UI.Output(wrapAtLength("Rekey verification successful. The rekey operation is complete and the new keys are now active."))
|
|
return 0
|
|
}
|
|
|
|
// status is used just to fetch and dump the status.
|
|
func (c *OperatorRekeyCommand) status(client *api.Client) int {
|
|
// Handle the different API requests
|
|
var fn func() (interface{}, error)
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
fn = func() (interface{}, error) {
|
|
return client.Sys().RekeyStatus()
|
|
}
|
|
if c.flagVerify {
|
|
fn = func() (interface{}, error) {
|
|
return client.Sys().RekeyVerificationStatus()
|
|
}
|
|
}
|
|
case "recovery", "hsm":
|
|
fn = func() (interface{}, error) {
|
|
return client.Sys().RekeyRecoveryKeyStatus()
|
|
}
|
|
if c.flagVerify {
|
|
fn = func() (interface{}, error) {
|
|
return client.Sys().RekeyRecoveryKeyVerificationStatus()
|
|
}
|
|
}
|
|
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 *OperatorRekeyCommand) 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, secret)
|
|
}
|
|
|
|
// backupDelete deletes the stored backup keys.
|
|
func (c *OperatorRekeyCommand) 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 *OperatorRekeyCommand) printStatus(in interface{}) int {
|
|
out := []string{}
|
|
out = append(out, "Key | Value")
|
|
|
|
switch in := in.(type) {
|
|
case *api.RekeyStatusResponse:
|
|
status := in
|
|
out = append(out, fmt.Sprintf("Nonce | %s", status.Nonce))
|
|
out = append(out, fmt.Sprintf("Started | %t", status.Started))
|
|
if status.Started {
|
|
if status.Progress == status.Required {
|
|
out = append(out, fmt.Sprintf("Rekey Progress | %d/%d (verification in progress)", status.Progress, status.Required))
|
|
} else {
|
|
out = append(out, fmt.Sprintf("Rekey 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))
|
|
out = append(out, fmt.Sprintf("Verification Required | %t", status.VerificationRequired))
|
|
if status.VerificationNonce != "" {
|
|
out = append(out, fmt.Sprintf("Verification Nonce | %s", status.VerificationNonce))
|
|
}
|
|
}
|
|
if len(status.PGPFingerprints) > 0 {
|
|
out = append(out, fmt.Sprintf("PGP Fingerprints | %s", status.PGPFingerprints))
|
|
out = append(out, fmt.Sprintf("Backup | %t", status.Backup))
|
|
}
|
|
case *api.RekeyVerificationStatusResponse:
|
|
status := in
|
|
out = append(out, fmt.Sprintf("Started | %t", status.Started))
|
|
out = append(out, fmt.Sprintf("New Shares | %d", status.N))
|
|
out = append(out, fmt.Sprintf("New Threshold | %d", status.T))
|
|
out = append(out, fmt.Sprintf("Verification Nonce | %s", status.Nonce))
|
|
out = append(out, fmt.Sprintf("Verification Progress | %d/%d", status.Progress, status.T))
|
|
default:
|
|
c.UI.Error("Unknown status type")
|
|
return 1
|
|
}
|
|
|
|
switch Format(c.UI) {
|
|
case "table":
|
|
c.UI.Output(tableOutput(out, nil))
|
|
return 0
|
|
default:
|
|
return OutputData(c.UI, in)
|
|
}
|
|
}
|
|
|
|
func (c *OperatorRekeyCommand) printUnsealKeys(client *api.Client, status *api.RekeyStatusResponse, resp *api.RekeyUpdateResponse) int {
|
|
switch Format(c.UI) {
|
|
case "table":
|
|
default:
|
|
return OutputData(c.UI, resp)
|
|
}
|
|
|
|
// 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("")
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"The encrypted unseal keys are backed up to \"core/unseal-keys-backup\" " +
|
|
"in the storage backend. Remove these keys at any time using " +
|
|
"\"vault operator rekey -backup-delete\". Vault does not automatically " +
|
|
"remove these keys.",
|
|
)))
|
|
case "recovery", "hsm":
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"The encrypted recovery keys are backed up to \"core/recovery-keys-backup\" " +
|
|
"in the storage backend. Remove these keys at any time using " +
|
|
"\"vault operator rekey -backup-delete -target=recovery\". Vault does not automatically " +
|
|
"remove these keys.",
|
|
)))
|
|
}
|
|
}
|
|
|
|
switch status.VerificationRequired {
|
|
case false:
|
|
c.UI.Output("")
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"Vault unseal keys rekeyed with %d key shares and a key threshold of %d. Please "+
|
|
"securely distribute the key shares printed above. When 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)))
|
|
case "recovery", "hsm":
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"Vault recovery keys rekeyed with %d key shares and a key threshold of %d. Please "+
|
|
"securely distribute the key shares printed above.",
|
|
status.N,
|
|
status.T)))
|
|
}
|
|
|
|
default:
|
|
c.UI.Output("")
|
|
var warningText string
|
|
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
|
|
case "barrier":
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"Vault has created a new unseal key, split into %d key shares and a key threshold "+
|
|
"of %d. These will not be active until after verification is complete. "+
|
|
"Please securely distribute the key shares printed above. When 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)))
|
|
warningText = "unseal"
|
|
case "recovery", "hsm":
|
|
c.UI.Output(wrapAtLength(fmt.Sprintf(
|
|
"Vault has created a new recovery key, split into %d key shares and a key threshold "+
|
|
"of %d. These will not be active until after verification is complete. "+
|
|
"Please securely distribute the key shares printed above.",
|
|
status.N,
|
|
status.T)))
|
|
warningText = "authenticate with"
|
|
|
|
}
|
|
c.UI.Output("")
|
|
c.UI.Warn(wrapAtLength(fmt.Sprintf(
|
|
"Again, these key shares are _not_ valid until verification is performed. "+
|
|
"Do not lose or discard your current key shares until after verification "+
|
|
"is complete or you will be unable to %s Vault. If you cancel the "+
|
|
"rekey process or seal Vault before verification is complete the new "+
|
|
"shares will be discarded and the current shares will remain valid.", warningText)))
|
|
c.UI.Output("")
|
|
c.UI.Warn(wrapAtLength(
|
|
"The current verification status, including initial nonce, is shown below.",
|
|
))
|
|
c.UI.Output("")
|
|
|
|
c.flagVerify = true
|
|
return c.status(client)
|
|
}
|
|
|
|
return 0
|
|
}
|