SSH with Go
GoSF Meetup
25 August 2016
Chris Roche
Software Engineer, Lyft
Chris Roche
Software Engineer, Lyft
Core Services Team at Lyft
Previously, Core Platform & DevOps at VSCO
Either:
$ ssh -o ProxyCommand='ssh proxy.example.com nc example.com 22' example.com
Or:
$ sshThru proxy.example.com example.com
func Connect(host string, methods ...ssh.AuthMethod) (*ssh.Client, error) { cfg := ssh.ClientConfig{ User: "chris", Auth: methods, } return ssh.Dial("tcp", host, &cfg) }
ssh.Password // static secret ssh.PasswordCallback // ask the user ssh.KeyboardInteractive // server-provided prompts ssh.RetryableAuthMethod // decorator for above ssh.PublicKeys // key pairs ssh.PublicKeysCallback // SSH-Agent
func KeyPair(keyFile string) (ssh.AuthMethod, error) { pem, err := ioutil.ReadFile(keyFile) if err != nil { return nil, err } key, err := ssh.ParsePrivateKey(pem) if err != nil { return nil, err } return ssh.PublicKeys(key), nil }
func SSHAgent() (ssh.AuthMethod, error) { agentSock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) if err != nil { return nil, err } return ssh.PublicKeysCallback(agent.NewClient(agentSock).Signers), nil }
agent, err := SSHAgent() // handle error keyPair, err := KeyPair("/home/chris/.ssh/id_rsa") // handle error client, err := Connect("example.com:22", agent, keyPair) // handle error defer client.Close()
sess, err := client.NewSession() // handle error defer sess.Close() sess.Stdout = os.Stdout sess.Setenv("LS_COLORS", os.Getenv("LS_COLORS")) err = sess.Run("ls -lah") // handle error
sess.Stdin = os.Stdin sess.Stdout = os.Stdout sess.Stderr = os.Stderr modes := ssh.TerminalModes{ ssh.ECHO: 1, // please print what I type ssh.ECHOCTL: 0, // please don't print control chars ssh.TTY_OP_ISPEED: 115200, // baud in ssh.TTY_OP_OSPEED: 115200, // baud out } termFD := int(os.Stdin.Fd()) w, h, _ := terminal.GetSize(termFD) termState, _ := terminal.MakeRaw(termFD) defer terminal.Restore(termFD, termState) sess.RequestPty("xterm-256color", h, w, modes) sess.Shell() sess.Wait()
func Proxy(bastion *ssh.Client, host string, clientCfg *ssh.ClientConfig) *ssh.Client { netConn, _ := bastion.Dial("tcp", host) conn, chans, reqs, _ := ssh.NewClientConn(netConn, host, clientCfg) return ssh.NewClient(conn, chans, reqs) }
func TailLog(name string, client *ssh.Client, lines chan<- string) { sess, _ := client.NewSession() defer sess.Close() out, _ := sess.StdoutPipe() scanner := bufio.NewScanner(out) scanner.Split(bufio.ScanLines) sess.Start("tail -f /var/log/app.log") for scanner.Scan() { lines <- fmt.Sprintf("[%s] %s", name, scanner.Text()) } sess.Wait() }
func MultiTail(bastion *ssh.Client, hosts []string, cfg *ssh.ClientConfig) { lines := make(chan string) for _, remote := range hosts { go TailLog( remote, Proxy(bastion, remote, cfg), lines, ) } for l := range lines { log.Print(l) } }
func Tunnel(client *ssh.Client, localHost, remoteHost string) { listener, _ := net.Listen("tcp", localHost) defer listener.Close() for { localConn, _ := listener.Accept() remoteConn, _ := client.Dial("tcp", remoteHost) go copy(localConn, remoteConn) go copy(remoteConn, localConn) } }
func ReverseTunnel(client *ssh.Client, remoteHost string) { listener, _ := client.Listen("tcp", remoteHost) defer listener.Close() handler := func(res http.ResponseWriter, req *http.Request) { fmt.Fprint(res, "Hello, GoSF!") } http.Serve(listener, http.HandlerFunc(handler)) }
Chris Roche
Software Engineer, Lyft