自前で作ってるGolangで書いたsshクライアント に、ssh-agentの機能を追加できないかなと思い調べてみたところ、ライブラリがすでに用意されているようで結構簡単に実装できそうだということがわかった。 というわけで、ssh-agentで鍵を転送できるsshクライアント(ターミナル接続可能)のサンプルコードを書いてみる。

以下、そのコード。ソースは こちら にもあげている。

ssh_term_agent.go
package main import ( "fmt" "io/ioutil" "os" "os/signal" "syscall" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/terminal" ) var ( host = "target_host" port = "22" user = "user" pass = "password" ssh_agent_key = "/path/to/key/id_rsa" // フルPATHの必要あり ) func main() { // ssh agentに読み込ませる鍵ファイルを読み込む buf, err := ioutil.ReadFile(ssh_agent_key) if err != nil { panic(err) } // ssh agentに読み込ませる鍵ファイルをパースする(interface形式) key, err := ssh.ParseRawPrivateKey(buf) if err != nil { panic(err) } // ssh agentで転送するkeyringを作成する keyring := agent.NewKeyring() // keyringに鍵追加前のリストを出力 fmt.Println(keyring.List()) // keyringに鍵を追加する err = keyring.Add(agent.AddedKey{ PrivateKey: key, ConfirmBeforeUse: true, LifetimeSecs: 3600, }) if err != nil { fmt.Println("add keyring error: %s", err) } // keyringに鍵追加後のリストを出力 fmt.Println(keyring.List()) // Create sshClientConfig sshConfig := &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{ ssh.Password(pass), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // SSH connect. client, err := ssh.Dial("tcp", host+":"+port, sshConfig) // Create Session session, err := client.NewSession() defer session.Close() // ssh agentに鍵を転送する agent.ForwardToAgent(client, keyring) agent.RequestAgentForwarding(session) // キー入力を接続先が認識できる形式に変換する(ここがキモ) fd := int(os.Stdin.Fd()) state, err := terminal.MakeRaw(fd) if err != nil { fmt.Println(err) } defer terminal.Restore(fd, state) // ターミナルサイズの取得 w, h, err := terminal.GetSize(fd) if err != nil { fmt.Println(err) } modes := ssh.TerminalModes{ ssh.ECHO: 1, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400, } err = session.RequestPty("xterm", h, w, modes) if err != nil { fmt.Println(err) } session.Stdout = os.Stdout session.Stderr = os.Stderr session.Stdin = os.Stdin err = session.Shell() if err != nil { fmt.Println(err) } // ターミナルサイズの変更検知・処理 signal_chan := make(chan os.Signal, 1) signal.Notify(signal_chan, syscall.SIGWINCH) go func() { for { s := <-signal_chan switch s { case syscall.SIGWINCH: fd := int(os.Stdout.Fd()) w, h, _ = terminal.GetSize(fd) session.WindowChange(h, w) } } }() err = session.Wait() if err != nil { fmt.Println(err) } }

実行後、ssh接続されたターミナルで「ssh-add -l」を実行すると鍵が転送されているのが確認できる。 個人的にはあまりssh-agentを使わず、そのままProxyでつなげることのほうが多いのだけど、使ってみると便利だし、今後は積極的に使っていこうと思う(lssh にも実装するし)。