From 4aeb7a57a13aac6a93e1de273891a851de4dff89 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 2 Nov 2022 17:01:46 -0700 Subject: [PATCH 1/2] Add windows support for read a password It adds a new internal library borrowed from age that prompts for a password. --- cmd/root.go | 9 ++--- internal/termutil/tui.go | 71 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 internal/termutil/tui.go diff --git a/cmd/root.go b/cmd/root.go index 77ea492..c8d4fe3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,13 +15,12 @@ package cmd import ( "errors" - "fmt" "os" - "syscall" "github.com/spf13/cobra" "go.step.sm/crypto/pemutil" - "golang.org/x/term" + + "github.com/smallstep/step-kms-plugin/internal/termutil" ) // rootCmd represents the base command when called without any subcommands @@ -68,8 +67,6 @@ func init() { if s[len(s)-1] != ':' { s += ":" } - fmt.Fprint(os.Stderr, s+" ") - defer fmt.Fprintln(os.Stderr) - return term.ReadPassword(syscall.Stderr) + return termutil.ReadPassword(s) } } diff --git a/internal/termutil/tui.go b/internal/termutil/tui.go new file mode 100644 index 0000000..2c980b2 --- /dev/null +++ b/internal/termutil/tui.go @@ -0,0 +1,71 @@ +// Copyright 2021 The age Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package termutil + +import ( + "fmt" + "io" + "os" + "runtime" + + "golang.org/x/term" +) + +// clearLine clears the current line on the terminal, or opens a new line if +// terminal escape codes don't work. +func clearLine(out io.Writer) { + const ( + CUI = "\033[" // Control Sequence Introducer + CPL = CUI + "F" // Cursor Previous Line + EL = CUI + "K" // Erase in Line + ) + + // First, open a new line, which is guaranteed to work everywhere. Then, try + // to erase the line above with escape codes. + // + // (We use CRLF instead of LF to work around an apparent bug in WSL2's + // handling of CONOUT$. Only when running a Windows binary from WSL2, the + // cursor would not go back to the start of the line with a simple LF. + // Honestly, it's impressive CONIN$ and CONOUT$ work at all inside WSL2.) + fmt.Fprintf(out, "\r\n"+CPL+EL) +} + +// withTerminal runs f with the terminal input and output files, if available. +// withTerminal does not open a non-terminal stdin, so the caller does not need +// to check stdinInUse. +func withTerminal(f func(in, out *os.File) error) error { + if runtime.GOOS == "windows" { + in, err := os.OpenFile("CONIN$", os.O_RDWR, 0) + if err != nil { + return err + } + defer in.Close() + out, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0) + if err != nil { + return err + } + defer out.Close() + return f(in, out) + } else if tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0); err == nil { + defer tty.Close() + return f(tty, tty) + } else if term.IsTerminal(int(os.Stdin.Fd())) { + return f(os.Stdin, os.Stdin) + } else { + return fmt.Errorf("standard input is not a terminal, and /dev/tty is not available: %v", err) + } +} + +// ReadPassword reads a value from the terminal with no echo. The prompt is +// ephemeral. +func ReadPassword(prompt string) (s []byte, err error) { + err = withTerminal(func(in, out *os.File) error { + fmt.Fprintf(out, "%s ", prompt) + defer clearLine(out) + s, err = term.ReadPassword(int(in.Fd())) + return err + }) + return +} From 99009432e30569188387c3ef50f655467aa4a9cd Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 2 Nov 2022 17:12:48 -0700 Subject: [PATCH 2/2] Nolint tui.go --- internal/termutil/tui.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/termutil/tui.go b/internal/termutil/tui.go index 2c980b2..0400aa5 100644 --- a/internal/termutil/tui.go +++ b/internal/termutil/tui.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//nolint:gocritic,errorlint // this file is borrowed from age package termutil import (