mirror of
https://github.com/rancher/rke.git
synced 2025-04-27 19:25:44 +00:00
262 lines
8.5 KiB
Go
262 lines
8.5 KiB
Go
package hosts
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"k8s.io/client-go/transport"
|
|
|
|
v3 "github.com/rancher/rke/types"
|
|
"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
|
|
sshCertString string
|
|
sshAddress string
|
|
username string
|
|
netConn string
|
|
dockerSocket string
|
|
useSSHAgentAuth bool
|
|
bastionDialer *dialer
|
|
}
|
|
|
|
type DialersOptions struct {
|
|
DockerDialerFactory DialerFactory
|
|
LocalConnDialerFactory DialerFactory
|
|
K8sWrapTransport transport.WrapperFunc
|
|
}
|
|
|
|
func GetDialerOptions(d, l DialerFactory, w transport.WrapperFunc) DialersOptions {
|
|
return DialersOptions{
|
|
DockerDialerFactory: d,
|
|
LocalConnDialerFactory: l,
|
|
K8sWrapTransport: w,
|
|
}
|
|
}
|
|
|
|
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,
|
|
sshCertString: h.BastionHost.SSHCert,
|
|
netConn: "tcp",
|
|
useSSHAgentAuth: h.SSHAgentAuth,
|
|
}
|
|
if bastionDialer.sshKeyString == "" && !bastionDialer.useSSHAgentAuth {
|
|
var err error
|
|
bastionDialer.sshKeyString, err = privateKeyPath(h.BastionHost.SSHKeyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if bastionDialer.sshCertString == "" && len(h.BastionHost.SSHCertPath) > 0 {
|
|
bastionDialer.sshCertString, err = certificatePath(h.BastionHost.SSHCertPath)
|
|
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,
|
|
sshCertString: h.SSHCert,
|
|
netConn: "unix",
|
|
useSSHAgentAuth: h.SSHAgentAuth,
|
|
bastionDialer: bastionDialer,
|
|
}
|
|
|
|
if dialer.sshKeyString == "" && !dialer.useSSHAgentAuth {
|
|
var err error
|
|
dialer.sshKeyString, err = privateKeyPath(h.SSHKeyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if dialer.sshCertString == "" && len(h.SSHCertPath) > 0 {
|
|
dialer.sshCertString, err = certificatePath(h.SSHCertPath)
|
|
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.sshCertString, 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.sshCertString, 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.sshCertString, 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) (transport.WrapperFunc, error) {
|
|
|
|
bastionDialer := &dialer{
|
|
sshAddress: fmt.Sprintf("%s:%s", bastionHost.Address, bastionHost.Port),
|
|
username: bastionHost.User,
|
|
sshKeyString: bastionHost.SSHKey,
|
|
sshCertString: bastionHost.SSHCert,
|
|
netConn: "tcp",
|
|
useSSHAgentAuth: bastionHost.SSHAgentAuth,
|
|
}
|
|
|
|
if bastionDialer.sshKeyString == "" && !bastionDialer.useSSHAgentAuth {
|
|
var err error
|
|
bastionDialer.sshKeyString, err = privateKeyPath(bastionHost.SSHKeyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
}
|
|
|
|
if bastionDialer.sshCertString == "" && len(bastionHost.SSHCertPath) > 0 {
|
|
var err error
|
|
bastionDialer.sshCertString, err = certificatePath(bastionHost.SSHCertPath)
|
|
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
|
|
}
|