Skip to content

Commit

Permalink
operator generate-root -decode: allow token from stdin (hashicorp#12881)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
davidducros authored and Artem Alexandrov committed Feb 4, 2022
1 parent 326797d commit 7ba02a6
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 1 deletion.
3 changes: 3 additions & 0 deletions changelog/12881.txt
@@ -0,0 +1,3 @@
```release-note:improvement
command: operator generate-root -decode: allow passing encoded token via stdin
```
24 changes: 23 additions & 1 deletion command/operator_generate_root.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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:
Expand Down
91 changes: 91 additions & 0 deletions command/operator_generate_root_test.go
@@ -1,3 +1,4 @@
//go:build !race
// +build !race

package command
Expand Down Expand Up @@ -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()

Expand Down
1 change: 1 addition & 0 deletions website/content/docs/commands/operator/generate-root.mdx
Expand Up @@ -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.
Expand Down

0 comments on commit 7ba02a6

Please sign in to comment.