-
Notifications
You must be signed in to change notification settings - Fork 50
/
ssh.go
241 lines (203 loc) · 5.86 KB
/
ssh.go
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package main
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"os/user"
"path/filepath"
"syscall"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/terminal"
)
// sshSession stores the open session and connection to execute a command.
type sshSession struct {
// conn is the ssh client that started the session.
conn *ssh.Client
*ssh.Session
}
// Close closses the open ssh session and connection.
func (s *sshSession) Close() {
s.Session.Close()
s.conn.Close()
}
// sshClientConfig stores the configuration and the ssh agent to forward authentication requests
type sshClientConfig struct {
// agent is the connection to the ssh agent
agent agent.Agent
// host to connect to
host string
*ssh.ClientConfig
}
// getEffectiveClientOptions return the merged copy of the given options from SSH client config file and CLI.
// Note, 'User' and 'IdentityFile' SSH client options passed from '--option' are ignored, user should use '--user' and '--identity' flag accordingly.
func getEffectiveClientOptions(configFileOptions, cliOptions SSHClientOptions) SSHClientOptions {
options := configFileOptions // configFileOptions is passed by value, it is safe to modify and return the copy.
if cliOptions.ForwardAgent != "" {
options.ForwardAgent = cliOptions.ForwardAgent
}
if cliOptions.HostName != "" {
options.HostName = cliOptions.HostName
}
if cliOptions.Port != "" {
options.Port = cliOptions.Port
}
if cliOptions.ProxyCommand != "" {
options.ProxyCommand = cliOptions.ProxyCommand
}
return options
}
// newSSHClientConfig initializes per-host SSH configuration.
func newSSHClientConfig(user, host string, agt agent.Agent, method ssh.AuthMethod) *sshClientConfig {
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{method},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
return &sshClientConfig{
agent: agt,
host: host,
ClientConfig: config,
}
}
// NewSession creates a new ssh session with the host.
// It forwards authentication to the agent when it's configured.
func (s *sshClientConfig) NewSession(options SSHClientOptions) (*sshSession, error) {
var (
conn *ssh.Client
err error
)
if options.ProxyCommand != "" {
cmdConn, err := NewProxyCmdConn(s, options.ProxyCommand)
if err != nil {
return nil, err
}
c, chans, reqs, err := ssh.NewClientConn(cmdConn, "", s.ClientConfig)
if err != nil {
return nil, err
}
conn = ssh.NewClient(c, chans, reqs)
} else {
conn, err = ssh.Dial("tcp", s.host, s.ClientConfig)
if err != nil {
return nil, err
}
}
if s.agent != nil {
if err := agent.ForwardToAgent(conn, s.agent); err != nil {
return nil, err
}
}
session, err := conn.NewSession()
if s.agent != nil {
err = agent.RequestAgentForwarding(session)
}
return &sshSession{
conn: conn,
Session: session,
}, err
}
// newAgent connects with the SSH agent in the to forward authentication requests.
func newAgent() (agent.Agent, error) {
sock := os.Getenv("SSH_AUTH_SOCK")
if sock == "" {
return nil, errors.New("Unable to connect to the ssh agent. Please, check that SSH_AUTH_SOCK is set and the ssh agent is running")
}
conn, err := net.Dial("unix", sock)
if err != nil {
return nil, err
}
return agent.NewClient(conn), nil
}
// defaultAuthMethods initializes all the available SSH authentication methods.
// By default, it uses ~/.ssh/id_dsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ed25519,
// and ~/.ssh/id_rsa for authentication.
func defaultAuthMethods(identityFiles []string, agt agent.Agent) map[string]ssh.AuthMethod {
methods := make(map[string]ssh.AuthMethod)
if len(identityFiles) == 0 {
u, err := user.Current()
if err == nil {
identityFiles = []string{
filepath.Join(u.HomeDir, ".ssh", "id_dsa"),
filepath.Join(u.HomeDir, ".ssh", "id_ecdsa"),
filepath.Join(u.HomeDir, ".ssh", "id_ed25519"),
filepath.Join(u.HomeDir, ".ssh", "id_rsa"),
}
}
}
if agt != nil {
if m, err := newSSHAgentAuthMethod(agt); err == nil {
methods["ssh-agent"] = m
}
}
for _, i := range identityFiles {
if m, err := newSSHPublicKeyAuthMethod(i); err == nil {
methods[i] = m
} else {
log.Debugf("Failed to load identity file %s", i)
}
}
return methods
}
// newSSHAgentAuthMethod creates a new SSH authentication method using SSH agent
func newSSHAgentAuthMethod(agt agent.Agent) (ssh.AuthMethod, error) {
signers, err := agt.Signers()
if err != nil {
return nil, err
}
return ssh.PublicKeys(signers...), nil
}
// newSSHPublicKeyAuthMethod creates a new SSH authentication method using public/private key
func newSSHPublicKeyAuthMethod(identityFile string) (ssh.AuthMethod, error) {
contents, err := ioutil.ReadFile(identityFile)
if err != nil {
return nil, err
}
// handle plain and encrypted private key file
block, _ := pem.Decode(contents)
if block == nil {
return nil, fmt.Errorf("cannot decode identity file %s", identityFile)
}
var signer ssh.Signer
if x509.IsEncryptedPEMBlock(block) {
fmt.Print("Key passphrase: ")
pass, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return nil, err
}
fmt.Println("")
block.Bytes, err = x509.DecryptPEMBlock(block, pass)
if err != nil {
return nil, err
}
var key interface{}
switch block.Type {
case "RSA PRIVATE KEY":
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
case "EC PRIVATE KEY":
key, err = x509.ParseECPrivateKey(block.Bytes)
case "DSA PRIVATE KEY":
key, err = ssh.ParseDSAPrivateKey(block.Bytes)
default:
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
if err != nil {
return nil, err
}
signer, err = ssh.NewSignerFromKey(key)
if err != nil {
return nil, err
}
} else {
signer, err = ssh.ParsePrivateKey(contents)
if err != nil {
return nil, err
}
}
return ssh.PublicKeys(signer), nil
}