github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/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/containers/storage/pkg/homedir" 14 "github.com/sirupsen/logrus" 15 "golang.org/x/crypto/ssh" 16 "golang.org/x/crypto/ssh/knownhosts" 17 "golang.org/x/term" 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 term.IsTerminal(fd) { 33 fmt.Fprint(os.Stderr, prompt) 34 pw, err = term.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.Signer, 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 return ssh.ParsePrivateKeyWithPassphrase(key, passphrase) 79 } 80 return signer, nil 81 } 82 83 func ReadPassphrase() []byte { 84 phraseSync.Do(func() { 85 secret, err := ReadPassword("Key Passphrase: ") 86 if err != nil { 87 secret = []byte{} 88 } 89 passPhrase = secret 90 }) 91 return passPhrase 92 } 93 94 func ReadLogin() []byte { 95 passwordSync.Do(func() { 96 secret, err := ReadPassword("Login password: ") 97 if err != nil { 98 secret = []byte{} 99 } 100 password = secret 101 }) 102 return password 103 } 104 105 func HostKey(host string) ssh.PublicKey { 106 // parse OpenSSH known_hosts file 107 // ssh or use ssh-keyscan to get initial key 108 knownHosts := filepath.Join(homedir.Get(), ".ssh", "known_hosts") 109 fd, err := os.Open(knownHosts) 110 if err != nil { 111 logrus.Error(err) 112 return nil 113 } 114 115 // support -H parameter for ssh-keyscan 116 hashhost := knownhosts.HashHostname(host) 117 118 scanner := bufio.NewScanner(fd) 119 for scanner.Scan() { 120 _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes()) 121 if err != nil { 122 logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text()) 123 continue 124 } 125 126 for _, h := range hosts { 127 if h == host || h == hashhost { 128 return key 129 } 130 } 131 } 132 133 return nil 134 }