-
Notifications
You must be signed in to change notification settings - Fork 162
/
ssh_args.go
164 lines (132 loc) · 4.38 KB
/
ssh_args.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
package ssh
import (
"fmt"
"net"
"strings"
boshdir "github.com/cloudfoundry/bosh-cli/director"
boshhttp "github.com/cloudfoundry/bosh-utils/httpclient"
boshlog "github.com/cloudfoundry/bosh-utils/logger"
boshsys "github.com/cloudfoundry/bosh-utils/system"
proxy "github.com/cloudfoundry/socks5-proxy"
)
type SSHArgs struct {
ConnOpts ConnectionOpts
Result boshdir.SSHResult
ForceTTY bool
PrivKeyFile boshsys.File
KnownHostsFile boshsys.File
CmdExistenceChecker cmdExistenceChecker
Socks5Proxy *proxy.Socks5Proxy
dialer proxy.DialFunc
}
type cmdExistenceChecker interface {
CommandExists(cmdName string) (exists bool)
}
func NewSSHArgs(connOpts ConnectionOpts, result boshdir.SSHResult, forceTTY bool, privKeyFile boshsys.File, knownHostsFile boshsys.File) SSHArgs {
cmdRunner := boshsys.NewExecCmdRunner(boshlog.NewLogger(boshlog.LevelNone))
socks5Proxy := proxy.NewSocks5Proxy(proxy.NewHostKey(), nil)
boshhttpDialer := boshhttp.SOCKS5DialFuncFromEnvironment(net.Dial, socks5Proxy)
dialer := func(net, addr string) (net.Conn, error) {
return boshhttpDialer(net, addr)
}
return SSHArgs{
ConnOpts: connOpts,
Result: result,
ForceTTY: forceTTY,
PrivKeyFile: privKeyFile,
CmdExistenceChecker: cmdRunner,
KnownHostsFile: knownHostsFile,
Socks5Proxy: socks5Proxy,
dialer: dialer,
}
}
func (a SSHArgs) LoginForHost(host boshdir.Host) []string {
return []string{host.Host, "-l", host.Username}
}
func formProxyOpt(existenceChecker cmdExistenceChecker, proxyHostString string) string {
if existenceChecker.CommandExists("connect-proxy") {
return fmtAsProxyCommandOpt("connect-proxy -S %s %%h %%p", proxyHostString)
}
return fmtAsProxyCommandOpt("nc -x %s %%h %%p", proxyHostString)
}
func fmtAsProxyCommandOpt(command, proxyHost string) string {
return fmt.Sprintf("ProxyCommand=%s", fmt.Sprintf(command, proxyHost))
}
func (a SSHArgs) OptsForHost(host boshdir.Host) []string {
// Options are used for both ssh and scp
cmdOpts := []string{}
if a.ForceTTY {
cmdOpts = append(cmdOpts, "-tt")
}
cmdOpts = append(cmdOpts, []string{
"-o", "ServerAliveInterval=30",
"-o", "ForwardAgent=no",
"-o", "PasswordAuthentication=no",
"-o", "IdentitiesOnly=yes",
"-o", "IdentityFile=" + a.PrivKeyFile.Name(),
"-o", "StrictHostKeyChecking=yes",
"-o", "UserKnownHostsFile=" + a.KnownHostsFile.Name(),
}...)
gwUsername, gwHost, gwPrivKeyPath := a.gwOpts()
if len(a.ConnOpts.SOCKS5Proxy) > 0 {
proxyString := a.ConnOpts.SOCKS5Proxy
if strings.HasPrefix(proxyString, "ssh+") {
a.Socks5Proxy.StartWithDialer(a.dialer)
proxyString, _ = a.Socks5Proxy.Addr()
}
proxyOpt := formProxyOpt(a.CmdExistenceChecker, strings.TrimPrefix(proxyString, "socks5://"))
cmdOpts = append(cmdOpts, "-o", proxyOpt)
} else if len(gwHost) > 0 {
gwCmdOpts := []string{
"-o", "ServerAliveInterval=30",
"-o", "ForwardAgent=no",
"-o", "ClearAllForwardings=yes",
// Strict host key checking for a gateway is not necessary
// since ProxyCommand is only used for forwarding TCP and
// agent forwarding is disabled
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
}
if len(gwPrivKeyPath) > 0 {
gwCmdOpts = append(
gwCmdOpts,
"-o", "PasswordAuthentication=no",
"-o", "IdentitiesOnly=yes",
"-o", "IdentityFile="+gwPrivKeyPath,
)
}
// It appears that when using ssh -W, IPv6 address needs to be put in brackets
// fixes: `Bad stdio forwarding specification 'fd7a:eeed:e696:...'`
proxyHostPortTmpl := "%h:%p"
if strings.Contains(host.Host, ":") {
proxyHostPortTmpl = "[%h]:%p"
}
proxyOpt := fmt.Sprintf(
// Always force TTY for gateway ssh
"ProxyCommand=ssh -tt -W %s -l %s %s %s",
proxyHostPortTmpl,
gwUsername,
gwHost,
strings.Join(gwCmdOpts, " "),
)
cmdOpts = append(cmdOpts, "-o", proxyOpt)
}
cmdOpts = append(cmdOpts, a.ConnOpts.RawOpts...)
return cmdOpts
}
func (a SSHArgs) gwOpts() (string, string, string) {
if a.ConnOpts.GatewayDisable {
return "", "", ""
}
// Take server provided gateway options
username := a.Result.GatewayUsername
host := a.Result.GatewayHost
if len(a.ConnOpts.GatewayUsername) > 0 {
username = a.ConnOpts.GatewayUsername
}
if len(a.ConnOpts.GatewayHost) > 0 {
host = a.ConnOpts.GatewayHost
}
privKeyPath := a.ConnOpts.GatewayPrivateKeyPath
return username, host, privKeyPath
}