Skip to content

Commit

Permalink
Add support for encrypted ssh keys
Browse files Browse the repository at this point in the history
Signed-off-by: Rene Kaufmann <[email protected]>
  • Loading branch information
HeavyHorst authored and alexellis committed Aug 20, 2019
1 parent 0a08e4d commit 9aaaf6a
Show file tree
Hide file tree
Showing 325 changed files with 248,063 additions and 10 deletions.
14 changes: 11 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,26 @@ If you are using public cloud, then make sure you see the notes from the Rancher

k3s docs: [k3s configuration / open ports](https://rancher.com/docs/k3s/latest/en/configuration/#open-ports-network-security)

## If your ssh-key is password-protected

If the ssh-key is encrypted the first step is to try to connect to the ssh-agent. If this works, it will be used to connect to the server.
If the ssh-agent is not running, the user will be prompted for the password of the ssh-key.

On most Linux systems and MacOS, ssh-agent is automatically configured and executed at login. No additional actions are required to use it.

To start the ssh-agent manually and add your key run the following commands:

```
eval `ssh-agent`
ssh-add ~/.ssh/id_rsa
```

You can now just run k3sup as usual. No special parameters are necessary.

```
k3sup --ip $IP --user user
```

## What are people saying about `k3sup`?

* Blog post by Ruan Bekker:
Expand Down
72 changes: 67 additions & 5 deletions pkg/cmd/install.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package cmd

import (
"bytes"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strings"

Expand All @@ -12,6 +15,8 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/terminal"
)

func MakeInstall() *cobra.Command {
Expand Down Expand Up @@ -48,10 +53,17 @@ func MakeInstall() *cobra.Command {
sshKeyPath := expandPath(sshKey)
fmt.Printf("ssh -i %s %s@%s\n", sshKeyPath, user, ip.String())

authMethod, closeSSHAgent, err := loadPublickey(sshKeyPath)
if err != nil {
return errors.Wrapf(err, "unable to load the ssh key with path %q", sshKeyPath)
}

defer closeSSHAgent()

config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
loadPublickey(sshKeyPath),
authMethod,
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
Expand Down Expand Up @@ -121,15 +133,65 @@ func expandPath(path string) string {
return res
}

func loadPublickey(path string) ssh.AuthMethod {
func sshAgent(publicKeyPath string) (ssh.AuthMethod, func() error) {
if sshAgentConn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
sshAgent := agent.NewClient(sshAgentConn)

keys, _ := sshAgent.List()
if len(keys) == 0 {
return nil, sshAgentConn.Close
}

pubkey, err := ioutil.ReadFile(publicKeyPath)
if err != nil {
return nil, sshAgentConn.Close
}

authkey, _, _, _, err := ssh.ParseAuthorizedKey(pubkey)
if err != nil {
return nil, sshAgentConn.Close
}
parsedkey := authkey.Marshal()

for _, key := range keys {
if bytes.Equal(key.Blob, parsedkey) {
return ssh.PublicKeysCallback(sshAgent.Signers), sshAgentConn.Close
}
}
}
return nil, func() error { return nil }
}

func loadPublickey(path string) (ssh.AuthMethod, func() error, error) {
noopCloseFunc := func() error { return nil }

key, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
return nil, noopCloseFunc, err
}

signer, err := ssh.ParsePrivateKey(key)
if err != nil {
panic(err)
if err.Error() != "ssh: cannot decode encrypted private keys" {
return nil, noopCloseFunc, err
}

agent, close := sshAgent(path + ".pub")
if agent != nil {
return agent, close, nil
}

defer close()

fmt.Printf("Enter passphrase for '%s': ", path)
bytePassword, err := terminal.ReadPassword(int(os.Stdin.Fd()))
fmt.Println()

signer, err = ssh.ParsePrivateKeyWithPassphrase(key, bytePassword)
if err != nil {
return nil, noopCloseFunc, err
}
}
return ssh.PublicKeys(signer)

return ssh.PublicKeys(signer), noopCloseFunc, nil
}
47 changes: 47 additions & 0 deletions pkg/cmd/install_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cmd

import (
"io/ioutil"
"os"
"testing"
)

const privateKey = `-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,6015676AEB0A96A9
A3HKcrAWcupUNvWZ+zfoMFPuaI76PF2XV2QXXOuz7mh3QQRlygVtMNDJZckZAJkd
Sn28TdfmFOhJ/owJElcxKRBrRE+JbKEIgyAUaKiRrAMPlqvDu2kPn5Jan5HhQfnk
K8Y+WI5dnR2cS3uoB7PkRlZjiJtSJzT3Qw2hO0KoZftWKNuQfBRkrY5+c94veb3X
kX+Ym4H3dHUXcIaYjHrTK+tuC36bzF0sdPQRf94JjtpGP3XkdVvWnmbL3i9XKZ/s
niaqfBleWT/EqfjIaex1JAj7XTlvau4AjKLCOaLZe1BkHEViL1lNQX0PoBVfFNK9
o8oGx8EBdmtxBpL6vSLMJSqEIyv2j+ziTUCjUkRa1O5S0lmWFoEXhz8hZ1GiVg7u
GmM0qN6tv7S9hiPx3x8jeTxaTyeGVs2O4Se3Y5bzdXoxWj0FcRh6DMR8SP/AeUDJ
IWFBbr3vD6nMWKYF4Ego9QRBsyIUL2oQfJk2j65dry+VMeVxcAlt9eQSOlRuxBg5
ySfAwn0bof4uY/I1u53ObnZvUZ1/AtuwK8K5mYDkNUchnoZiUC+v1PuyDowmJJxC
ds/3e4Opcs/T+3dJJ6MDO1STGJwsGd3aUWIeJX/E8USs/D20tLdYdJjiH/ijjp8K
lSTBND/n5CH417m/ta/QMy1e1zRgAKcc0WbdyrAFv5P9E4dZuMa0Ppq/1QjhoY48
WBDTI4J6Jw0muGSRQGIO9FCCH2mU/l/JOQ8+dzeMspYq9CY0tqRI6HweDyKR7nII
9QdL0fOnltgsNyziC6AUOhlDGKVuorIyHiYhOLVY6No4K+RbNE5/Tw==
-----END RSA PRIVATE KEY-----
`

func Test_loadPublickeyEncrypted(t *testing.T) {
expected := "x509: decryption password incorrect"

tmpfile, err := ioutil.TempFile("", "key")
if err != nil {
t.Error(err)
}

defer os.Remove(tmpfile.Name())
if _, err := tmpfile.Write([]byte(privateKey)); err != nil {
t.Error(err)
}

tmpfile.Close()
_, err = loadPublickey(tmpfile.Name())
if err.Error() != expected {
t.Errorf("Unexpected error, got: %q, want: %q.", err.Error(), expected)
}
}
18 changes: 16 additions & 2 deletions pkg/cmd/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,17 @@ func MakeJoin() *cobra.Command {
sshKeyPath := expandPath(sshKey)
fmt.Printf("ssh -i %s %s@%s\n", sshKeyPath, user, serverIP.String())

authMethod, closeSSHAgent, err := loadPublickey(sshKeyPath)
if err != nil {
return errors.Wrapf(err, "unable to load the ssh key with path %q", sshKeyPath)
}

defer closeSSHAgent()

config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
loadPublickey(sshKeyPath),
authMethod,
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
Expand Down Expand Up @@ -105,10 +112,17 @@ func MakeJoin() *cobra.Command {

func setupAgent(serverIP, ip net.IP, port int, user, sshKeyPath, joinToken string) error {

authMethod, closeSSHAgent, err := loadPublickey(sshKeyPath)
if err != nil {
return errors.Wrapf(err, "unable to load the ssh key with path %q", sshKeyPath)
}

defer closeSSHAgent()

config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
loadPublickey(sshKeyPath),
authMethod,
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
Expand Down
Loading

0 comments on commit 9aaaf6a

Please sign in to comment.