じゃあ、おうちで学べる

本能を呼び覚ますこのコードに、君は抗えるか

Golangでcrypto/ssh をやっていく 単パスワード認証と単鍵認証編

Nヵ月ぶりに技術ブログを書くで候

概要

正直なことを話すとたぶん、人はcrypto: golang.org/x/crypto/sshさえ読めばGolangでSSHを書けるようになる。しかし、50回ぐらい読んでいるので個人的によく使うモジュールについてのメモである。 本当にcrypto: golang.org/x/crypto/sshのドキュメントが優秀なのでここを見れば解決するので、とりあえず読んでくれ。それだけではこのブログの価値はクソ以下なのでもはや、サンプルコードを書くしかない

パスワード認証

&ssh.ClientConfigでは、認証方式に関係なくHostKeyCallbackを指定しなければならない。ここでは手軽に試すためにHostKeyCallback: ssh.InsecureIgnoreHostKey() を設定しているが、これはホスト鍵を検証しないので検証用に限る。実運用ではgolang.org/x/crypto/ssh/knownhostsを使い、known_hostsで検証する。
あとは、ssh.Dial でセッションを繋いで(切るの忘れずに) conn.NewSession()でアクセスを行って session.Stdout = &bで標準出力を確認しながらsession.Run()でコマンドを実行させる。

https://godoc.org/golang.org/x/crypto/ssh#Dial より

Dialは、指定されたSSHサーバーへのクライアント接続を開始します。これは、指定されたネットワークアドレスに接続し、SSHハンドシェイクを開始してからクライアントをセットアップする便利な機能です。着信チャネルおよび要求へのアクセスには、代わりにNewClientConnでnet.Dialを使用します。 上記のような

package main

import (
        "bytes"
        "log"

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

func main() {

        ip := "****.**.***.***"
        port := "22"
        user := "root"
        pass := "********************************"
        config := &ssh.ClientConfig{
                User:            user,
                HostKeyCallback: ssh.InsecureIgnoreHostKey(),   // https://github.com/golang/go/issues/19767
                Auth: []ssh.AuthMethod{
                        ssh.Password(pass),
                },
        }

        conn, err := ssh.Dial("tcp", ip+":"+port, config)
        if err != nil {
                log.Println(err)
                return
        }
        defer conn.Close()

        session, err := conn.NewSession()
        if err != nil {
                log.Println(err)
                return
        }
        defer session.Close()

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

鍵認証

鍵認証では、秘密鍵を読み込み、ssh.ParsePrivateKeyで得た署名用の値をssh.PublicKeysへ渡す。以下はパスフレーズのない秘密鍵を使う例。公開鍵は接続先に登録しておく。ホスト鍵の検証については、前節と同じ注意が必要になる。

package main

import (
        "bytes"
        "io/ioutil"
        "log"

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

func main() {

        ip := "***.***.***.***"
        port := "22"
        user := "root"
        key, err := ioutil.ReadFile("id_rsa")
        if err != nil {
                log.Println(err)
                return
        }
        signer, err := ssh.ParsePrivateKey(key)
        if err != nil {
                log.Println(err)
                return
        }
        config := &ssh.ClientConfig{
                User:            user,
                HostKeyCallback: ssh.InsecureIgnoreHostKey(),
                Auth: []ssh.AuthMethod{
                        ssh.PublicKeys(signer),
                },
        }

        conn, err := ssh.Dial("tcp", ip+":"+port, config)
        if err != nil {
                log.Println(err)
                return
        }
        defer conn.Close()

        session, err := conn.NewSession()
        if err != nil {
                log.Println(err)
                return
        }
        defer session.Close()

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

利用したコードはここ workspace_2019/blog/crypto_ssh at master · nwiizo/workspace_2019 · GitHub に載せてあるので自由に使ってください。

本文の鍵認証例は、ssh.PublicKeysの公式例を参照して訂正した。リンク先リポジトリのコードを今回更新したものではない。

まとめ

基本的には crypto: golang.org/x/crypto/ssh を読めばいいのです。が、もし Golang で sshをいい感じにやっていくにはまだまだ工夫していかねばなりません。設定ファイルとの呼び出しをどうするのか?N個同時にセッションを持ちたい時にはどういった工夫が必要なのか?考慮すべき事項はたくさんあります。ですが 皆さん、オペレーションはどんどん自動化していきましょう。