terraform-provider-system/system/ssh.go

99 lines
2.4 KiB
Go

package system
import (
"fmt"
"io/ioutil"
"log"
"net"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/knownhosts"
)
func getKnownHosts() (ssh.HostKeyCallback, error) {
homedir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("need homedir: %s", err)
}
return knownhosts.New(fmt.Sprintf("%s/.ssh/known_hosts", homedir))
}
func expandHome(path string) string {
if strings.HasPrefix(path, "~/") {
dirname, _ := os.UserHomeDir()
path = filepath.Join(dirname, path[2:])
}
return path
}
func NewSSHConfigFromUrl(u *url.URL) (string, int, *ssh.ClientConfig, error) {
port := 22
portStr := u.Port()
if portStr != "" {
port, _ = strconv.Atoi(portStr)
}
knownHostsCallback, err := getKnownHosts()
if err != nil {
return "", -1, nil, fmt.Errorf("couldn't load known hosts: %s", err)
}
username := u.User.Username()
password, _ := u.User.Password()
sshConfig := &ssh.ClientConfig{
User: username,
HostKeyCallback: knownHostsCallback,
}
extraArguments, err := url.ParseQuery(u.RawQuery)
if err != nil {
return "", -1, nil, err
}
sshKeyPath, hasSshKeyPath := extraArguments["ssh_key_path"]
agentValue, hasAgent := extraArguments["agent"]
if username != "" && hasSshKeyPath {
log.Printf("using ssh key: %s", sshKeyPath)
key, err := ioutil.ReadFile(expandHome(sshKeyPath[0]))
if err != nil {
return "", -1, nil, fmt.Errorf("read private key: %s", err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return "", -1, nil, fmt.Errorf("parse private key: %v", err)
}
sshConfig.Auth = []ssh.AuthMethod{
ssh.PublicKeys(signer),
}
} else if hasAgent && agentValue[0] == "yes" {
socket := os.Getenv("SSH_AUTH_SOCK")
if socket == "" {
return "", -1, nil, fmt.Errorf("requested ssh-agent but $SSH_AUTH_SOCK is unset")
}
conn, err := net.Dial("unix", socket)
if err != nil {
return "", -1, nil, fmt.Errorf("Failed to open SSH_AUTH_SOCK: %v", err)
}
agentClient := agent.NewClient(conn)
sshConfig.Auth = []ssh.AuthMethod{
ssh.PublicKeysCallback(agentClient.Signers),
}
} else if username != "" && password != "" {
log.Printf("using ssh password")
sshConfig.Auth = []ssh.AuthMethod{
ssh.Password(password),
}
} else {
return "", -1, nil, fmt.Errorf("no credential supplied, need user with password or ?ssh_key_path=<path> or ?agent=yes")
}
return u.Host, port, sshConfig, nil
}