open-vault/command/generate-root.go

373 lines
11 KiB
Go

package command
import (
"crypto/rand"
"encoding/base64"
"fmt"
"os"
"strings"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/password"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/helper/xor"
"github.com/hashicorp/vault/meta"
"github.com/posener/complete"
)
// GenerateRootCommand is a Command that generates a new root token.
type GenerateRootCommand struct {
meta.Meta
// 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 *GenerateRootCommand) Run(args []string) int {
var init, cancel, status, genotp bool
var nonce, decode, otp, pgpKey string
var pgpKeyArr pgpkeys.PubKeyFilesFlag
flags := c.Meta.FlagSet("generate-root", meta.FlagSetDefault)
flags.BoolVar(&init, "init", false, "")
flags.BoolVar(&cancel, "cancel", false, "")
flags.BoolVar(&status, "status", false, "")
flags.BoolVar(&genotp, "genotp", false, "")
flags.StringVar(&decode, "decode", "", "")
flags.StringVar(&otp, "otp", "", "")
flags.StringVar(&nonce, "nonce", "", "")
flags.Var(&pgpKeyArr, "pgp-key", "")
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
}
if genotp {
buf := make([]byte, 16)
readLen, err := rand.Read(buf)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading random bytes: %s", err))
return 1
}
if readLen != 16 {
c.Ui.Error(fmt.Sprintf("Read %d bytes when we should have read 16", readLen))
return 1
}
c.Ui.Output(fmt.Sprintf("OTP: %s", base64.StdEncoding.EncodeToString(buf)))
return 0
}
if len(decode) > 0 {
if len(otp) == 0 {
c.Ui.Error("Both the value to decode and the OTP must be passed in")
return 1
}
return c.decode(decode, otp)
}
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing client: %s", err))
return 2
}
// Check if the root generation is started
rootGenerationStatus, err := client.Sys().GenerateRootStatus()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
return 1
}
// If we are initing, or if we are not started but are not running a
// special function, check otp and pgpkey
checkOtpPgp := false
switch {
case init:
checkOtpPgp = true
case cancel:
case status:
case genotp:
case len(decode) != 0:
case rootGenerationStatus.Started:
default:
checkOtpPgp = true
}
if checkOtpPgp {
switch {
case len(otp) == 0 && (pgpKeyArr == nil || len(pgpKeyArr) == 0):
c.Ui.Error(c.Help())
return 1
case len(otp) != 0 && pgpKeyArr != nil && len(pgpKeyArr) != 0:
c.Ui.Error(c.Help())
return 1
case len(otp) != 0:
err := c.verifyOTP(otp)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error verifying the provided OTP: %s", err))
return 1
}
case pgpKeyArr != nil:
if len(pgpKeyArr) != 1 {
c.Ui.Error("Could not parse PGP key")
return 1
}
if len(pgpKeyArr[0]) == 0 {
c.Ui.Error("Got an empty PGP key")
return 1
}
pgpKey = pgpKeyArr[0]
default:
panic("unreachable case")
}
}
if nonce != "" {
c.Nonce = nonce
}
// Check if we are running doing any restricted variants
switch {
case init:
return c.initGenerateRoot(client, otp, pgpKey)
case cancel:
return c.cancelGenerateRoot(client)
case status:
return c.rootGenerationStatus(client)
}
// Start the root generation process if not started
if !rootGenerationStatus.Started {
rootGenerationStatus, err = client.Sys().GenerateRootInit(otp, pgpKey)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
return 1
}
c.Nonce = rootGenerationStatus.Nonce
}
serverNonce := rootGenerationStatus.Nonce
// Get the unseal key
args = flags.Args()
key := c.Key
if len(args) > 0 {
key = args[0]
}
if key == "" {
c.Nonce = serverNonce
fmt.Printf("Root generation operation nonce: %s\n", serverNonce)
fmt.Printf("Key (will be hidden): ")
key, err = password.Read(os.Stdin)
fmt.Printf("\n")
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error attempting to ask for password. The raw error message\n"+
"is shown below, but the most common reason for this error is\n"+
"that you attempted to pipe a value into unseal or you're\n"+
"executing `vault generate-root` from outside of a terminal.\n\n"+
"You should use `vault generate-root` from a terminal for maximum\n"+
"security. If this isn't an option, the unseal key can be passed\n"+
"in using the first parameter.\n\n"+
"Raw error: %s", err))
return 1
}
}
// Provide the key, this may potentially complete the update
statusResp, err := client.Sys().GenerateRootUpdate(strings.TrimSpace(key), c.Nonce)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error attempting generate-root update: %s", err))
return 1
}
c.dumpStatus(statusResp)
return 0
}
func (c *GenerateRootCommand) verifyOTP(otp string) error {
if len(otp) == 0 {
return fmt.Errorf("No OTP passed in")
}
otpBytes, err := base64.StdEncoding.DecodeString(otp)
if err != nil {
return fmt.Errorf("Error decoding base64 OTP value: %s", err)
}
if otpBytes == nil || len(otpBytes) != 16 {
return fmt.Errorf("Decoded OTP value is invalid or wrong length")
}
return nil
}
func (c *GenerateRootCommand) decode(encodedVal, otp string) int {
tokenBytes, err := xor.XORBase64(encodedVal, otp)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
token, err := uuid.FormatUUID(tokenBytes)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error formatting base64 token value: %v", err))
return 1
}
c.Ui.Output(fmt.Sprintf("Root token: %s", token))
return 0
}
// initGenerateRoot is used to start the generation process
func (c *GenerateRootCommand) initGenerateRoot(client *api.Client, otp string, pgpKey string) int {
// Start the rekey
status, err := client.Sys().GenerateRootInit(otp, pgpKey)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing root generation: %s", err))
return 1
}
c.dumpStatus(status)
return 0
}
// cancelGenerateRoot is used to abort the generation process
func (c *GenerateRootCommand) cancelGenerateRoot(client *api.Client) int {
err := client.Sys().GenerateRootCancel()
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to cancel root generation: %s", err))
return 1
}
c.Ui.Output("Root generation canceled.")
return 0
}
// rootGenerationStatus is used just to fetch and dump the status
func (c *GenerateRootCommand) rootGenerationStatus(client *api.Client) int {
// Check the status
status, err := client.Sys().GenerateRootStatus()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading root generation status: %s", err))
return 1
}
c.dumpStatus(status)
return 0
}
// dumpStatus dumps the status to output
func (c *GenerateRootCommand) dumpStatus(status *api.GenerateRootStatusResponse) {
// Dump the status
statString := fmt.Sprintf(
"Nonce: %s\n"+
"Started: %v\n"+
"Generate Root Progress: %d\n"+
"Required Keys: %d\n"+
"Complete: %t",
status.Nonce,
status.Started,
status.Progress,
status.Required,
status.Complete,
)
if len(status.PGPFingerprint) > 0 {
statString = fmt.Sprintf("%s\nPGP Fingerprint: %s", statString, status.PGPFingerprint)
}
if len(status.EncodedRootToken) > 0 {
statString = fmt.Sprintf("%s\n\nEncoded root token: %s", statString, status.EncodedRootToken)
}
c.Ui.Output(statString)
}
func (c *GenerateRootCommand) Synopsis() string {
return "Generates a new root token"
}
func (c *GenerateRootCommand) Help() string {
helpText := `
Usage: vault generate-root [options] [key]
'generate-root' is used to create a new root token.
Root generation can only be done when the vault is already unsealed. The
operation is done online, but requires that a threshold of the current unseal
keys be provided.
One (and only one) of the following must be provided when initializing the
root generation attempt:
1) A 16-byte, base64-encoded One Time Password (OTP) provided in the '-otp'
flag; the token is XOR'd with this value before it is returned once the final
unseal key has been provided. The '-decode' operation can be used with this
value and the OTP to output the final token value. The '-genotp' flag can be
used to generate a suitable value.
or
2) A file containing a PGP key (binary or base64-encoded) or a Keybase.io
username in the format of "keybase:<username>" in the '-pgp-key' flag. The
final token value will be encrypted with this public key and base64-encoded.
General Options:
` + meta.GeneralOptionsUsage() + `
Generate Root Options:
-init Initialize the root generation attempt. This can only
be done if no generation is already initiated.
-cancel Reset the root generation process by throwing away
prior unseal keys and the configuration.
-status Prints the status of the current attempt. This can be
used to see the status without attempting to provide
an unseal key.
-decode=abcd Decodes and outputs the generated root token. The OTP
used at '-init' time must be provided in the '-otp'
parameter.
-genotp Returns a high-quality OTP suitable for passing into
the '-init' method.
-otp=abcd The base64-encoded 16-byte OTP for use with the
'-init' or '-decode' methods.
-pgp-key A file on disk containing a binary- or base64-format
public PGP key, or a Keybase username specified as
"keybase:<username>". The output root token will be
encrypted and base64-encoded, in order, with the given
public key.
-nonce=abcd The nonce provided at initialization time. This same
nonce value must be provided with each unseal key. If
the unseal key is not being passed in via the command
line the nonce parameter is not required, and will
instead be displayed with the key prompt.
`
return strings.TrimSpace(helpText)
}
func (c *GenerateRootCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *GenerateRootCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-init": complete.PredictNothing,
"-cancel": complete.PredictNothing,
"-status": complete.PredictNothing,
"-decode": complete.PredictNothing,
"-genotp": complete.PredictNothing,
"-otp": complete.PredictNothing,
"-pgp-key": complete.PredictNothing,
"-nonce": complete.PredictNothing,
}
}