github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/terminal/util.go (about) 1 package terminal 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "sync" 12 13 "github.com/sirupsen/logrus" 14 "golang.org/x/crypto/ssh" 15 "golang.org/x/crypto/ssh/knownhosts" 16 "golang.org/x/crypto/ssh/terminal" 17 "k8s.io/client-go/util/homedir" 18 ) 19 20 var ( 21 passPhrase []byte 22 phraseSync sync.Once 23 password []byte 24 passwordSync sync.Once 25 ) 26 27 // ReadPassword prompts for a secret and returns value input by user from stdin 28 // Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported. 29 // Additionally, all input after `<secret>/n` is queued to podman command. 30 func ReadPassword(prompt string) (pw []byte, err error) { 31 fd := int(os.Stdin.Fd()) 32 if terminal.IsTerminal(fd) { 33 fmt.Fprint(os.Stderr, prompt) 34 pw, err = terminal.ReadPassword(fd) 35 fmt.Fprintln(os.Stderr) 36 return 37 } 38 39 var b [1]byte 40 for { 41 n, err := os.Stdin.Read(b[:]) 42 // terminal.ReadPassword discards any '\r', so we do the same 43 if n > 0 && b[0] != '\r' { 44 if b[0] == '\n' { 45 return pw, nil 46 } 47 pw = append(pw, b[0]) 48 // limit size, so that a wrong input won't fill up the memory 49 if len(pw) > 1024 { 50 err = errors.New("password too long, 1024 byte limit") 51 } 52 } 53 if err != nil { 54 // terminal.ReadPassword accepts EOF-terminated passwords 55 // if non-empty, so we do the same 56 if err == io.EOF && len(pw) > 0 { 57 err = nil 58 } 59 return pw, err 60 } 61 } 62 } 63 64 func PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) { 65 key, err := ioutil.ReadFile(path) 66 if err != nil { 67 return nil, err 68 } 69 70 signer, err := ssh.ParsePrivateKey(key) 71 if err != nil { 72 if _, ok := err.(*ssh.PassphraseMissingError); !ok { 73 return nil, err 74 } 75 if len(passphrase) == 0 { 76 passphrase = ReadPassphrase() 77 } 78 signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase) 79 if err != nil { 80 return nil, err 81 } 82 } 83 return ssh.PublicKeys(signer), nil 84 } 85 86 func ReadPassphrase() []byte { 87 phraseSync.Do(func() { 88 secret, err := ReadPassword("Key Passphrase: ") 89 if err != nil { 90 secret = []byte{} 91 } 92 passPhrase = secret 93 }) 94 return passPhrase 95 } 96 97 func ReadLogin() []byte { 98 passwordSync.Do(func() { 99 secret, err := ReadPassword("Login password: ") 100 if err != nil { 101 secret = []byte{} 102 } 103 password = secret 104 }) 105 return password 106 } 107 108 func HostKey(host string) ssh.PublicKey { 109 // parse OpenSSH known_hosts file 110 // ssh or use ssh-keyscan to get initial key 111 knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts") 112 fd, err := os.Open(knownHosts) 113 if err != nil { 114 logrus.Error(err) 115 return nil 116 } 117 118 // support -H parameter for ssh-keyscan 119 hashhost := knownhosts.HashHostname(host) 120 121 scanner := bufio.NewScanner(fd) 122 for scanner.Scan() { 123 _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes()) 124 if err != nil { 125 logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text()) 126 continue 127 } 128 129 for _, h := range hosts { 130 if h == host || h == hashhost { 131 return key 132 } 133 } 134 } 135 136 return nil 137 }