operator generate-root -decode: allow token from stdin (#12881)
* operator generate-root -decode: allow token from stdin Allow passing "-" as the value for -decode, causing the encoded token to be read from stdin. This is intended to prevent leaking the encoded token + otp into process logs in enterprise environments. * add changelog entry for PR12881 * add check/test for empty decode value passed via stdin
This commit is contained in:
parent
b76d2cd09c
commit
ceac6e913d
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
command: operator generate-root -decode: allow passing encoded token via stdin
|
||||
```
|
|
@ -130,7 +130,8 @@ func (c *OperatorGenerateRootCommand) Flags() *FlagSets {
|
|||
Default: "",
|
||||
EnvVar: "",
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "The value to decode; setting this triggers a decode operation.",
|
||||
Usage: "The value to decode; setting this triggers a decode operation. " +
|
||||
" If the value is \"-\" then read the encoded token from stdin.",
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
|
@ -328,6 +329,27 @@ func (c *OperatorGenerateRootCommand) decode(client *api.Client, encoded, otp st
|
|||
return 1
|
||||
}
|
||||
|
||||
if encoded == "-" {
|
||||
// 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
|
||||
}
|
||||
|
||||
encoded = buf.String()
|
||||
|
||||
if encoded == "" {
|
||||
c.UI.Error("Missing encoded value. When using -decode=\"-\" value must be passed via stdin.")
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
f := client.Sys().GenerateRootStatus
|
||||
switch kind {
|
||||
case generateRootDR:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !race
|
||||
// +build !race
|
||||
|
||||
package command
|
||||
|
@ -158,6 +159,96 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("decode_from_stdin", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
encoded := "Bxg9JQQqOCNKBRICNwMIRzo2J3cWCBRi"
|
||||
otp := "3JhHkONiyiaNYj14nnD9xZQS"
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
go func() {
|
||||
stdinW.Write([]byte(encoded))
|
||||
stdinW.Close()
|
||||
}()
|
||||
|
||||
ui, cmd := testOperatorGenerateRootCommand(t)
|
||||
cmd.client = client
|
||||
cmd.testStdin = stdinR
|
||||
|
||||
// Simulate piped output to print raw output
|
||||
old := os.Stdout
|
||||
_, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Stdout = w
|
||||
|
||||
code := cmd.Run([]string{
|
||||
"-decode", "-", // read from stdin
|
||||
"-otp", otp,
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
expected := "4RUmoevJ3lsLni9sTXcNnRE1"
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if combined != expected {
|
||||
t.Errorf("expected %q to be %q", combined, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("decode_from_stdin_empty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
encoded := ""
|
||||
otp := "3JhHkONiyiaNYj14nnD9xZQS"
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
go func() {
|
||||
stdinW.Write([]byte(encoded))
|
||||
stdinW.Close()
|
||||
}()
|
||||
|
||||
ui, cmd := testOperatorGenerateRootCommand(t)
|
||||
cmd.client = client
|
||||
cmd.testStdin = stdinR
|
||||
|
||||
// Simulate piped output to print raw output
|
||||
old := os.Stdout
|
||||
_, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Stdout = w
|
||||
|
||||
code := cmd.Run([]string{
|
||||
"-decode", "-", // read from stdin
|
||||
"-otp", otp,
|
||||
})
|
||||
if exp := 1; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
expected := "Missing encoded value"
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cancel", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ flags](/docs/commands) included on all commands.
|
|||
|
||||
- `-decode` `(string: "")` - Decode and output the generated root token. This
|
||||
option requires the `-otp` flag be set to the OTP used during initialization.
|
||||
If value is "-" then read the encoded token from stdin.
|
||||
|
||||
- `-generate-otp` `(bool: false)` - Generate and print a high-entropy
|
||||
one-time-password (OTP) suitable for use with the "-init" flag.
|
||||
|
|
Loading…
Reference in New Issue