diff options
Diffstat (limited to 'pkg/terminal')
-rw-r--r-- | pkg/terminal/util.go | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/pkg/terminal/util.go b/pkg/terminal/util.go new file mode 100644 index 000000000..ab3dc54e4 --- /dev/null +++ b/pkg/terminal/util.go @@ -0,0 +1,133 @@ +package terminal + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sync" + + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/terminal" + "k8s.io/client-go/util/homedir" +) + +var ( + passPhrase []byte + phraseSync sync.Once + password []byte + passwordSync sync.Once +) + +// ReadPassword prompts for a secret and returns value input by user from stdin +// Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported. +// Additionally, all input after `<secret>/n` is queued to podman command. +func ReadPassword(prompt string) (pw []byte, err error) { + fd := int(os.Stdin.Fd()) + if terminal.IsTerminal(fd) { + fmt.Fprint(os.Stderr, prompt) + pw, err = terminal.ReadPassword(fd) + fmt.Fprintln(os.Stderr) + return + } + + var b [1]byte + for { + n, err := os.Stdin.Read(b[:]) + // terminal.ReadPassword discards any '\r', so we do the same + if n > 0 && b[0] != '\r' { + if b[0] == '\n' { + return pw, nil + } + pw = append(pw, b[0]) + // limit size, so that a wrong input won't fill up the memory + if len(pw) > 1024 { + err = errors.New("password too long, 1024 byte limit") + } + } + if err != nil { + // terminal.ReadPassword accepts EOF-terminated passwords + // if non-empty, so we do the same + if err == io.EOF && len(pw) > 0 { + err = nil + } + return pw, err + } + } +} + +func PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) { + key, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + if _, ok := err.(*ssh.PassphraseMissingError); !ok { + return nil, err + } + if len(passphrase) == 0 { + passphrase = ReadPassphrase() + } + signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase) + if err != nil { + return nil, err + } + } + return ssh.PublicKeys(signer), nil +} + +func ReadPassphrase() []byte { + phraseSync.Do(func() { + secret, err := ReadPassword("Key Passphrase: ") + if err != nil { + secret = []byte{} + } + passPhrase = secret + }) + return passPhrase +} + +func ReadLogin() []byte { + passwordSync.Do(func() { + secret, err := ReadPassword("Login password: ") + if err != nil { + secret = []byte{} + } + password = secret + }) + return password +} + +func HostKey(host string) ssh.PublicKey { + // parse OpenSSH known_hosts file + // ssh or use ssh-keyscan to get initial key + knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts") + fd, err := os.Open(knownHosts) + if err != nil { + logrus.Error(err) + return nil + } + + scanner := bufio.NewScanner(fd) + for scanner.Scan() { + _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes()) + if err != nil { + logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text()) + continue + } + + for _, h := range hosts { + if h == host { + return key + } + } + } + + return nil +} |