2017-09-05 04:01:55 +00:00
package command
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"os"
"strings"
2021-07-16 00:17:31 +00:00
"github.com/hashicorp/go-secure-stdlib/base62"
"github.com/hashicorp/go-secure-stdlib/password"
2018-12-20 15:40:01 +00:00
uuid "github.com/hashicorp/go-uuid"
2017-09-05 04:01:55 +00:00
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/helper/xor"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
2021-04-08 16:43:39 +00:00
var (
_ cli . Command = ( * OperatorGenerateRootCommand ) ( nil )
_ cli . CommandAutocomplete = ( * OperatorGenerateRootCommand ) ( nil )
)
2017-09-05 04:01:55 +00:00
2019-10-15 04:55:31 +00:00
type generateRootKind int
const (
generateRootRegular generateRootKind = iota
generateRootDR
generateRootRecovery
)
2017-09-08 02:03:12 +00:00
type OperatorGenerateRootCommand struct {
2017-09-05 04:01:55 +00:00
* BaseCommand
2019-10-15 04:55:31 +00:00
flagInit bool
flagCancel bool
flagStatus bool
flagDecode string
flagOTP string
flagPGPKey string
flagNonce string
flagGenerateOTP bool
flagDRToken bool
flagRecoveryToken bool
2017-09-05 04:01:55 +00:00
testStdin io . Reader // for tests
}
2017-09-08 02:03:12 +00:00
func ( c * OperatorGenerateRootCommand ) Synopsis ( ) string {
2017-09-05 04:01:55 +00:00
return "Generates a new root token"
}
2017-09-08 02:03:12 +00:00
func ( c * OperatorGenerateRootCommand ) Help ( ) string {
2017-09-05 04:01:55 +00:00
helpText := `
2017-09-08 02:03:12 +00:00
Usage : vault operator generate - root [ options ] [ KEY ]
2017-09-05 04:01:55 +00:00
Generates a new root token by combining a quorum of share holders . One of
the following must be provided to start the root token generation :
- A base64 - encoded one - time - password ( OTP ) provided via the "-otp" flag .
Use the "-generate-otp" flag to generate a usable value . The resulting
2017-09-08 02:03:12 +00:00
token is XORed with this value when it is returned . Use the "-decode"
2017-09-05 04:01:55 +00:00
flag to output the final value .
- A file containing a PGP key or a keybase username in the "-pgp-key"
flag . The resulting token is encrypted with this public key .
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 .
Generate an OTP code for the final token :
2017-09-08 02:03:12 +00:00
$ vault operator generate - root - generate - otp
2017-09-05 04:01:55 +00:00
Start a root token generation :
2017-09-08 02:03:12 +00:00
$ vault operator generate - root - init - otp = "..."
$ vault operator generate - root - init - pgp - key = "..."
2017-09-05 04:01:55 +00:00
Enter an unseal key to progress root token generation :
2017-09-08 02:03:12 +00:00
$ vault operator generate - root - otp = "..."
2017-09-05 04:01:55 +00:00
` + c . Flags ( ) . Help ( )
return strings . TrimSpace ( helpText )
}
2017-09-08 02:03:12 +00:00
func ( c * OperatorGenerateRootCommand ) Flags ( ) * FlagSets {
2018-02-12 23:12:16 +00:00
set := c . flagSet ( FlagSetHTTP | FlagSetOutputFormat )
2017-09-05 04:01:55 +00:00
f := set . NewFlagSet ( "Command Options" )
f . BoolVar ( & BoolVar {
Name : "init" ,
Target : & c . flagInit ,
Default : false ,
EnvVar : "" ,
Completion : complete . PredictNothing ,
Usage : "Start a root token generation. This can only be done if " +
"there is not currently one in progress." ,
} )
f . BoolVar ( & BoolVar {
Name : "cancel" ,
Target : & c . flagCancel ,
Default : false ,
EnvVar : "" ,
Completion : complete . PredictNothing ,
Usage : "Reset the root token generation progress. This will discard any " +
"submitted unseal keys or configuration." ,
} )
f . BoolVar ( & BoolVar {
Name : "status" ,
Target : & c . flagStatus ,
Default : false ,
EnvVar : "" ,
Completion : complete . PredictNothing ,
2017-09-08 02:03:12 +00:00
Usage : "Print the status of the current attempt without providing an " +
2017-09-05 04:01:55 +00:00
"unseal key." ,
} )
f . StringVar ( & StringVar {
Name : "decode" ,
Target : & c . flagDecode ,
Default : "" ,
EnvVar : "" ,
Completion : complete . PredictAnything ,
2018-08-22 18:37:40 +00:00
Usage : "The value to decode; setting this triggers a decode operation." ,
2017-09-05 04:01:55 +00:00
} )
f . BoolVar ( & BoolVar {
Name : "generate-otp" ,
Target : & c . flagGenerateOTP ,
Default : false ,
EnvVar : "" ,
Completion : complete . PredictNothing ,
Usage : "Generate and print a high-entropy one-time-password (OTP) " +
"suitable for use with the \"-init\" flag." ,
} )
2018-01-20 00:25:45 +00:00
f . BoolVar ( & BoolVar {
Name : "dr-token" ,
Target : & c . flagDRToken ,
Default : false ,
EnvVar : "" ,
Completion : complete . PredictNothing ,
Usage : "Set this flag to do generate root operations on DR Operational " +
"tokens." ,
} )
2019-10-15 04:55:31 +00:00
f . BoolVar ( & BoolVar {
Name : "recovery-token" ,
Target : & c . flagRecoveryToken ,
Default : false ,
EnvVar : "" ,
Completion : complete . PredictNothing ,
Usage : "Set this flag to do generate root operations on Recovery Operational " +
"tokens." ,
} )
2017-09-05 04:01:55 +00:00
f . StringVar ( & StringVar {
Name : "otp" ,
Target : & c . flagOTP ,
Default : "" ,
EnvVar : "" ,
Completion : complete . PredictAnything ,
Usage : "OTP code to use with \"-decode\" or \"-init\"." ,
} )
f . VarFlag ( & VarFlag {
Name : "pgp-key" ,
Value : ( * pgpkeys . PubKeyFileFlag ) ( & c . flagPGPKey ) ,
Default : "" ,
EnvVar : "" ,
Completion : complete . PredictAnything ,
Usage : "Path to a file on disk containing a binary or base64-encoded " +
"public GPG key. This can also be specified as a Keybase username " +
"using the format \"keybase:<username>\". When supplied, the generated " +
"root token will be encrypted and base64-encoded with the given public " +
"key." ,
} )
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." ,
} )
return set
}
2017-09-08 02:03:12 +00:00
func ( c * OperatorGenerateRootCommand ) AutocompleteArgs ( ) complete . Predictor {
2017-09-05 04:01:55 +00:00
return nil
}
2017-09-08 02:03:12 +00:00
func ( c * OperatorGenerateRootCommand ) AutocompleteFlags ( ) complete . Flags {
2017-09-05 04:01:55 +00:00
return c . Flags ( ) . Completions ( )
}
2017-09-08 02:03:12 +00:00
func ( c * OperatorGenerateRootCommand ) Run ( args [ ] string ) int {
2017-09-05 04:01:55 +00:00
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
}
2019-10-15 04:55:31 +00:00
if c . flagDRToken && c . flagRecoveryToken {
c . UI . Error ( "Both -recovery-token and -dr-token flags are set" )
return 1
}
2017-09-05 04:01:55 +00:00
client , err := c . Client ( )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 2
}
2019-10-15 04:55:31 +00:00
kind := generateRootRegular
switch {
case c . flagDRToken :
kind = generateRootDR
case c . flagRecoveryToken :
kind = generateRootRecovery
}
2017-09-05 04:01:55 +00:00
switch {
case c . flagGenerateOTP :
2019-10-15 04:55:31 +00:00
otp , code := c . generateOTP ( client , kind )
2018-08-22 18:37:40 +00:00
if code == 0 {
2020-02-15 02:26:49 +00:00
switch Format ( c . UI ) {
case "" , "table" :
return PrintRaw ( c . UI , otp )
default :
status := map [ string ] interface { } {
"otp" : otp ,
"otp_length" : len ( otp ) ,
}
return OutputData ( c . UI , status )
}
2018-08-22 18:37:40 +00:00
}
return code
2017-09-05 04:01:55 +00:00
case c . flagDecode != "" :
2019-10-15 04:55:31 +00:00
return c . decode ( client , c . flagDecode , c . flagOTP , kind )
2017-09-05 04:01:55 +00:00
case c . flagCancel :
2019-10-15 04:55:31 +00:00
return c . cancel ( client , kind )
2017-09-05 04:01:55 +00:00
case c . flagInit :
2019-10-15 04:55:31 +00:00
return c . init ( client , c . flagOTP , c . flagPGPKey , kind )
2017-09-05 04:01:55 +00:00
case c . flagStatus :
2019-10-15 04:55:31 +00:00
return c . status ( client , kind )
2017-09-05 04:01:55 +00:00
default :
// If there are no other flags, prompt for an unseal key.
key := ""
if len ( args ) > 0 {
key = strings . TrimSpace ( args [ 0 ] )
}
2019-10-15 04:55:31 +00:00
return c . provide ( client , key , kind )
2017-09-05 04:01:55 +00:00
}
}
2018-08-22 18:37:40 +00:00
// generateOTP generates a suitable OTP code for generating a root token.
2019-10-15 04:55:31 +00:00
func ( c * OperatorGenerateRootCommand ) generateOTP ( client * api . Client , kind generateRootKind ) ( string , int ) {
2018-08-22 18:37:40 +00:00
f := client . Sys ( ) . GenerateRootStatus
2019-10-15 04:55:31 +00:00
switch kind {
case generateRootDR :
2018-08-22 18:37:40 +00:00
f = client . Sys ( ) . GenerateDROperationTokenStatus
2019-10-15 04:55:31 +00:00
case generateRootRecovery :
f = client . Sys ( ) . GenerateRecoveryOperationTokenStatus
2017-09-05 04:01:55 +00:00
}
2019-10-15 04:55:31 +00:00
2018-08-22 18:37:40 +00:00
status , err := f ( )
2017-09-05 04:01:55 +00:00
if err != nil {
2018-08-22 18:37:40 +00:00
c . UI . Error ( fmt . Sprintf ( "Error getting root generation status: %s" , err ) )
return "" , 2
2017-09-05 04:01:55 +00:00
}
2018-08-22 18:37:40 +00:00
switch status . OTPLength {
case 0 :
// This is the fallback case
buf := make ( [ ] byte , 16 )
readLen , err := rand . Read ( buf )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error reading random bytes: %s" , err ) )
return "" , 2
}
2017-09-05 04:01:55 +00:00
2018-08-22 18:37:40 +00:00
if readLen != 16 {
c . UI . Error ( fmt . Sprintf ( "Read %d bytes when we should have read 16" , readLen ) )
return "" , 2
}
2017-09-05 04:01:55 +00:00
2018-08-22 18:37:40 +00:00
return base64 . StdEncoding . EncodeToString ( buf ) , 0
default :
2018-12-20 15:40:01 +00:00
otp , err := base62 . Random ( status . OTPLength )
2018-08-22 18:37:40 +00:00
if err != nil {
2021-06-02 13:22:31 +00:00
c . UI . Error ( fmt . Errorf ( "Error reading random bytes: %w" , err ) . Error ( ) )
2018-08-22 18:37:40 +00:00
return "" , 2
}
2017-09-05 04:01:55 +00:00
2018-08-22 18:37:40 +00:00
return otp , 0
}
2017-09-05 04:01:55 +00:00
}
// decode decodes the given value using the otp.
2019-10-15 04:55:31 +00:00
func ( c * OperatorGenerateRootCommand ) decode ( client * api . Client , encoded , otp string , kind generateRootKind ) int {
2017-09-05 04:01:55 +00:00
if encoded == "" {
c . UI . Error ( "Missing encoded value: use -decode=<string> to supply it" )
return 1
}
if otp == "" {
c . UI . Error ( "Missing otp: use -otp to supply it" )
return 1
}
2018-08-22 18:37:40 +00:00
f := client . Sys ( ) . GenerateRootStatus
2019-10-15 04:55:31 +00:00
switch kind {
case generateRootDR :
2018-08-22 18:37:40 +00:00
f = client . Sys ( ) . GenerateDROperationTokenStatus
2019-10-15 04:55:31 +00:00
case generateRootRecovery :
f = client . Sys ( ) . GenerateRecoveryOperationTokenStatus
2017-09-05 04:01:55 +00:00
}
2019-10-15 04:55:31 +00:00
2018-08-22 18:37:40 +00:00
status , err := f ( )
2017-09-05 04:01:55 +00:00
if err != nil {
2018-08-22 18:37:40 +00:00
c . UI . Error ( fmt . Sprintf ( "Error getting root generation status: %s" , err ) )
return 2
2017-09-05 04:01:55 +00:00
}
2020-02-15 02:26:49 +00:00
var token string
2018-08-22 18:37:40 +00:00
switch status . OTPLength {
case 0 :
// Backwards compat
tokenBytes , err := xor . XORBase64 ( encoded , otp )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error xoring token: %s" , err ) )
return 1
}
2020-02-15 02:26:49 +00:00
uuidToken , err := uuid . FormatUUID ( tokenBytes )
2018-08-22 18:37:40 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error formatting base64 token value: %s" , err ) )
return 1
}
2020-02-15 02:26:49 +00:00
token = strings . TrimSpace ( uuidToken )
2018-08-22 18:37:40 +00:00
default :
tokenBytes , err := base64 . RawStdEncoding . DecodeString ( encoded )
if err != nil {
2021-06-02 13:22:31 +00:00
c . UI . Error ( fmt . Errorf ( "Error decoding base64'd token: %w" , err ) . Error ( ) )
2018-08-22 18:37:40 +00:00
return 1
}
tokenBytes , err = xor . XORBytes ( tokenBytes , [ ] byte ( otp ) )
if err != nil {
2021-06-02 13:22:31 +00:00
c . UI . Error ( fmt . Errorf ( "Error xoring token: %w" , err ) . Error ( ) )
2018-08-22 18:37:40 +00:00
return 1
}
2020-02-15 02:26:49 +00:00
token = string ( tokenBytes )
}
2018-08-22 18:37:40 +00:00
2020-02-15 02:26:49 +00:00
switch Format ( c . UI ) {
case "" , "table" :
return PrintRaw ( c . UI , token )
default :
tokenJSON := map [ string ] interface { } {
"token" : token ,
}
return OutputData ( c . UI , tokenJSON )
2018-08-22 18:37:40 +00:00
}
2017-09-05 04:01:55 +00:00
}
// init is used to start the generation process
2019-10-15 04:55:31 +00:00
func ( c * OperatorGenerateRootCommand ) init ( client * api . Client , otp , pgpKey string , kind generateRootKind ) int {
2017-09-05 04:01:55 +00:00
// Validate incoming fields. Either OTP OR PGP keys must be supplied.
2018-08-22 18:37:40 +00:00
if otp != "" && pgpKey != "" {
2017-09-05 04:01:55 +00:00
c . UI . Error ( "Error initializing: cannot specify both -otp and -pgp-key" )
return 1
}
// Start the root generation
2018-01-20 00:25:45 +00:00
f := client . Sys ( ) . GenerateRootInit
2019-10-15 04:55:31 +00:00
switch kind {
case generateRootDR :
2018-01-20 00:25:45 +00:00
f = client . Sys ( ) . GenerateDROperationTokenInit
2019-10-15 04:55:31 +00:00
case generateRootRecovery :
f = client . Sys ( ) . GenerateRecoveryOperationTokenInit
2018-01-20 00:25:45 +00:00
}
status , err := f ( otp , pgpKey )
2017-09-05 04:01:55 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error initializing root generation: %s" , err ) )
return 2
}
2018-02-12 23:12:16 +00:00
switch Format ( c . UI ) {
case "table" :
return c . printStatus ( status )
default :
return OutputData ( c . UI , status )
}
2017-09-05 04:01:55 +00:00
}
// 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.
2019-10-15 04:55:31 +00:00
func ( c * OperatorGenerateRootCommand ) provide ( client * api . Client , key string , kind generateRootKind ) int {
2018-01-20 00:25:45 +00:00
f := client . Sys ( ) . GenerateRootStatus
2019-10-15 04:55:31 +00:00
switch kind {
case generateRootDR :
2018-01-20 00:25:45 +00:00
f = client . Sys ( ) . GenerateDROperationTokenStatus
2019-10-15 04:55:31 +00:00
case generateRootRecovery :
f = client . Sys ( ) . GenerateRecoveryOperationTokenStatus
2018-01-20 00:25:45 +00:00
}
status , err := f ( )
2017-09-05 04:01:55 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error getting root generation 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 root generation is in progress. Start a root generation by " +
2018-06-07 04:11:21 +00:00
"running \"vault operator generate-root -init\"." ) )
2018-08-22 18:37:40 +00:00
c . UI . Warn ( wrapAtLength ( fmt . Sprintf (
"If starting root generation using the OTP method and generating " +
"your own OTP, the length of the OTP string needs to be %d " +
"characters in length." , status . OTPLength ) ) )
2017-09-05 04:01:55 +00:00
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 )
2018-07-06 13:02:47 +00:00
fmt . Fprintf ( w , "Operation nonce: %s\n" , nonce )
2017-09-05 04:01:55 +00:00
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
2018-01-20 00:25:45 +00:00
fUpd := client . Sys ( ) . GenerateRootUpdate
2019-10-15 04:55:31 +00:00
switch kind {
case generateRootDR :
2018-01-20 00:25:45 +00:00
fUpd = client . Sys ( ) . GenerateDROperationTokenUpdate
2019-10-15 04:55:31 +00:00
case generateRootRecovery :
fUpd = client . Sys ( ) . GenerateRecoveryOperationTokenUpdate
2018-01-20 00:25:45 +00:00
}
status , err = fUpd ( key , nonce )
2017-09-05 04:01:55 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error posting unseal key: %s" , err ) )
return 2
}
2018-02-12 23:12:16 +00:00
switch Format ( c . UI ) {
case "table" :
return c . printStatus ( status )
default :
return OutputData ( c . UI , status )
}
2017-09-05 04:01:55 +00:00
}
// cancel cancels the root token generation
2019-10-15 04:55:31 +00:00
func ( c * OperatorGenerateRootCommand ) cancel ( client * api . Client , kind generateRootKind ) int {
2018-01-20 00:25:45 +00:00
f := client . Sys ( ) . GenerateRootCancel
2019-10-15 04:55:31 +00:00
switch kind {
case generateRootDR :
2018-01-20 00:25:45 +00:00
f = client . Sys ( ) . GenerateDROperationTokenCancel
2019-10-15 04:55:31 +00:00
case generateRootRecovery :
f = client . Sys ( ) . GenerateRecoveryOperationTokenCancel
2018-01-20 00:25:45 +00:00
}
if err := f ( ) ; err != nil {
2017-09-05 04:01:55 +00:00
c . UI . Error ( fmt . Sprintf ( "Error canceling root token generation: %s" , err ) )
return 2
}
c . UI . Output ( "Success! Root token generation canceled (if it was started)" )
return 0
}
// status is used just to fetch and dump the status
2019-10-15 04:55:31 +00:00
func ( c * OperatorGenerateRootCommand ) status ( client * api . Client , kind generateRootKind ) int {
2018-01-20 00:25:45 +00:00
f := client . Sys ( ) . GenerateRootStatus
2019-10-15 04:55:31 +00:00
switch kind {
case generateRootDR :
2018-01-20 00:25:45 +00:00
f = client . Sys ( ) . GenerateDROperationTokenStatus
2019-10-15 04:55:31 +00:00
case generateRootRecovery :
f = client . Sys ( ) . GenerateRecoveryOperationTokenStatus
2018-01-20 00:25:45 +00:00
}
2019-10-15 04:55:31 +00:00
2018-01-20 00:25:45 +00:00
status , err := f ( )
2017-09-05 04:01:55 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error getting root generation status: %s" , err ) )
return 2
}
2018-02-12 23:12:16 +00:00
switch Format ( c . UI ) {
case "table" :
return c . printStatus ( status )
default :
return OutputData ( c . UI , status )
}
2017-09-05 04:01:55 +00:00
}
// printStatus dumps the status to output
2017-09-08 02:03:12 +00:00
func ( c * OperatorGenerateRootCommand ) printStatus ( status * api . GenerateRootStatusResponse ) int {
2017-09-05 04:01:55 +00:00
out := [ ] string { }
out = append ( out , fmt . Sprintf ( "Nonce | %s" , status . Nonce ) )
out = append ( out , fmt . Sprintf ( "Started | %t" , status . Started ) )
out = append ( out , fmt . Sprintf ( "Progress | %d/%d" , status . Progress , status . Required ) )
out = append ( out , fmt . Sprintf ( "Complete | %t" , status . Complete ) )
if status . PGPFingerprint != "" {
out = append ( out , fmt . Sprintf ( "PGP Fingerprint | %s" , status . PGPFingerprint ) )
}
2018-04-10 15:21:38 +00:00
switch {
case status . EncodedToken != "" :
2018-07-06 13:02:47 +00:00
out = append ( out , fmt . Sprintf ( "Encoded Token | %s" , status . EncodedToken ) )
case status . EncodedRootToken != "" :
out = append ( out , fmt . Sprintf ( "Encoded Root Token | %s" , status . EncodedRootToken ) )
2017-09-05 04:01:55 +00:00
}
2018-08-22 18:37:40 +00:00
if status . OTP != "" {
c . UI . Warn ( wrapAtLength ( "A One-Time-Password has been generated for you and is shown in the OTP field. You will need this value to decode the resulting root token, so keep it safe." ) )
out = append ( out , fmt . Sprintf ( "OTP | %s" , status . OTP ) )
}
if status . OTPLength != 0 {
out = append ( out , fmt . Sprintf ( "OTP Length | %d" , status . OTPLength ) )
}
2017-09-05 04:01:55 +00:00
2017-09-08 02:03:12 +00:00
output := columnOutput ( out , nil )
2017-09-05 04:01:55 +00:00
c . UI . Output ( output )
return 0
}