mirror of
https://github.com/containers/podman.git
synced 2025-05-17 23:26:08 +08:00

* Move all public key handling into one AuthMethod. Prioritize ssh-agent keys over identity files. * Cache server connection when tunneling, saves one RoundTrip on ssh handshake Signed-off-by: Jhon Honce <jhonce@redhat.com>
135 lines
2.8 KiB
Go
135 lines
2.8 KiB
Go
package terminal
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/crypto/ssh/knownhosts"
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
"k8s.io/client-go/util/homedir"
|
|
)
|
|
|
|
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 `<secret>/n` is queued to podman command.
|
|
func ReadPassword(prompt string) (pw []byte, err error) {
|
|
fd := int(os.Stdin.Fd())
|
|
if terminal.IsTerminal(fd) {
|
|
fmt.Fprint(os.Stderr, prompt)
|
|
pw, err = terminal.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.HomeDir(), ".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
|
|
}
|