Golangで、自前のsshのクライアントを作ってるのだけど、それに証明書認証の機能を追加したかったので調べてた。 で、以下のようなコードでできるようになったので備忘で残しておく(一応こちらにもソースを置いておく)。

ssh_term_cert.go
package main import ( "fmt" "io" "io/ioutil" "os" "os/signal" "os/user" "strings" "syscall" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" ) func main() { Host := "172.20.100.122" Port := "22" User := "blacknon" Cert := "/Users/blacknon/.ssh/user.key-cert.pub" Certkey := "/Users/blacknon/.ssh/user.key" // PATHをフルパスへ変換する usr, _ := user.Current() cert := strings.Replace(Cert, "~", usr.HomeDir, 1) certkey := strings.Replace(Certkey, "~", usr.HomeDir, 1) // 秘密鍵のSignerを作成する keyData, err := ioutil.ReadFile(certkey) if err != nil { fmt.Println(err) os.Exit(1) } keySigner, _ := ssh.ParsePrivateKey(keyData) // 証明書を読み込む certData, err := ioutil.ReadFile(cert) if err != nil { fmt.Println(err) os.Exit(1) } // 証明書から公開鍵を取得する pubkey, _, _, _, err := ssh.ParseAuthorizedKey(certData) if err != nil { fmt.Println(err) os.Exit(1) } // 証明書をデータとして取得 certificate, ok := pubkey.(*ssh.Certificate) if !ok { fmt.Println("ng") os.Exit(1) } // 証明書からsignerを作成する signer, err := ssh.NewCertSigner(certificate, keySigner) // authを作成する var auth []ssh.AuthMethod // ssh.PublicKeys(signers) auth = append(auth, ssh.PublicKeys(signer)) // Create sshClientConfig sshConfig := &ssh.ClientConfig{ User: User, Auth: auth, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // SSH connect. client, err := ssh.Dial("tcp", Host+":"+Port, sshConfig) // Create Session session, err := client.NewSession() defer session.Close() // キー入力を接続先が認識できる形式に変換する(ここがキモ) 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) } // log := new(bytes.Buffer) logFile, _ := os.OpenFile("./ssh_term_with_log.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) session.Stdout = io.MultiWriter(os.Stdout, logFile) session.Stderr = io.MultiWriter(os.Stderr, logFile) 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) } }