-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdialer.go
220 lines (193 loc) · 7.28 KB
/
dialer.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
package hosts
import (
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/rancher/rke/k8s"
"github.com/rancher/types/apis/management.cattle.io/v3"
"golang.org/x/crypto/ssh"
)
const (
DockerDialerTimeout = 50
)
type DialerFactory func(h *Host) (func(network, address string) (net.Conn, error), error)
type dialer struct {
signer ssh.Signer
sshKeyString string
sshAddress string
username string
netConn string
dockerSocket string
useSSHAgentAuth bool
bastionDialer *dialer
}
func newDialer(h *Host, kind string) (*dialer, error) {
// Check for Bastion host connection
var bastionDialer *dialer
if len(h.BastionHost.Address) > 0 {
bastionDialer = &dialer{
sshAddress: fmt.Sprintf("%s:%s", h.BastionHost.Address, h.BastionHost.Port),
username: h.BastionHost.User,
sshKeyString: h.BastionHost.SSHKey,
netConn: "tcp",
useSSHAgentAuth: h.SSHAgentAuth,
}
if bastionDialer.sshKeyString == "" {
var err error
bastionDialer.sshKeyString, err = privateKeyPath(h.BastionHost.SSHKeyPath)
if err != nil {
return nil, err
}
}
}
dialer := &dialer{
sshAddress: fmt.Sprintf("%s:%s", h.Address, h.Port),
username: h.User,
dockerSocket: h.DockerSocket,
sshKeyString: h.SSHKey,
netConn: "unix",
useSSHAgentAuth: h.SSHAgentAuth,
bastionDialer: bastionDialer,
}
if dialer.sshKeyString == "" {
var err error
dialer.sshKeyString, err = privateKeyPath(h.SSHKeyPath)
if err != nil {
return nil, err
}
}
switch kind {
case "network", "health":
dialer.netConn = "tcp"
}
if len(dialer.dockerSocket) == 0 {
dialer.dockerSocket = "/var/run/docker.sock"
}
return dialer, nil
}
func SSHFactory(h *Host) (func(network, address string) (net.Conn, error), error) {
dialer, err := newDialer(h, "docker")
return dialer.Dial, err
}
func LocalConnFactory(h *Host) (func(network, address string) (net.Conn, error), error) {
dialer, err := newDialer(h, "network")
return dialer.Dial, err
}
func (d *dialer) DialDocker(network, addr string) (net.Conn, error) {
return d.Dial(network, addr)
}
func (d *dialer) DialLocalConn(network, addr string) (net.Conn, error) {
return d.Dial(network, addr)
}
func (d *dialer) Dial(network, addr string) (net.Conn, error) {
var conn *ssh.Client
var err error
if d.bastionDialer != nil {
conn, err = d.getBastionHostTunnelConn()
} else {
conn, err = d.getSSHTunnelConnection()
}
if err != nil {
if strings.Contains(err.Error(), "no key found") {
return nil, fmt.Errorf("Unable to access node with address [%s] using SSH. Please check if the configured key or specified key file is a valid SSH Private Key. Error: %v", d.sshAddress, err)
} else if strings.Contains(err.Error(), "no supported methods remain") {
return nil, fmt.Errorf("Unable to access node with address [%s] using SSH. Please check if you are able to SSH to the node using the specified SSH Private Key and if you have configured the correct SSH username. Error: %v", d.sshAddress, err)
} else if strings.Contains(err.Error(), "cannot decode encrypted private keys") {
return nil, fmt.Errorf("Unable to access node with address [%s] using SSH. Using encrypted private keys is only supported using ssh-agent. Please configure the option `ssh_agent_auth: true` in the configuration file or use --ssh-agent-auth as a parameter when running RKE. This will use the `SSH_AUTH_SOCK` environment variable. Error: %v", d.sshAddress, err)
} else if strings.Contains(err.Error(), "operation timed out") {
return nil, fmt.Errorf("Unable to access node with address [%s] using SSH. Please check if the node is up and is accepting SSH connections or check network policies and firewall rules. Error: %v", d.sshAddress, err)
}
return nil, fmt.Errorf("Failed to dial ssh using address [%s]: %v", d.sshAddress, err)
}
// Docker Socket....
if d.netConn == "unix" {
addr = d.dockerSocket
network = d.netConn
}
remote, err := conn.Dial(network, addr)
if err != nil {
if strings.Contains(err.Error(), "connect failed") {
return nil, fmt.Errorf("Unable to access the service on %s. The service might be still starting up. Error: %v", addr, err)
} else if strings.Contains(err.Error(), "administratively prohibited") {
return nil, fmt.Errorf("Unable to access the Docker socket (%s). Please check if the configured user can execute `docker ps` on the node, and if the SSH server version is at least version 6.7 or higher. If you are using RedHat/CentOS, you can't use the user `root`. Please refer to the documentation for more instructions. Error: %v", addr, err)
}
return nil, fmt.Errorf("Failed to dial to %s: %v", addr, err)
}
return remote, err
}
func (d *dialer) getSSHTunnelConnection() (*ssh.Client, error) {
cfg, err := getSSHConfig(d.username, d.sshKeyString, d.useSSHAgentAuth)
if err != nil {
return nil, fmt.Errorf("Error configuring SSH: %v", err)
}
// Establish connection with SSH server
return ssh.Dial("tcp", d.sshAddress, cfg)
}
func (h *Host) newHTTPClient(dialerFactory DialerFactory) (*http.Client, error) {
factory := dialerFactory
if factory == nil {
factory = SSHFactory
}
dialer, err := factory(h)
if err != nil {
return nil, err
}
dockerDialerTimeout := time.Second * DockerDialerTimeout
return &http.Client{
Transport: &http.Transport{
Dial: dialer,
TLSHandshakeTimeout: dockerDialerTimeout,
IdleConnTimeout: dockerDialerTimeout,
ResponseHeaderTimeout: dockerDialerTimeout,
},
}, nil
}
func (d *dialer) getBastionHostTunnelConn() (*ssh.Client, error) {
bastionCfg, err := getSSHConfig(d.bastionDialer.username, d.bastionDialer.sshKeyString, d.bastionDialer.useSSHAgentAuth)
if err != nil {
return nil, fmt.Errorf("Error configuring SSH for bastion host [%s]: %v", d.bastionDialer.sshAddress, err)
}
bastionClient, err := ssh.Dial("tcp", d.bastionDialer.sshAddress, bastionCfg)
if err != nil {
return nil, fmt.Errorf("Failed to connect to the bastion host [%s]: %v", d.bastionDialer.sshAddress, err)
}
conn, err := bastionClient.Dial(d.bastionDialer.netConn, d.sshAddress)
if err != nil {
return nil, fmt.Errorf("Failed to connect to the host [%s]: %v", d.sshAddress, err)
}
cfg, err := getSSHConfig(d.username, d.sshKeyString, d.useSSHAgentAuth)
if err != nil {
return nil, fmt.Errorf("Error configuring SSH for host [%s]: %v", d.sshAddress, err)
}
newClientConn, channels, sshRequest, err := ssh.NewClientConn(conn, d.sshAddress, cfg)
if err != nil {
return nil, fmt.Errorf("Failed to establish new ssh client conn [%s]: %v", d.sshAddress, err)
}
return ssh.NewClient(newClientConn, channels, sshRequest), nil
}
func BastionHostWrapTransport(bastionHost v3.BastionHost) (k8s.WrapTransport, error) {
bastionDialer := &dialer{
sshAddress: fmt.Sprintf("%s:%s", bastionHost.Address, bastionHost.Port),
username: bastionHost.User,
sshKeyString: bastionHost.SSHKey,
netConn: "tcp",
useSSHAgentAuth: bastionHost.SSHAgentAuth,
}
if bastionDialer.sshKeyString == "" {
var err error
bastionDialer.sshKeyString, err = privateKeyPath(bastionHost.SSHKeyPath)
if err != nil {
return nil, err
}
}
return func(rt http.RoundTripper) http.RoundTripper {
if ht, ok := rt.(*http.Transport); ok {
ht.DialContext = nil
ht.DialTLS = nil
ht.Dial = bastionDialer.Dial
}
return rt
}, nil
}