From 07d47aba28bde5a96e8ba113c07d7c849578a88f Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 7 Feb 2023 17:29:49 -0800 Subject: [PATCH] fix ValueString (wtf?) and add ssh bits --- go.mod | 4 +- internal/provider/provider.go | 3 +- system/ssh.go | 98 +++++++++++++++++++++++++++++++++++ system/system.go | 27 +++++++++- 4 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 system/ssh.go diff --git a/go.mod b/go.mod index b48c045..6b5d45c 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/hashicorp/terraform-plugin-go v0.14.3 github.com/hashicorp/terraform-plugin-log v0.7.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d ) require ( @@ -59,7 +60,6 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/zclconf/go-cty v1.12.1 // indirect - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/text v0.4.0 // indirect @@ -67,4 +67,4 @@ require ( google.golang.org/genproto v0.0.0-20200711021454-869866162049 // indirect google.golang.org/grpc v1.51.0 // indirect google.golang.org/protobuf v1.28.1 // indirect -) \ No newline at end of file +) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index dae9d14..57a00ad 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -47,12 +47,11 @@ func (p *SystemProvider) Configure(ctx context.Context, req provider.ConfigureRe var data SystemProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } - client, err := system.NewSystemManagerFromUri(data.ConnectionString.String()) + client, err := system.NewSystemManagerFromUri(data.ConnectionString.ValueString()) if err != nil { resp.Diagnostics.AddError( "Unable to create System Manager API client", diff --git a/system/ssh.go b/system/ssh.go new file mode 100644 index 0000000..898e4da --- /dev/null +++ b/system/ssh.go @@ -0,0 +1,98 @@ +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= or ?agent=yes") + } + + return u.Host, port, sshConfig, nil +} diff --git a/system/system.go b/system/system.go index f6dad50..619b28e 100644 --- a/system/system.go +++ b/system/system.go @@ -1,5 +1,11 @@ package system +import ( + "fmt" + "golang.org/x/crypto/ssh" + "net/url" +) + // SystemManager providers the functionality to interrogate / interact with target systems type SystemManager struct { connection *SystemConnection @@ -21,8 +27,27 @@ func (s *SystemManager) Test() error { // SystemConnection provides an unopinionated interface for interacting with remote systems on a low level type SystemConnection struct { + client *ssh.Client } func NewSystemConnectionFromUri(uri string) (*SystemConnection, error) { - return nil, nil + + //return nil, fmt.Errorf("using uri: '%s'", uri) + + sshUrl, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("invalid ssh uri: %s", err) + } + + sshHost, sshPort, sshConfig, err := NewSSHConfigFromUrl(sshUrl) + if err != nil { + return nil, fmt.Errorf("ssh failed: %s", err) + } + sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", sshHost, sshPort), sshConfig) + if err != nil { + return nil, fmt.Errorf("dial failed: %s", err) + } + return &SystemConnection{ + client: sshClient, + }, nil }