goでsshクライアントの実装メモ

Goで `golang.org/x/crypto/ssh` を使えばSSHクライアントが実装できるという事なので偉大なる先人様達のコードを参考(~~コピぺ~~)にしながら実装してみたメモです。

# 目的

GoでSSH接続してコマンド操作できるクライアントツールを作ってみる

# ドキュメント

[https://godoc.org/golang.org/x/crypto/ssh](https://godoc.org/golang.org/x/crypto/ssh)

# ツール

## ソース

```go:main.go
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"syscall"

	"github.com/urfave/cli"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

func doMain(c *cli.Context) {
	ce := func(err error, msg string) {
		if err != nil {
			log.Fatalf("%s error: %v", msg, err)
		}
	}

	// check private key or password.
	var passwd string
	auth := []ssh.AuthMethod{}
	if c.String("key") != "" {
		key, err := ioutil.ReadFile(c.String("key"))
		ce(err, "private key")

		signer, err := ssh.ParsePrivateKey(key)
		ce(err, "signer")

		auth = append(auth, ssh.PublicKeys(signer))
	} else {
		// check password.
		if c.String("password") != "" {
			passwd = c.String("password")
		} else {
			fmt.Print("Password: ")
			inPasswd, err := terminal.ReadPassword(int(syscall.Stdin))
			ce(err, "password")
			passwd = string(inPasswd)
		}
		auth = append(auth, ssh.Password(passwd))
	}

	// set ssh config.
	sshConfig := &ssh.ClientConfig{
		User:            c.String("user"),
		Auth:            auth,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// SSH connect.
	client, err := ssh.Dial("tcp", c.String("host")+":"+c.String("port"), sshConfig)
	ce(err, "dial")

	session, err := client.NewSession()
	ce(err, "new session")
	defer session.Close()

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	modes := ssh.TerminalModes{
		ssh.ECHO:          0,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}
	term := os.Getenv("TERM")
	err = session.RequestPty(term, 25, 80, modes)
	ce(err, "request pty")

	err = session.Shell()
	ce(err, "start shell")

	err = session.Wait()
	ce(err, "return")
}

func main() {
	app := cli.NewApp()
	app.Name = "sshClient"
	app.Usage = "SSh Client Tool"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "user, u",
			Value: "root",
			Usage: "SSH login user",
		},
		cli.StringFlag{
			Name:  "password, p",
			Usage: "SSH login user password",
		},
		cli.StringFlag{
			Name:  "host",
			Value: "localhost",
			Usage: "SSH connect host",
		},
		cli.StringFlag{
			Name:  "port",
			Value: "22",
			Usage: "SSH connect port",
		},
		cli.StringFlag{
			Name:  "key, k",
			Usage: "SSH private key",
		},
	}
	app.Action = doMain
	app.Run(os.Args)
}
```

## 使ってみる

### パスワード認証

```
[root@localhost hoge5]# go run main.go --host 192.168.0.233
Password:
Last login: Mon Jan 22 20:37:54 2018 from 192.168.0.231
[root@localhost ~]# ls
anaconda-ks.cfg
[root@localhost ~]# exit
ログアウト
```

### 鍵認証

```
[root@localhost hoge5]# go run main.go --host 192.168.0.233 --key ../../id_rsa
Last login: Mon Jan 22 20:58:42 2018 from 192.168.0.231
[root@localhost ~]# ls
anaconda-ks.cfg
[root@localhost ~]# exit
ログアウト
```

## 課題

やりたいことのほとんどが出来ましたが、クライアントの標準入力を接続先へ送信しているので入力でタブ補完が効かない。。。

# 参考

* [https://github.com/inatus/ssh-client-go/blob/master/main.go](https://github.com/inatus/ssh-client-go/blob/master/main.go)
* [https://play.golang.org/p/R4f6bPP2RM](https://play.golang.org/p/R4f6bPP2RM)
* [https://www.youtube.com/watch?v=SQCiuYfuV4M](https://www.youtube.com/watch?v=SQCiuYfuV4M)
* [https://www.ietf.org/rfc/rfc4254.txt](https://www.ietf.org/rfc/rfc4254.txt)