This is a proof of concept which uses public key authentication through a running SSH Agent. (needs to be found in the SSH_AUTH_SOCK
env
variable)
$ make
$ ./bin/test_ssh_client -h 192.168.0.13 -l root -c "ps aux | head -n 5"
package main | |
import ( | |
"fmt" | |
"time" | |
) | |
var LogColors = map[int]int { | |
DEBUG: 102, | |
INFO: 28, | |
WARN: 214, | |
ERROR: 196, | |
} | |
const TIME_FORMAT = "2006-01-02T15:04:05.000000" | |
func colorize(c int, s string) (r string) { | |
return fmt.Sprintf("\033[38;5;%dm%s\033[0m", c, s) | |
} | |
const ( | |
DEBUG = iota | |
INFO | |
WARN | |
ERROR | |
) | |
var LogPrefixes = map[int]string { | |
DEBUG: "DEBUG", | |
INFO: "INFO ", | |
WARN: "WARN ", | |
ERROR: "ERROR", | |
} | |
type Logger struct { | |
LogLevel int | |
Prefix string | |
} | |
func (l* Logger) Debugf(format string, n ...interface{}) { | |
l.Logf(DEBUG, format, n...) | |
} | |
func (l* Logger) Infof(format string, n ...interface{}) { | |
l.Logf(INFO, format, n...) | |
} | |
func (l* Logger) Warnf(format string, n ...interface{}) { | |
l.Logf(WARN, format, n...) | |
} | |
func (l* Logger) Errorf(format string, n ...interface{}) { | |
l.Logf(ERROR, format, n...) | |
} | |
func (l* Logger) Logf(level int, s string, n ...interface{}) { | |
if level >= l.LogLevel { | |
fmt.Println(l.LogPrefix(level), fmt.Sprintf(s, n...)) | |
} | |
} | |
func (l* Logger) Debug(n ...interface{}) { | |
l.Log(DEBUG, n...) | |
} | |
func (l* Logger) Info(n ...interface{}) { | |
l.Log(INFO, n...) | |
} | |
func (l* Logger) Warn(n ...interface{}) { | |
l.Log(WARN, n...) | |
} | |
func (l* Logger) Error(n ...interface{}) { | |
l.Log(ERROR, n...) | |
} | |
func (l* Logger) LogPrefix(i int) (s string ) { | |
s = time.Now().Format(TIME_FORMAT) | |
if l.Prefix != "" { | |
s = s + " [" + l.Prefix + "]" | |
} | |
s = s + " " + l.LogLevelPrefix(i) | |
return | |
} | |
func (l* Logger) LogLevelPrefix(level int) (s string) { | |
color := LogColors[level] | |
prefix := LogPrefixes[level] | |
return colorize(color, prefix) | |
} | |
func (l* Logger) Log(level int, n ...interface{}) { | |
if level >= l.LogLevel { | |
all := append([]interface{} { l.LogPrefix(level) }, n...) | |
fmt.Println(all...) | |
} | |
} |
default: | |
@go get code.google.com/p/go.crypto/ssh | |
go build -o bin/test_ssh_client *.go |
package main | |
import ( | |
"strings" | |
"time" | |
"code.google.com/p/go.crypto/ssh" | |
"os" | |
"net" | |
"bytes" | |
) | |
func NewSSHClient(host, user string) (c* SSHClient) { | |
c = &SSHClient{ | |
User: user, | |
Host: host, | |
Logger: &Logger{Prefix: host}, | |
} | |
return | |
} | |
type SSHClient struct { | |
User string | |
Host string | |
Agent net.Conn | |
Conn *ssh.ClientConn | |
Logger* Logger | |
} | |
func (c* SSHClient) Close() { | |
if c.Conn != nil { | |
c.Conn.Close() | |
} | |
if c.Agent != nil{ | |
c.Agent.Close() | |
} | |
} | |
func (c* SSHClient) ConnectWhenNotConnected() (e error) { | |
if c.Conn != nil { | |
c.Logger.Debug("already connected") | |
return | |
} | |
return c.Connect() | |
} | |
func (c* SSHClient) Connect() (e error) { | |
c.Logger.Debug("connecting " + c.Host) | |
var auths []ssh.ClientAuth | |
if c.Agent, e = net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); e == nil { | |
auths = append(auths, ssh.ClientAuthAgent(ssh.NewAgentClient(c.Agent))) | |
} | |
config := &ssh.ClientConfig { | |
User: "root", | |
Auth: auths, | |
} | |
c.Conn, e = ssh.Dial("tcp", c.Host + ":22", config) | |
if e != nil { | |
return | |
} | |
return | |
} | |
type LogWriter struct { | |
LogTo func(n... interface{}) | |
Buffer bytes.Buffer | |
} | |
func (w* LogWriter) String() (s string) { | |
return w.Buffer.String() | |
} | |
func (w* LogWriter) Write(b []byte) (i int, e error) { | |
if w.LogTo != nil { | |
for _, s := range strings.Split(string(b), "\n") { | |
trimmed := strings.TrimSpace(s) | |
if len(trimmed) > 0 { | |
w.LogTo(trimmed) | |
} | |
} | |
} | |
w.Buffer.Write(b) | |
return len(b), nil | |
} | |
func (c* SSHClient) Execute(s string) (r* SSHResult, e error) { | |
started := time.Now() | |
c.ConnectWhenNotConnected() | |
ses, e := c.Conn.NewSession() | |
if e != nil { | |
return | |
} | |
r = &SSHResult{ | |
StdoutBuffer: &LogWriter{LogTo: c.Logger.Debug}, | |
StderrBuffer: &LogWriter{LogTo: c.Logger.Error}, | |
} | |
ses.Stdout = r.StdoutBuffer | |
ses.Stderr = r.StderrBuffer | |
c.Logger.Infof(`executing command "%s"`, s) | |
r.Error = ses.Run(s) | |
c.Logger.Debugf("executed in %.06f", time.Now().Sub(started).Seconds()) | |
ses.Close() | |
r.Runtime = time.Now().Sub(started) | |
return | |
} | |
package main | |
import ( | |
"time" | |
"fmt" | |
) | |
type SSHResult struct { | |
StdoutBuffer, StderrBuffer *LogWriter | |
Runtime time.Duration | |
Error error | |
} | |
func (r* SSHResult) Stdout() string { | |
return r.StdoutBuffer.String() | |
} | |
func (r* SSHResult) String() (out string) { | |
m := map[string]string { | |
"stdout": fmt.Sprintf("%d bytes", len(r.StdoutBuffer.String())), | |
"stderr": fmt.Sprintf("%d bytes", len(r.StderrBuffer.String())), | |
"runtime": fmt.Sprintf("%0.6f", r.Runtime.Seconds()), | |
} | |
return fmt.Sprintf("%+v", m) | |
} |
package main | |
import ( | |
"fmt" | |
"flag" | |
"os" | |
) | |
func ExitWith(reason string) { | |
fmt.Println("ERROR:", reason) | |
flag.PrintDefaults() | |
os.Exit(1) | |
} | |
func main() { | |
var host* string | |
var login* string | |
var cmd* string | |
host = flag.String("h", "", "Host") | |
login = flag.String("l", "", "Login") | |
cmd = flag.String("c", "", "Command to execute") | |
flag.Parse() | |
if *host == "" { | |
ExitWith("host must be provided") | |
} | |
if *login == "" { | |
ExitWith("login must be provided") | |
} | |
if *cmd == "" { | |
ExitWith("cmd must be provided") | |
} | |
client := NewSSHClient(*host, *login) | |
defer client.Close() | |
rsp, e := client.Execute(*cmd) | |
if e != nil { | |
return | |
} | |
client.Logger.Info("response:", rsp.String()) | |
return | |
} |