package terminal import ( "bufio" "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" "sync" "github.com/containers/storage/pkg/homedir" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" "golang.org/x/term" ) 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 `/n` is queued to podman command. func ReadPassword(prompt string) (pw []byte, err error) { fd := int(os.Stdin.Fd()) if term.IsTerminal(fd) { fmt.Fprint(os.Stderr, prompt) pw, err = term.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.Signer, 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() } return ssh.ParsePrivateKeyWithPassphrase(key, passphrase) } return 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.Get(), ".ssh", "known_hosts") fd, err := os.Open(knownHosts) if err != nil { logrus.Error(err) return nil } // support -H parameter for ssh-keyscan hashhost := knownhosts.HashHostname(host) 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 || h == hashhost { return key } } } return nil }