forked from hashicorp/vault
/
generate-root.go
372 lines (320 loc) · 10.7 KB
/
generate-root.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
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,
}
}