diff --git a/cmd/clusterAddWorker.go b/cmd/clusterAddWorker.go index c409f89d..68441f89 100644 --- a/cmd/clusterAddWorker.go +++ b/cmd/clusterAddWorker.go @@ -75,6 +75,12 @@ You can specify the worker server type as in cluster create.`, log.Fatal("master not found") } + err := capturePassphrase(sshKeyName) + + if err != nil { + log.Fatal(err) + } + maxNo := 0 for _, node := range cluster.Nodes { if !node.IsMaster { diff --git a/cmd/clusterCreate.go b/cmd/clusterCreate.go index 89eafc33..85d1cf89 100644 --- a/cmd/clusterCreate.go +++ b/cmd/clusterCreate.go @@ -48,6 +48,12 @@ to quickly create a Cobra application.`, masterServerType, _ := cmd.Flags().GetString("master-server-type") workerServerType, _ := cmd.Flags().GetString("worker-server-type") + err := capturePassphrase(sshKeyName) + + if err != nil { + log.Fatal(err) + } + cluster := Cluster{Name: clusterName, wait: false} if err := cluster.CreateMasterNodes(sshKeyName, masterServerType, 1); err != nil { diff --git a/cmd/clusterKubeconfig.go b/cmd/clusterKubeconfig.go index d6c872fa..dbc34e8b 100644 --- a/cmd/clusterKubeconfig.go +++ b/cmd/clusterKubeconfig.go @@ -39,7 +39,9 @@ Example 4: hetzner-kube cluster kubeconfig -n my-cluster -p > my-conf.yaml # pri name, _ := cmd.Flags().GetString("name") _, cluster := AppConf.Config.FindClusterByName(name) masterNode, err := cluster.GetMasterNode() + FatalOnError(err) + err = capturePassphrase(masterNode.SSHKeyName) FatalOnError(err) kubeConfigContent, err := runCmd(*masterNode, "cat /etc/kubernetes/admin.conf") diff --git a/cmd/contextAdd.go b/cmd/contextAdd.go index a69dd668..eed02a8a 100644 --- a/cmd/contextAdd.go +++ b/cmd/contextAdd.go @@ -19,7 +19,6 @@ import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" - "log" "os" "strings" ) diff --git a/cmd/contextUse.go b/cmd/contextUse.go index 4ceb0d1b..8df13062 100644 --- a/cmd/contextUse.go +++ b/cmd/contextUse.go @@ -16,9 +16,7 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" - "os" ) // useCmd represents the use command diff --git a/cmd/util.go b/cmd/util.go index 2145f0eb..579000eb 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -11,22 +11,112 @@ import ( "log" "time" "github.com/Pallinder/go-randomdata" + "encoding/pem" + "strings" + "golang.org/x/crypto/ssh/terminal" + "syscall" ) -func runCmd(node Node, command string) (output string, err error) { - index, privateKey := AppConf.Config.FindSSHKeyByName(node.SSHKeyName) +var sshPassPhrases = make(map[string][]byte) + +func capturePassphrase(sshKeyName string) (error) { + index, privateKey := AppConf.Config.FindSSHKeyByName(sshKeyName) if index < 0 { - return "", errors.New(fmt.Sprintf("cound not find SSH key '%s'", node.SSHKeyName)) + return errors.New(fmt.Sprintf("could not find SSH key '%s'", sshKeyName)) + } + + encrypted, err := isEncrypted(privateKey) + + if err != nil { + return err + } + + if !encrypted { + return nil } + fmt.Print("Enter passphrase for ssh key " + privateKey.PrivateKeyPath + ": ") + text, err := terminal.ReadPassword(int(syscall.Stdin)) + + if err != nil { + return err + } + + fmt.Print("\n") + sshPassPhrases[privateKey.PrivateKeyPath] = text + + return nil +} + +func getPassphrase(privateKeyPath string) ([]byte, error) { + if phrase, ok := sshPassPhrases[privateKeyPath]; ok { + return phrase, nil + } + + return nil, errors.New("passphrase not found") +} + +func isEncrypted(privateKey *SSHKey) (bool, error) { pemBytes, err := ioutil.ReadFile(privateKey.PrivateKeyPath) if err != nil { - return "", err + return false, err + } + + block, _ := pem.Decode(pemBytes) + if block == nil { + return false, errors.New("ssh: no key found") + + } + + return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED"), nil +} + +func getPrivateSshKey(sshKeyName string) (ssh.Signer, error) { + index, privateKey := AppConf.Config.FindSSHKeyByName(sshKeyName) + if index < 0 { + return nil, errors.New(fmt.Sprintf("cound not find SSH key '%s'", sshKeyName)) } - signer, err := ssh.ParsePrivateKey(pemBytes) + + encrypted, err := isEncrypted(privateKey) + + if err != nil { + return nil, err + } + + pemBytes, err := ioutil.ReadFile(privateKey.PrivateKeyPath) if err != nil { - return "", errors.New(fmt.Sprintf("parse key failed:%v", err)) + return nil, err } + + if encrypted { + passPhrase, err := getPassphrase(privateKey.PrivateKeyPath) + if err != nil { + return nil, errors.New(fmt.Sprintf("parse key failed:%v", err)) + } + + signer, err := ssh.ParsePrivateKeyWithPassphrase(pemBytes, passPhrase) + if err != nil { + return nil, errors.New(fmt.Sprintf("parse key failed:%v", err)) + } + + return signer, err + } else { + signer, err := ssh.ParsePrivateKey(pemBytes) + if err != nil { + return nil, errors.New(fmt.Sprintf("parse key failed:%v", err)) + } + + return signer, err + } +} + +func runCmd(node Node, command string) (output string, err error) { + signer, err := getPrivateSshKey(node.SSHKeyName) + + if err != nil { + return "", err + } + config := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, @@ -142,4 +232,4 @@ func FatalOnError(err error) { if err != nil { log.Fatal(err) } -} \ No newline at end of file +}