diff --git a/cluster.yml b/cluster.yml index 568b97f3..3b172d85 100644 --- a/cluster.yml +++ b/cluster.yml @@ -14,13 +14,20 @@ network: options: foo: bar +ssh_key_path: ~/.ssh/test + nodes: - address: 1.1.1.1 user: ubuntu role: [controlplane, etcd] + ssh_key_path: /home/user/.ssh/id_rsa - address: 2.2.2.2 user: ubuntu role: [worker] + ssh_key: |- + -----BEGIN RSA PRIVATE KEY----- + + -----END RSA PRIVATE KEY----- - address: example.com user: ubuntu role: [worker] diff --git a/cluster/cluster.go b/cluster/cluster.go index 8be4a1ef..bbd7bb21 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -42,6 +42,7 @@ const ( DefaultInfraContainerImage = "gcr.io/google_containers/pause-amd64:3.0" DefaultAuthStrategy = "x509" DefaultNetworkPlugin = "flannel" + DefaultClusterSSHKeyPath = "~/.ssh/id_rsa" StateConfigMapName = "cluster-state" UpdateStateTimeout = 30 GetStateTimeout = 30 @@ -110,6 +111,9 @@ func parseClusterFile(clusterFile string) (*Cluster, error) { } func (c *Cluster) setClusterDefaults() { + if len(c.SSHKeyPath) == 0 { + c.SSHKeyPath = DefaultClusterSSHKeyPath + } for i, host := range c.Nodes { if len(host.InternalAddress) == 0 { c.Nodes[i].InternalAddress = c.Nodes[i].Address @@ -118,6 +122,9 @@ func (c *Cluster) setClusterDefaults() { // This is a temporary modification c.Nodes[i].HostnameOverride = c.Nodes[i].Address } + if len(host.SSHKeyPath) == 0 { + c.Nodes[i].SSHKeyPath = c.SSHKeyPath + } } if len(c.Services.KubeAPI.ServiceClusterIPRange) == 0 { c.Services.KubeAPI.ServiceClusterIPRange = DefaultServiceClusterIPRange diff --git a/cluster/hosts.go b/cluster/hosts.go index 5f45c24d..b763744f 100644 --- a/cluster/hosts.go +++ b/cluster/hosts.go @@ -2,43 +2,27 @@ package cluster import ( "fmt" - "os" - "path/filepath" - "strings" - "syscall" "github.com/rancher/rke/hosts" "github.com/rancher/rke/pki" "github.com/rancher/rke/services" "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/terminal" -) - -const ( - DefaultSSHKeyPath = "/.ssh/id_rsa" ) func (c *Cluster) TunnelHosts() error { - key, err := checkEncryptedKey(c.SSHKeyPath) - if err != nil { - return fmt.Errorf("Failed to parse the private key: %v", err) - } for i := range c.EtcdHosts { - err := c.EtcdHosts[i].TunnelUp(key) - if err != nil { + if err := c.EtcdHosts[i].TunnelUp(); err != nil { return fmt.Errorf("Failed to set up SSH tunneling for Etcd hosts: %v", err) } } for i := range c.ControlPlaneHosts { - err := c.ControlPlaneHosts[i].TunnelUp(key) + err := c.ControlPlaneHosts[i].TunnelUp() if err != nil { return fmt.Errorf("Failed to set up SSH tunneling for Control hosts: %v", err) } } for i := range c.WorkerHosts { - err := c.WorkerHosts[i].TunnelUp(key) - if err != nil { + if err := c.WorkerHosts[i].TunnelUp(); err != nil { return fmt.Errorf("Failed to set up SSH tunneling for Worker hosts: %v", err) } } @@ -101,34 +85,3 @@ func CheckEtcdHostsChanged(kubeCluster, currentCluster *Cluster) error { } return nil } - -func checkEncryptedKey(sshKeyPath string) (ssh.Signer, error) { - logrus.Infof("[ssh] Checking private key") - key, err := hosts.ParsePrivateKey(privateKeyPath(sshKeyPath)) - if err != nil { - if strings.Contains(err.Error(), "decode encrypted private keys") { - fmt.Printf("Passphrase for Private SSH Key: ") - passphrase, err := terminal.ReadPassword(int(syscall.Stdin)) - fmt.Printf("\n") - if err != nil { - return nil, err - } - key, err = hosts.ParsePrivateKeyWithPassPhrase(privateKeyPath(sshKeyPath), passphrase) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - return key, nil -} - -func privateKeyPath(sshKeyPath string) string { - if len(sshKeyPath) == 0 { - return os.Getenv("HOME") + DefaultSSHKeyPath - } else if sshKeyPath[:2] == "~/" { - return filepath.Join(os.Getenv("HOME"), sshKeyPath[2:]) - } - return sshKeyPath -} diff --git a/cluster/reconcile.go b/cluster/reconcile.go index 61cf5d82..68af3224 100644 --- a/cluster/reconcile.go +++ b/cluster/reconcile.go @@ -7,7 +7,6 @@ import ( "github.com/rancher/rke/k8s" "github.com/rancher/rke/services" "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh" "k8s.io/client-go/kubernetes" ) @@ -30,20 +29,18 @@ func ReconcileCluster(kubeCluster, currentCluster *Cluster) error { return fmt.Errorf("Failed to initialize new kubernetes client: %v", err) } - key, _ := checkEncryptedKey(kubeCluster.SSHKeyPath) - - if err := reconcileWorker(currentCluster, kubeCluster, key, kubeClient); err != nil { + if err := reconcileWorker(currentCluster, kubeCluster, kubeClient); err != nil { return err } - if err := reconcileControl(currentCluster, kubeCluster, key, kubeClient); err != nil { + if err := reconcileControl(currentCluster, kubeCluster, kubeClient); err != nil { return err } logrus.Infof("[reconcile] Reconciled cluster state successfully") return nil } -func reconcileWorker(currentCluster, kubeCluster *Cluster, key ssh.Signer, kubeClient *kubernetes.Clientset) error { +func reconcileWorker(currentCluster, kubeCluster *Cluster, kubeClient *kubernetes.Clientset) error { // worker deleted first to avoid issues when worker+controller on same host logrus.Debugf("[reconcile] Check worker hosts to be deleted") wpToDelete := hosts.GetToDeleteHosts(currentCluster.WorkerHosts, kubeCluster.WorkerHosts) @@ -53,7 +50,7 @@ func reconcileWorker(currentCluster, kubeCluster *Cluster, key ssh.Signer, kubeC return fmt.Errorf("Failed to delete worker node %s from cluster", toDeleteHost.Address) } // attempting to clean services/files on the host - if err := reconcileHost(toDeleteHost, key, true); err != nil { + if err := reconcileHost(toDeleteHost, true); err != nil { logrus.Warnf("[reconcile] Couldn't clean up worker node [%s]: %v", toDeleteHost.Address, err) continue } @@ -61,7 +58,7 @@ func reconcileWorker(currentCluster, kubeCluster *Cluster, key ssh.Signer, kubeC return nil } -func reconcileControl(currentCluster, kubeCluster *Cluster, key ssh.Signer, kubeClient *kubernetes.Clientset) error { +func reconcileControl(currentCluster, kubeCluster *Cluster, kubeClient *kubernetes.Clientset) error { logrus.Debugf("[reconcile] Check Control plane hosts to be deleted") selfDeleteAddress, err := getLocalConfigAddress(kubeCluster.LocalKubeConfigPath) if err != nil { @@ -85,7 +82,7 @@ func reconcileControl(currentCluster, kubeCluster *Cluster, key ssh.Signer, kube return fmt.Errorf("Failed to delete controlplane node %s from cluster", toDeleteHost.Address) } // attempting to clean services/files on the host - if err := reconcileHost(toDeleteHost, key, false); err != nil { + if err := reconcileHost(toDeleteHost, false); err != nil { logrus.Warnf("[reconcile] Couldn't clean up controlplane node [%s]: %v", toDeleteHost.Address, err) continue } @@ -106,8 +103,8 @@ func reconcileControl(currentCluster, kubeCluster *Cluster, key ssh.Signer, kube return nil } -func reconcileHost(toDeleteHost *hosts.Host, key ssh.Signer, worker bool) error { - if err := toDeleteHost.TunnelUp(key); err != nil { +func reconcileHost(toDeleteHost *hosts.Host, worker bool) error { + if err := toDeleteHost.TunnelUp(); err != nil { return fmt.Errorf("Not able to reach the host: %v", err) } if worker { diff --git a/cmd/config.go b/cmd/config.go index 26134b00..a7f4f036 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -89,8 +89,7 @@ func clusterConfig(ctx *cli.Context) error { return writeConfig(&cluster, configFile, print) } - // Get number of hosts - sshKeyPath, err := getConfig(reader, "SSH Private Key Path", "~/.ssh/id_rsa") + sshKeyPath, err := getConfig(reader, "Cluster Level SSH Private Key Path", "~/.ssh/id_rsa") if err != nil { return err } @@ -142,12 +141,25 @@ func clusterConfig(ctx *cli.Context) error { func getHostConfig(reader *bufio.Reader, index int) (*v1.RKEConfigNode, error) { host := v1.RKEConfigNode{} + address, err := getConfig(reader, fmt.Sprintf("SSH Address of host (%d)", index+1), "") if err != nil { return nil, err } host.Address = address + sshKeyPath, err := getConfig(reader, fmt.Sprintf("SSH Private Key Path of host (%s)", address), "") + if err != nil { + return nil, err + } + host.SSHKeyPath = sshKeyPath + + sshKey, err := getConfig(reader, fmt.Sprintf("SSH Private Key of host (%s)", address), "") + if err != nil { + return nil, err + } + host.SSHKey = sshKey + sshUser, err := getConfig(reader, fmt.Sprintf("SSH User of host (%s)", address), "ubuntu") if err != nil { return nil, err diff --git a/hosts/dialer.go b/hosts/dialer.go index 90011e87..cde330ee 100644 --- a/hosts/dialer.go +++ b/hosts/dialer.go @@ -5,10 +5,15 @@ import ( "io/ioutil" "net" "net/http" + "os" + "path/filepath" + "strings" + "syscall" "github.com/docker/docker/client" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/terminal" ) type dialer struct { @@ -42,12 +47,15 @@ func (d *dialer) Dial(network, addr string) (net.Conn, error) { return remote, err } -func (h *Host) TunnelUp(signer ssh.Signer) error { +func (h *Host) TunnelUp() error { logrus.Infof("[ssh] Start tunnel for host [%s]", h.Address) - + key, err := checkEncryptedKey(h.SSHKey, h.SSHKeyPath) + if err != nil { + return fmt.Errorf("Failed to parse the private key: %v", err) + } dialer := &dialer{ host: h, - signer: signer, + signer: key, } httpClient := &http.Client{ Transport: &http.Transport{ @@ -56,7 +64,6 @@ func (h *Host) TunnelUp(signer ssh.Signer) error { } // set Docker client - var err error logrus.Debugf("Connecting to Docker API for host [%s]", h.Address) h.DClient, err = client.NewClient("unix:///var/run/docker.sock", DockerAPIVersion, httpClient, nil) if err != nil { @@ -65,14 +72,12 @@ func (h *Host) TunnelUp(signer ssh.Signer) error { return nil } -func ParsePrivateKey(keyPath string) (ssh.Signer, error) { - buff, _ := ioutil.ReadFile(keyPath) - return ssh.ParsePrivateKey(buff) +func parsePrivateKey(keyBuff string) (ssh.Signer, error) { + return ssh.ParsePrivateKey([]byte(keyBuff)) } -func ParsePrivateKeyWithPassPhrase(keyPath string, passphrase []byte) (ssh.Signer, error) { - buff, _ := ioutil.ReadFile(keyPath) - return ssh.ParsePrivateKeyWithPassphrase(buff, passphrase) +func parsePrivateKeyWithPassPhrase(keyBuff string, passphrase []byte) (ssh.Signer, error) { + return ssh.ParsePrivateKeyWithPassphrase([]byte(keyBuff), passphrase) } func makeSSHConfig(user string, signer ssh.Signer) (*ssh.ClientConfig, error) { @@ -86,3 +91,44 @@ func makeSSHConfig(user string, signer ssh.Signer) (*ssh.ClientConfig, error) { return &config, nil } + +func checkEncryptedKey(sshKey, sshKeyPath string) (ssh.Signer, error) { + logrus.Debugf("[ssh] Checking private key") + var err error + var key ssh.Signer + if len(sshKey) > 0 { + key, err = parsePrivateKey(sshKey) + } else { + key, err = parsePrivateKey(privateKeyPath(sshKeyPath)) + } + if err == nil { + return key, nil + } + + // parse encrypted key + if strings.Contains(err.Error(), "decode encrypted private keys") { + fmt.Printf("Passphrase for Private SSH Key: ") + passphrase, err := terminal.ReadPassword(int(syscall.Stdin)) + fmt.Printf("\n") + if err != nil { + return nil, err + } + if len(sshKey) > 0 { + key, err = parsePrivateKeyWithPassPhrase(sshKey, passphrase) + } else { + key, err = parsePrivateKeyWithPassPhrase(privateKeyPath(sshKeyPath), passphrase) + } + if err != nil { + return nil, err + } + } + return key, nil +} + +func privateKeyPath(sshKeyPath string) string { + if sshKeyPath[:2] == "~/" { + sshKeyPath = filepath.Join(os.Getenv("HOME"), sshKeyPath[2:]) + } + buff, _ := ioutil.ReadFile(sshKeyPath) + return string(buff) +} diff --git a/vendor.conf b/vendor.conf index b5ca727c..8e44f3ba 100644 --- a/vendor.conf +++ b/vendor.conf @@ -10,13 +10,14 @@ github.com/docker/distribution 3800056b8832cf6075e78b282ac010131d8687b github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d github.com/docker/go-units 0dadbb0345b35ec7ef35e228dabb8de89a65bf52 golang.org/x/net 186fd3fc8194a5e9980a82230d69c1ff7134229f -github.com/rancher/types 0a0a5647cfdec48e7f531534d383e755fae7861c +github.com/rancher/types eb5154d777208bbf9acd0fe760ea2d1cc4d2c7b9 github.com/opencontainers/go-digest 279bed98673dd5bef374d3b6e4b09e2af76183bf github.com/gogo/protobuf 117892bf1866fbaa2318c03e50e40564c8845457 github.com/opencontainers/image-spec 7c889fafd04a893f5c5f50b7ab9963d5d64e5242 github.com/pkg/errors f15c970de5b76fac0b59abb32d62c17cc7bed265 -github.com/rancher/norman faa1fb2148211044253fc2f6403008958c72b1f0 +github.com/rancher/norman 5ec6a8d719917fcca3cab8e8e9036384385ced40 gopkg.in/check.v1 11d3bc7aa68e238947792f30573146a3231fc0f1 k8s.io/api/core/v1 4df58c811fe2e65feb879227b2b245e4dc26e7ad k8s.io/client-go v5.0.0 transitive=true github.com/gorilla/websocket v1.2.0 +golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5 diff --git a/vendor/github.com/rancher/types/apis/cluster.cattle.io/v1/types.go b/vendor/github.com/rancher/types/apis/cluster.cattle.io/v1/types.go index 94ef6f0b..1dff386e 100644 --- a/vendor/github.com/rancher/types/apis/cluster.cattle.io/v1/types.go +++ b/vendor/github.com/rancher/types/apis/cluster.cattle.io/v1/types.go @@ -122,6 +122,8 @@ type RancherKubernetesEngineConfig struct { Addons string `yaml:"addons" json:"addons,omitempty"` // List of images used internally for proxy, cert downlaod and kubedns RKEImages map[string]string `yaml:"rke_images" json:"rke_images,omitempty"` + // SSH Private Key Path + SSHKeyPath string `yaml:"ssh_key_path" json:"sshKeyPath,omitempty"` } type RKEConfigNode struct {