1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
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
}
|