GoでSSH接続してコマンドを叩く

下記にてWin10でSSHサーバーを立ててみたので、
ならGoからいじってみようということでいろいろ試してみます。

https://qiita.com/hiro_nico/items/48d091a7200f2012650b

## 試した環境

* Windows 10 Pro (64bit) 20H2
    * SSHサーバー

別のPCに接続するわけではなく、同じPC内でやり取りする感じです。

## やりたいこと

Go から SSHサーバーに 接続してコマンド叩く！

## どうやるか

まずは golang.org/x/crypto/ssh の godoc を見てみる。

https://pkg.go.dev/golang.org/x/crypto/ssh?utm_source=godoc#Dial

Dial() の Example 使ったらできそう。

1. Dial する
2. Session 作る
3. Run する

```go:ssh.go
package main

import (
	"bytes"
	"fmt"
	"log"

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

func main() {
	config := &ssh.ClientConfig{
		User: "hiro",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), // password認証は設定
	}
	client, err := ssh.Dial("tcp", "localhost:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}
	defer client.Close()

	session, err := client.NewSession()
	if err != nil {
		log.Fatal("Failed to create session: ", err)
	}
	defer session.Close()

	var b bytes.Buffer
	session.Stdout = &b
	if err := session.Run("/usr/bin/whoami"); err != nil {
		log.Fatal("Failed to run: " + err.Error())
	}
	fmt.Println(b.String())
}
```

パスワード認証な設定にしつつ、IDとPASSを変えて実行してみる。

```shell:output
$ ssh.exe
hiro

```

できた！


糸冬。

・・・だけだとあっけないので、もう少しいじってみます。

### ファイルを送ってみる

SCPを使ってファイルを送ってみる。

1. epoch秒＋文字な入力を送ってファイルを作成
2. 送られたファイルの内容を表示
3. ファイルに追記・表示

コードはいろいろまとめたほうが綺麗になるし再利用できるけど
とりあえず試すだけなので適当実装ですｗ

```go:ssh.go
package main

import (
	"bytes"
	"fmt"
	"log"
	"time"

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

func main() {
	config := &ssh.ClientConfig{
		User: "hiro",
		Auth: []ssh.AuthMethod{
			ssh.Password("hirohito"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), // password認証は設定
	}
	client, err := ssh.Dial("tcp", "localhost:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}
	defer client.Close()

	// scp: send content remotely
	fmt.Println("scp -----")
	{
		session, err := client.NewSession()
		if err != nil {
			log.Fatal("Failed to create session: ", err)
		}
		defer session.Close()

		go func() {
			w, _ := session.StdinPipe()
			defer w.Close()
			t := time.Now()

			content := fmt.Sprintf("%d\n%s", t.Unix(), "hoge\nfuga\npiyo\n")

			fmt.Fprintln(w, "C0644", len(content), "hoge.txt")
			fmt.Fprint(w, content)
			fmt.Fprint(w, "\x00")
		}()

		if err := session.Run("/usr/bin/scp -qrt ./"); err != nil {
			log.Fatal("Failed to run: " + err.Error())
		}
	}

	// wait
	time.Sleep(1 * time.Second)

	// view sent file
	fmt.Println("cat -----")
	{
		session, err := client.NewSession()
		if err != nil {
			log.Fatal("Failed to create session: ", err)
		}
		defer session.Close()

		var b bytes.Buffer
		session.Stdout = &b
		if err := session.Run("cat hoge.txt"); err != nil {
			log.Fatal("Failed to run: " + err.Error())
		}
		fmt.Println(b.String())
	}

	// add to file
	fmt.Println("add -----")
	{
		session, err := client.NewSession()
		if err != nil {
			log.Fatal("Failed to create session: ", err)
		}
		defer session.Close()

		var b bytes.Buffer
		session.Stdout = &b
		if err := session.Run(`perl -e 'printf qq/%s\n/, time' >> hoge.txt && cat hoge.txt`); err != nil {
			log.Fatal("Failed to run: " + err.Error())
		}
		fmt.Println(b.String())
	}
}
```

```shell:output
$ ssh.exe
scp -----
cat -----
1615619729
hoge
fuga
piyo

add -----
1615619729
hoge
fuga
piyo
1615619730
```
SCPなノウハウは下記の記事を参考に作成。

https://qiita.com/yamasaki-masahide/items/56bea80dd8a044e0ac4b

## SFTP

いろいろ調べてたら、ファイル転送系は SFTP 使った方が楽かもしれない。

https://pkg.go.dev/github.com/pkg/sftp

## 感想

公式のExampleをなぞっただけですが、手順がわかったのでいろいろ使えそう！