Skip to content

Commit cf4faa4

Browse files
karalabefjl
authored andcommitted
cmd/puppeth, vendor: update ssh, manage server keys (#14398)
1 parent 5996625 commit cf4faa4

File tree

19 files changed

+701
-285
lines changed

19 files changed

+701
-285
lines changed

cmd/puppeth/ssh.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package main
1818

1919
import (
20+
"bufio"
21+
"bytes"
2022
"errors"
2123
"fmt"
2224
"io/ioutil"
@@ -37,25 +39,36 @@ import (
3739
type sshClient struct {
3840
server string // Server name or IP without port number
3941
address string // IP address of the remote server
42+
pubkey []byte // RSA public key to authenticate the server
4043
client *ssh.Client
4144
logger log.Logger
4245
}
4346

4447
// dial establishes an SSH connection to a remote node using the current user and
45-
// the user's configured private RSA key.
46-
func dial(server string) (*sshClient, error) {
48+
// the user's configured private RSA key. If that fails, password authentication
49+
// is fallen back to. The caller may override the login user via user@server:port.
50+
func dial(server string, pubkey []byte) (*sshClient, error) {
4751
// Figure out a label for the server and a logger
4852
label := server
4953
if strings.Contains(label, ":") {
5054
label = label[:strings.Index(label, ":")]
5155
}
56+
login := ""
57+
if strings.Contains(server, "@") {
58+
login = label[:strings.Index(label, "@")]
59+
label = label[strings.Index(label, "@")+1:]
60+
server = server[strings.Index(server, "@")+1:]
61+
}
5262
logger := log.New("server", label)
5363
logger.Debug("Attempting to establish SSH connection")
5464

5565
user, err := user.Current()
5666
if err != nil {
5767
return nil, err
5868
}
69+
if login == "" {
70+
login = user.Username
71+
}
5972
// Configure the supported authentication methods (private key and password)
6073
var auths []ssh.AuthMethod
6174

@@ -71,7 +84,7 @@ func dial(server string) (*sshClient, error) {
7184
}
7285
}
7386
auths = append(auths, ssh.PasswordCallback(func() (string, error) {
74-
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", user.Username, server)
87+
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server)
7588
blob, err := terminal.ReadPassword(int(syscall.Stdin))
7689

7790
fmt.Println()
@@ -86,18 +99,44 @@ func dial(server string) (*sshClient, error) {
8699
return nil, errors.New("no IPs associated with domain")
87100
}
88101
// Try to dial in to the remote server
89-
logger.Trace("Dialing remote SSH server", "user", user.Username, "key", path)
102+
logger.Trace("Dialing remote SSH server", "user", login)
90103
if !strings.Contains(server, ":") {
91104
server += ":22"
92105
}
93-
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: user.Username, Auth: auths})
106+
keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
107+
// If no public key is known for SSH, ask the user to confirm
108+
if pubkey == nil {
109+
fmt.Printf("The authenticity of host '%s (%s)' can't be established.\n", hostname, remote)
110+
fmt.Printf("SSH key fingerprint is %s [MD5]\n", ssh.FingerprintLegacyMD5(key))
111+
fmt.Printf("Are you sure you want to continue connecting (yes/no)? ")
112+
113+
text, err := bufio.NewReader(os.Stdin).ReadString('\n')
114+
switch {
115+
case err != nil:
116+
return err
117+
case strings.TrimSpace(text) == "yes":
118+
pubkey = key.Marshal()
119+
return nil
120+
default:
121+
return fmt.Errorf("unknown auth choice: %v", text)
122+
}
123+
}
124+
// If a public key exists for this SSH server, check that it matches
125+
if bytes.Compare(pubkey, key.Marshal()) == 0 {
126+
return nil
127+
}
128+
// We have a mismatch, forbid connecting
129+
return errors.New("ssh key mismatch, readd the machine to update")
130+
}
131+
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: login, Auth: auths, HostKeyCallback: keycheck})
94132
if err != nil {
95133
return nil, err
96134
}
97135
// Connection established, return our utility wrapper
98136
c := &sshClient{
99137
server: label,
100138
address: addr[0],
139+
pubkey: pubkey,
101140
client: client,
102141
logger: logger,
103142
}

cmd/puppeth/wizard.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,24 @@ type config struct {
4444
bootLight []string // Bootnodes to always connect to by light nodes
4545
ethstats string // Ethstats settings to cache for node deploys
4646

47-
Servers []string `json:"servers,omitempty"`
47+
Servers map[string][]byte `json:"servers,omitempty"`
48+
}
49+
50+
// servers retrieves an alphabetically sorted list of servers.
51+
func (c config) servers() []string {
52+
servers := make([]string, 0, len(c.Servers))
53+
for server := range c.Servers {
54+
servers = append(servers, server)
55+
}
56+
sort.Strings(servers)
57+
58+
return servers
4859
}
4960

5061
// flush dumps the contents of config to disk.
5162
func (c config) flush() {
5263
os.MkdirAll(filepath.Dir(c.path), 0755)
5364

54-
sort.Strings(c.Servers)
5565
out, _ := json.MarshalIndent(c, "", " ")
5666
if err := ioutil.WriteFile(c.path, out, 0644); err != nil {
5767
log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)

cmd/puppeth/wizard_intro.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ import (
3131
// makeWizard creates and returns a new puppeth wizard.
3232
func makeWizard(network string) *wizard {
3333
return &wizard{
34-
network: network,
34+
network: network,
35+
conf: config{
36+
Servers: make(map[string][]byte),
37+
},
3538
servers: make(map[string]*sshClient),
3639
services: make(map[string][]string),
3740
in: bufio.NewReader(os.Stdin),
@@ -77,9 +80,9 @@ func (w *wizard) run() {
7780
} else if err := json.Unmarshal(blob, &w.conf); err != nil {
7881
log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
7982
} else {
80-
for _, server := range w.conf.Servers {
83+
for server, pubkey := range w.conf.Servers {
8184
log.Info("Dialing previously configured server", "server", server)
82-
client, err := dial(server)
85+
client, err := dial(server, pubkey)
8386
if err != nil {
8487
log.Error("Previous server unreachable", "server", server, "err", err)
8588
}

cmd/puppeth/wizard_netstats.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ func (w *wizard) networkStats(tips bool) {
4141
stats.SetHeader([]string{"Server", "IP", "Status", "Service", "Details"})
4242
stats.SetColWidth(128)
4343

44-
for _, server := range w.conf.Servers {
44+
for server, pubkey := range w.conf.Servers {
4545
client := w.servers[server]
4646
logger := log.New("server", server)
4747
logger.Info("Starting remote server health-check")
4848

4949
// If the server is not connected, try to connect again
5050
if client == nil {
51-
conn, err := dial(server)
51+
conn, err := dial(server, pubkey)
5252
if err != nil {
5353
logger.Error("Failed to establish remote connection", "err", err)
5454
stats.Append([]string{server, "", err.Error(), "", ""})

cmd/puppeth/wizard_network.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ import (
2828
func (w *wizard) manageServers() {
2929
// List all the servers we can disconnect, along with an entry to connect a new one
3030
fmt.Println()
31-
for i, server := range w.conf.Servers {
31+
32+
servers := w.conf.servers()
33+
for i, server := range servers {
3234
fmt.Printf(" %d. Disconnect %s\n", i+1, server)
3335
}
3436
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
@@ -40,14 +42,14 @@ func (w *wizard) manageServers() {
4042
}
4143
// If the user selected an existing server, drop it
4244
if choice <= len(w.conf.Servers) {
43-
server := w.conf.Servers[choice-1]
45+
server := servers[choice-1]
4446
client := w.servers[server]
4547

4648
delete(w.servers, server)
4749
if client != nil {
4850
client.Close()
4951
}
50-
w.conf.Servers = append(w.conf.Servers[:choice-1], w.conf.Servers[choice:]...)
52+
delete(w.conf.Servers, server)
5153
w.conf.flush()
5254

5355
log.Info("Disconnected existing server", "server", server)
@@ -73,14 +75,14 @@ func (w *wizard) makeServer() string {
7375
// Read and fial the server to ensure docker is present
7476
input := w.readString()
7577

76-
client, err := dial(input)
78+
client, err := dial(input, nil)
7779
if err != nil {
7880
log.Error("Server not ready for puppeth", "err", err)
7981
return ""
8082
}
8183
// All checks passed, start tracking the server
8284
w.servers[input] = client
83-
w.conf.Servers = append(w.conf.Servers, input)
85+
w.conf.Servers[input] = client.pubkey
8486
w.conf.flush()
8587

8688
return input
@@ -93,7 +95,9 @@ func (w *wizard) selectServer() string {
9395
// List the available server to the user and wait for a choice
9496
fmt.Println()
9597
fmt.Println("Which server do you want to interact with?")
96-
for i, server := range w.conf.Servers {
98+
99+
servers := w.conf.servers()
100+
for i, server := range servers {
97101
fmt.Printf(" %d. %s\n", i+1, server)
98102
}
99103
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
@@ -105,7 +109,7 @@ func (w *wizard) selectServer() string {
105109
}
106110
// If the user requested connecting to a new server, go for it
107111
if choice <= len(w.conf.Servers) {
108-
return w.conf.Servers[choice-1]
112+
return servers[choice-1]
109113
}
110114
return w.makeServer()
111115
}

vendor/golang.org/x/crypto/curve25519/cswap_amd64.s

Lines changed: 54 additions & 77 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)