/
ssh.go
160 lines (125 loc) · 3.79 KB
/
ssh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package ops
import (
"fmt"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"io"
"net"
"os"
"os/user"
)
// SshProgClient is an ssh client designed to do remote commands or RPC's
type SshProgClient struct {
Host string
Port int
Config *ssh.ClientConfig
}
// NewSshProgClient creates a client for the given host, port, and config.
func NewSshProgClient(host string, port int, config *ssh.ClientConfig) (client *SshProgClient) {
client = &SshProgClient{
Host: host,
Port: port,
Config: config,
}
return client
}
// SshClient generates an SSH client for talking to the provisioning server
func SshClient(hostname string, port int, username string) (client *SshProgClient, err error) {
var operator string
if username == "" {
userobj, err := user.Current()
if err != nil {
err = errors.Wrapf(err, "failed to determine local user")
return client, err
}
operator = userobj.Username
} else {
operator = username
}
sshConfig := &ssh.ClientConfig{
User: operator,
Auth: []ssh.AuthMethod{
SSHAgent(),
},
//HostKeyCallback: ssh.FixedHostKey(HostKey()),
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client = NewSshProgClient(hostname, port, sshConfig)
return client, err
}
// SSHAgent is a programmatic client that talks to the ssh agent.
func SSHAgent() ssh.AuthMethod {
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
}
return nil
}
// SCPFile copies a file via SCP to the remote host.
func (c *SshProgClient) SCPFile(content string, filename string) (err error) {
addr := fmt.Sprintf("%s:%d", c.Host, c.Port)
connection, err := ssh.Dial("tcp", addr, c.Config)
if err != nil {
err = errors.Wrapf(err, "failed to dial server")
return err
}
session, err := connection.NewSession()
if err != nil {
err = errors.Wrapf(err, "failed to create connection")
return err
}
go func() {
w, _ := session.StdinPipe()
// this has to have a new line
_, _ = fmt.Fprintln(w, "C0644", len(content), filename)
// this is whatever the content is
_, _ = fmt.Fprint(w, content)
// this cannot have a new line
_, _ = fmt.Fprint(w, "\x00")
_ = w.Close()
}()
/*
scp transfers by opening an ssh connection and opening another copy of scp on the remote system. One instances sends, the other receives and copies to it's local disk.
-t tells scp that it was invoked by another instance and it will be receiving.
-f tells that it was involed by another instance and it should send.
These options are totally undocumented.
*/
err = session.Run("/usr/bin/scp -tr ./")
if err != nil {
err = errors.Wrapf(err, "Failed to copy file")
return err
}
_ = session.Close()
return err
}
// RpcCall flings bytes at a remote server over SSH to STDIN, and receives whatever that
// server decides to send back on STDOUT and STDERR. What you send it, and what you do with the
// reply is between you and the server.
func (c *SshProgClient) RpcCall(input []byte, stdout, stderr io.Writer) (err error) {
addr := fmt.Sprintf("%s:%d", c.Host, c.Port)
connection, err := ssh.Dial("tcp", addr, c.Config)
if err != nil {
err = errors.Wrapf(err, "failed to dial server")
return err
}
session, err := connection.NewSession()
if err != nil {
err = errors.Wrapf(err, "failed to create connection")
return err
}
session.Stdout = stdout
session.Stderr = stderr
err = session.Start(string(input))
if err != nil {
err = errors.Wrapf(err, "failed to open shell on remote server")
return err
}
err = session.Wait()
if err != nil {
err = errors.Wrapf(err, "error waiting for session to complete")
return err
}
// It probably closes serverside before this is necessary, but let's be thorough.
_ = session.Close()
return err
}