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:
Dave Du Cros 2021-10-20 17:29:17 +01:00 committed by GitHub
parent b76d2cd09c
commit ceac6e913d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 1 deletions

3
changelog/12881.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
command: operator generate-root -decode: allow passing encoded token via stdin
```

View File

@ -130,7 +130,8 @@ func (c *OperatorGenerateRootCommand) Flags() *FlagSets {
Default: "", Default: "",
EnvVar: "", EnvVar: "",
Completion: complete.PredictAnything, 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{ f.BoolVar(&BoolVar{
@ -328,6 +329,27 @@ func (c *OperatorGenerateRootCommand) decode(client *api.Client, encoded, otp st
return 1 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 f := client.Sys().GenerateRootStatus
switch kind { switch kind {
case generateRootDR: case generateRootDR:

View File

@ -1,3 +1,4 @@
//go:build !race
// +build !race // +build !race
package command 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.Run("cancel", func(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -68,6 +68,7 @@ flags](/docs/commands) included on all commands.
- `-decode` `(string: "")` - Decode and output the generated root token. This - `-decode` `(string: "")` - Decode and output the generated root token. This
option requires the `-otp` flag be set to the OTP used during initialization. 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 - `-generate-otp` `(bool: false)` - Generate and print a high-entropy
one-time-password (OTP) suitable for use with the "-init" flag. one-time-password (OTP) suitable for use with the "-init" flag.