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  }