リモートホストへの操作などをしたくなるとsshを使いたくなる。
Goにはgolang.org/x/crypto/ssh というパッケージがあり比較的簡単に利用できる。
試してみる
使う準備 ClientConfig
最初に ssh.ClientConfig を準備する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
buf, _ := ioutil.ReadFile("path/to/id_rsa")
var key ssh.Signer
if pass := os.Getenv(envKey); pass != "" {
key, _ = ssh.ParsePrivateKeyWithPassphrase(buf, []byte(pass))
} else {
key, _ = ssh.ParsePrivateKey(buf)
}
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
|
使う準備 Connection,Session
ssh はコネクションの中で複数のセッションを確立できる。
1
2
3
4
|
conn, _ := ssh.Dial("tcp", rr.Hostport, config)
defer conn.Close()
session, _ := conn.NewSession()
defer session.Close()
|
session では Start(cmd), Run(cmd), Shell() のどれか1つを1回呼べる。
Start(cmd) の場合は Wait() で終了を待つこともできる。
Run(cmd)を使う
Run(cmd) は同期的に実行される。非同期実行したい場合は Start(cmd), Wait() を使う。
1
2
3
4
|
stdout, _ := session.StdoutPipe()
stdin, _ := session.StdinPipe()
stderr, _ := session.StderrPipe()
session.Run("ls")
|
Shell()を使う
session の入出力を繋げて Shell() を呼ぶと使える。
RequestPty() を使うとターミナルを設定できる。
ターミナルの設定に使うTerminalModesで定義するオプションの意味はRFC4254に定義されている。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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")
_ = session.RequestPty(term, 25, 80, modes)
session.Shell()
_ = session.Wait()
|
サンプルコード(cat remote log)
サンプルとして rcat なるツールを書いてみるとしたのようになる。
環境変数 SSH_PASS にssh鍵のパスワードを与えて使う形にした。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package main
import (
"io"
"os"
"github.com/masu-mi/rcat/remotelog"
)
func main() {
f := remotelog.RemoteReaderFactory{
Hostport: "host:port",
User: "user",
Signer: remotelog.SimpleSigner(
"path/to/.ssh/id_rsa",
"SSH_PASS",
),
}
io.Copy(os.Stdout, f.LogReader("/var/log/message"))
}
|
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
|
package remotelog
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
"golang.org/x/crypto/ssh"
)
type RemoteReaderFactory struct {
Hostport string
User string
Signer ssh.Signer
}
func (rr RemoteReaderFactory) LogReader(paths ...string) (bio *bufio.Reader) {
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
config := &ssh.ClientConfig{
User: rr.User,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(rr.Signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", rr.Hostport, config)
if err != nil {
panic(err)
}
defer conn.Close()
session, err := conn.NewSession()
if err != nil {
panic(err)
}
defer session.Close()
output, err := session.StdoutPipe()
if err != nil {
panic(err)
}
bio = bufio.NewReader(output)
wg.Done()
cmd := fmt.Sprintf("cat %s", strings.Join(paths, " "))
session.Run(cmd)
}()
wg.Wait()
return bio
}
func SimpleSigner(idRsaPath, envKey string) ssh.Signer {
buf, err := ioutil.ReadFile(idRsaPath)
if err != nil {
panic(err)
}
var key ssh.Signer
if pass := os.Getenv(envKey); pass != "" {
key, err = ssh.ParsePrivateKeyWithPassphrase(buf, []byte(pass))
} else {
key, err = ssh.ParsePrivateKey(buf)
}
if err != nil {
panic(err)
}
return key
}
|
簡単にSSHを利用できた。goroutineと合わせて複数ホストと並列で通信させて遊ぶとかできる。