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  }