2017-10-29 09:45:21 +00:00
|
|
|
package hosts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2018-03-27 21:07:16 +00:00
|
|
|
"time"
|
2017-10-29 09:45:21 +00:00
|
|
|
|
2018-05-08 22:30:50 +00:00
|
|
|
"github.com/rancher/rke/k8s"
|
|
|
|
"github.com/rancher/types/apis/management.cattle.io/v3"
|
2017-10-29 09:45:21 +00:00
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
)
|
|
|
|
|
2018-03-27 21:07:16 +00:00
|
|
|
const (
|
|
|
|
DockerDialerTimeout = 30
|
|
|
|
)
|
|
|
|
|
2017-12-16 03:38:15 +00:00
|
|
|
type DialerFactory func(h *Host) (func(network, address string) (net.Conn, error), error)
|
2017-12-11 18:28:08 +00:00
|
|
|
|
2017-12-16 03:38:15 +00:00
|
|
|
type dialer struct {
|
2018-03-06 00:52:43 +00:00
|
|
|
signer ssh.Signer
|
|
|
|
sshKeyString string
|
|
|
|
sshAddress string
|
|
|
|
username string
|
|
|
|
netConn string
|
|
|
|
dockerSocket string
|
|
|
|
useSSHAgentAuth bool
|
2018-05-08 22:30:50 +00:00
|
|
|
bastionDialer *dialer
|
2017-10-29 09:45:21 +00:00
|
|
|
}
|
|
|
|
|
2018-02-26 21:27:51 +00:00
|
|
|
func newDialer(h *Host, kind string) (*dialer, error) {
|
2018-05-08 22:30:50 +00:00
|
|
|
// 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 == "" {
|
2018-06-25 19:01:02 +00:00
|
|
|
var err error
|
|
|
|
bastionDialer.sshKeyString, err = privateKeyPath(h.BastionHost.SSHKeyPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-08 22:30:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-16 03:38:15 +00:00
|
|
|
dialer := &dialer{
|
2018-03-06 00:52:43 +00:00
|
|
|
sshAddress: fmt.Sprintf("%s:%s", h.Address, h.Port),
|
|
|
|
username: h.User,
|
|
|
|
dockerSocket: h.DockerSocket,
|
|
|
|
sshKeyString: h.SSHKey,
|
|
|
|
netConn: "unix",
|
|
|
|
useSSHAgentAuth: h.SSHAgentAuth,
|
2018-05-08 22:30:50 +00:00
|
|
|
bastionDialer: bastionDialer,
|
2017-12-11 18:28:08 +00:00
|
|
|
}
|
2017-10-29 09:45:21 +00:00
|
|
|
|
2018-02-26 21:27:51 +00:00
|
|
|
if dialer.sshKeyString == "" {
|
2018-06-25 19:01:02 +00:00
|
|
|
var err error
|
|
|
|
dialer.sshKeyString, err = privateKeyPath(h.SSHKeyPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-12-19 22:18:27 +00:00
|
|
|
}
|
2018-02-26 21:27:51 +00:00
|
|
|
|
|
|
|
switch kind {
|
|
|
|
case "network", "health":
|
|
|
|
dialer.netConn = "tcp"
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(dialer.dockerSocket) == 0 {
|
|
|
|
dialer.dockerSocket = "/var/run/docker.sock"
|
2017-12-19 22:18:27 +00:00
|
|
|
}
|
2018-02-26 21:27:51 +00:00
|
|
|
|
|
|
|
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
|
2017-12-19 22:18:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *dialer) DialDocker(network, addr string) (net.Conn, error) {
|
2018-02-26 21:27:51 +00:00
|
|
|
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) {
|
2018-05-08 22:30:50 +00:00
|
|
|
var conn *ssh.Client
|
|
|
|
var err error
|
|
|
|
if d.bastionDialer != nil {
|
|
|
|
conn, err = d.getBastionHostTunnelConn()
|
|
|
|
} else {
|
|
|
|
conn, err = d.getSSHTunnelConnection()
|
|
|
|
}
|
2017-10-29 09:45:21 +00:00
|
|
|
if err != nil {
|
2018-02-26 21:27:51 +00:00
|
|
|
return nil, fmt.Errorf("Failed to dial ssh using address [%s]: %v", d.sshAddress, err)
|
2017-10-29 09:45:21 +00:00
|
|
|
}
|
2018-02-26 21:27:51 +00:00
|
|
|
|
|
|
|
// Docker Socket....
|
|
|
|
if d.netConn == "unix" {
|
|
|
|
addr = d.dockerSocket
|
|
|
|
network = d.netConn
|
2017-10-29 09:45:21 +00:00
|
|
|
}
|
2018-02-26 21:27:51 +00:00
|
|
|
|
|
|
|
remote, err := conn.Dial(network, addr)
|
2017-10-29 09:45:21 +00:00
|
|
|
if err != nil {
|
2018-02-26 21:27:51 +00:00
|
|
|
return nil, fmt.Errorf("Failed to dial to %s: %v", addr, err)
|
2017-10-29 09:45:21 +00:00
|
|
|
}
|
|
|
|
return remote, err
|
|
|
|
}
|
2017-12-16 03:38:15 +00:00
|
|
|
|
2018-02-26 21:27:51 +00:00
|
|
|
func (d *dialer) getSSHTunnelConnection() (*ssh.Client, error) {
|
2018-05-08 22:30:50 +00:00
|
|
|
cfg, err := getSSHConfig(d.username, d.sshKeyString, d.useSSHAgentAuth)
|
2017-12-19 22:18:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error configuring SSH: %v", err)
|
|
|
|
}
|
|
|
|
// Establish connection with SSH server
|
2018-02-26 21:27:51 +00:00
|
|
|
return ssh.Dial("tcp", d.sshAddress, cfg)
|
2017-12-19 22:18:27 +00:00
|
|
|
}
|
|
|
|
|
2017-12-16 03:38:15 +00:00
|
|
|
func (h *Host) newHTTPClient(dialerFactory DialerFactory) (*http.Client, error) {
|
2018-02-26 21:27:51 +00:00
|
|
|
factory := dialerFactory
|
|
|
|
if factory == nil {
|
2017-12-16 03:38:15 +00:00
|
|
|
factory = SSHFactory
|
|
|
|
}
|
|
|
|
|
|
|
|
dialer, err := factory(h)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-03-27 21:07:16 +00:00
|
|
|
dockerDialerTimeout := time.Second * DockerDialerTimeout
|
2017-12-16 03:38:15 +00:00
|
|
|
return &http.Client{
|
|
|
|
Transport: &http.Transport{
|
2018-03-27 21:07:16 +00:00
|
|
|
Dial: dialer,
|
|
|
|
TLSHandshakeTimeout: dockerDialerTimeout,
|
|
|
|
IdleConnTimeout: dockerDialerTimeout,
|
|
|
|
ResponseHeaderTimeout: dockerDialerTimeout,
|
2017-12-16 03:38:15 +00:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
2018-05-08 22:30:50 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-06-25 19:01:02 +00:00
|
|
|
func BastionHostWrapTransport(bastionHost v3.BastionHost) (k8s.WrapTransport, error) {
|
2018-05-08 22:30:50 +00:00
|
|
|
|
|
|
|
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 == "" {
|
2018-06-25 19:01:02 +00:00
|
|
|
var err error
|
|
|
|
bastionDialer.sshKeyString, err = privateKeyPath(bastionHost.SSHKeyPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-05-08 22:30:50 +00:00
|
|
|
}
|
|
|
|
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
|
2018-06-25 19:01:02 +00:00
|
|
|
}, nil
|
2018-05-08 22:30:50 +00:00
|
|
|
}
|