Builds on top of #4600 to provide CLI support (#4605)

This commit is contained in:
Jeff Mitchell 2018-05-28 00:39:53 -04:00 committed by GitHub
parent 635fd18bf6
commit 14b65ff4db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 191 additions and 52 deletions

View File

@ -296,6 +296,7 @@ type RekeyRetrieveResponse struct {
type RekeyVerificationStatusResponse struct {
Nonce string `json:"nonce"`
Started bool `json:"started"`
T int `json:"t"`
N int `json:"n"`
Progress int `json:"progress"`

View File

@ -29,6 +29,7 @@ type OperatorRekeyCommand struct {
flagPGPKeys []string
flagStatus bool
flagTarget string
flagVerify bool
// Backup options
flagBackup bool
@ -167,6 +168,15 @@ func (c *OperatorRekeyCommand) Flags() *FlagSets {
"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),
@ -331,11 +341,12 @@ func (c *OperatorRekeyCommand) init(client *api.Client) int {
// Make the request
status, err := fn(&api.RekeyInitRequest{
SecretShares: c.flagKeyShares,
SecretThreshold: c.flagKeyThreshold,
StoredShares: c.flagStoredShares,
PGPKeys: c.flagPGPKeys,
Backup: c.flagBackup,
SecretShares: c.flagKeyShares,
SecretThreshold: c.flagKeyThreshold,
StoredShares: c.flagStoredShares,
PGPKeys: c.flagPGPKeys,
Backup: c.flagBackup,
RequireVerification: c.flagVerify,
})
if err != nil {
c.UI.Error(fmt.Sprintf("Error initializing rekey: %s", err))
@ -376,8 +387,15 @@ func (c *OperatorRekeyCommand) cancel(client *api.Client) int {
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
@ -396,16 +414,40 @@ func (c *OperatorRekeyCommand) cancel(client *api.Client) int {
// 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() (*api.RekeyStatusResponse, error)
var updateFn func(string, string) (*api.RekeyUpdateResponse, error)
var statusFn func() (interface{}, error)
var updateFn func(string, string) (interface{}, error)
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
case "barrier":
statusFn = client.Sys().RekeyStatus
updateFn = client.Sys().RekeyUpdate
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":
statusFn = client.Sys().RekeyRecoveryKeyStatus
updateFn = client.Sys().RekeyRecoveryKeyUpdate
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
@ -417,17 +459,32 @@ func (c *OperatorRekeyCommand) provide(client *api.Client, key string) int {
return 2
}
var started bool
var nonce string
switch status.(type) {
case *api.RekeyStatusResponse:
stat := status.(*api.RekeyStatusResponse)
started = stat.Started
nonce = stat.Nonce
case *api.RekeyVerificationStatusResponse:
stat := status.(*api.RekeyVerificationStatusResponse)
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 !status.Started {
if !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
@ -447,8 +504,6 @@ func (c *OperatorRekeyCommand) provide(client *api.Client, key string) int {
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): ")
@ -489,22 +544,56 @@ func (c *OperatorRekeyCommand) provide(client *api.Client, key string) int {
return 2
}
if !resp.Complete {
var complete bool
var mightContainUnsealKeys bool
switch resp.(type) {
case *api.RekeyUpdateResponse:
complete = resp.(*api.RekeyUpdateResponse).Complete
mightContainUnsealKeys = true
case *api.RekeyVerificationUpdateResponse:
complete = resp.(*api.RekeyVerificationUpdateResponse).Complete
default:
c.UI.Error("Unknown update response type")
return 1
}
if !complete {
return c.status(client)
}
return c.printUnsealKeys(status, resp)
if mightContainUnsealKeys {
return c.printUnsealKeys(client, status.(*api.RekeyStatusResponse),
resp.(*api.RekeyUpdateResponse))
}
c.UI.Output("Rekey verification successful.")
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() (*api.RekeyStatusResponse, error)
var fn func() (interface{}, error)
switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
case "barrier":
fn = client.Sys().RekeyStatus
fn = func() (interface{}, error) {
return client.Sys().RekeyStatus()
}
if c.flagVerify {
fn = func() (interface{}, error) {
return client.Sys().RekeyVerificationStatus()
}
}
case "recovery", "hsm":
fn = client.Sys().RekeyRecoveryKeyStatus
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
@ -573,21 +662,35 @@ func (c *OperatorRekeyCommand) backupDelete(client *api.Client) int {
}
// printStatus dumps the status to output
func (c *OperatorRekeyCommand) printStatus(status *api.RekeyStatusResponse) int {
func (c *OperatorRekeyCommand) printStatus(in interface{}) int {
out := []string{}
out = append(out, "Key | Value")
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("Rekey Progress | %d/%d", status.Progress, status.Required))
switch in.(type) {
case *api.RekeyStatusResponse:
status := in.(*api.RekeyStatusResponse)
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("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 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.(*api.RekeyVerificationStatusResponse)
out = append(out, fmt.Sprintf("Nonce | %s", status.Nonce))
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))
}
if len(status.PGPFingerprints) > 0 {
out = append(out, fmt.Sprintf("PGP Fingerprints | %s", status.PGPFingerprints))
out = append(out, fmt.Sprintf("Backup | %t", status.Backup))
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) {
@ -595,11 +698,11 @@ func (c *OperatorRekeyCommand) printStatus(status *api.RekeyStatusResponse) int
c.UI.Output(tableOutput(out, nil))
return 0
default:
return OutputData(c.UI, status)
return OutputData(c.UI, in)
}
}
func (c *OperatorRekeyCommand) printUnsealKeys(status *api.RekeyStatusResponse, resp *api.RekeyUpdateResponse) int {
func (c *OperatorRekeyCommand) printUnsealKeys(client *api.Client, status *api.RekeyStatusResponse, resp *api.RekeyUpdateResponse) int {
switch Format(c.UI) {
case "table":
default:
@ -643,15 +746,40 @@ func (c *OperatorRekeyCommand) printUnsealKeys(status *api.RekeyStatusResponse,
)))
}
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)))
switch status.VerificationRequired {
case false:
c.UI.Output("")
c.UI.Output(wrapAtLength(fmt.Sprintf(
"Vault rekeyed with %d key shares and a key threshold of %d. Please "+
"securely distributed 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)))
default:
c.UI.Output("")
c.UI.Output(wrapAtLength(fmt.Sprintf(
"Vault has created a new key, split into %d key shares and a key threshold "+
"of %d. These will not be active until after verification is complete. "+
"Please securely distributed 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)))
c.UI.Output("")
c.UI.Warn(wrapAtLength(
"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 unseal Vault. The current " +
"verification status, including initial nonce, is shown below.",
))
c.UI.Output("")
c.flagVerify = true
return c.status(client)
}
return 0
}

View File

@ -72,7 +72,7 @@ func handleSysRekeyInitGet(ctx context.Context, core *vault.Core, recovery bool,
}
if rekeyConf != nil {
// Get the progress
progress, err := core.RekeyProgress(recovery, false)
started, progress, err := core.RekeyProgress(recovery, false)
if err != nil {
respondError(w, err.Code(), err)
return
@ -85,7 +85,7 @@ func handleSysRekeyInitGet(ctx context.Context, core *vault.Core, recovery bool,
}
status.Nonce = rekeyConf.Nonce
status.Started = true
status.Started = started
status.T = rekeyConf.SecretThreshold
status.N = rekeyConf.SecretShares
status.Progress = progress
@ -290,7 +290,7 @@ func handleSysRekeyVerifyGet(ctx context.Context, core *vault.Core, recovery boo
}
// Get the progress
progress, err := core.RekeyProgress(recovery, true)
started, progress, err := core.RekeyProgress(recovery, true)
if err != nil {
respondError(w, err.Code(), err)
return
@ -298,6 +298,7 @@ func handleSysRekeyVerifyGet(ctx context.Context, core *vault.Core, recovery boo
// Format the status
status := &RekeyVerificationStatusResponse{
Started: started,
Nonce: rekeyConf.VerificationNonce,
T: rekeyConf.SecretThreshold,
N: rekeyConf.SecretShares,
@ -410,6 +411,7 @@ type RekeyVerificationUpdateRequest struct {
type RekeyVerificationStatusResponse struct {
Nonce string `json:"nonce"`
Started bool `json:"started"`
T int `json:"t"`
N int `json:"n"`
Progress int `json:"progress"`

View File

@ -92,14 +92,14 @@ func (c *Core) RekeyThreshold(ctx context.Context, recovery bool) (int, logical.
}
// RekeyProgress is used to return the rekey progress (num shares).
func (c *Core) RekeyProgress(recovery, verification bool) (int, logical.HTTPCodedError) {
func (c *Core) RekeyProgress(recovery, verification bool) (bool, int, logical.HTTPCodedError) {
c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed {
return 0, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
return false, 0, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
}
if c.standby {
return 0, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
return false, 0, logical.CodedError(http.StatusBadRequest, consts.ErrStandby.Error())
}
c.rekeyLock.RLock()
@ -112,10 +112,14 @@ func (c *Core) RekeyProgress(recovery, verification bool) (int, logical.HTTPCode
conf = c.barrierRekeyConfig
}
if verification {
return len(conf.VerificationProgress), nil
if conf == nil {
return false, 0, logical.CodedError(http.StatusBadRequest, "rekey operation not in progress")
}
return len(conf.RekeyProgress), nil
if verification {
return len(conf.VerificationKey) > 0, len(conf.VerificationProgress), nil
}
return true, len(conf.RekeyProgress), nil
}
// RekeyConfig is used to read the rekey configuration
@ -755,6 +759,10 @@ func (c *Core) RekeyVerify(ctx context.Context, key []byte, nonce string, recove
return nil, logical.CodedError(http.StatusBadRequest, "no rekey in progress")
}
if len(c.barrierRekeyConfig.VerificationKey) == 0 {
return nil, logical.CodedError(http.StatusBadRequest, "no rekey verification in progress")
}
if nonce != config.VerificationNonce {
return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("incorrect nonce supplied; nonce for this verify operation is %q", config.VerificationNonce))
}
@ -854,7 +862,7 @@ func (c *Core) RekeyCancel(recovery bool) logical.HTTPCodedError {
return nil
}
// RekeyVerifyCancel is used to start the verification process over
// RekeyVerifyRestart is used to start the verification process over
func (c *Core) RekeyVerifyRestart(recovery bool) logical.HTTPCodedError {
c.stateLock.RLock()
defer c.stateLock.RUnlock()