SOCKS ダイヤラーがトランスポートを用意し、SSH パッケージがその上で SSH ハンドシェイクを行います。あとから ssh.Dial を呼ぶと、そのトランスポートを使わずに別の接続を作ってしまいます。

すでに x/net/proxy と x/crypto/ssh を使っているアプリケーションでは、補助関数が設定済みの SSH クライアントを返せます:

func dialSSHViaSOCKS(proxyAddr, sshAddr string,
    config *ssh.ClientConfig) (*ssh.Client, error) {
    dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
    if err != nil {
        return nil, err
    }

    conn, err := dialer.Dial("tcp", sshAddr)
    if err != nil {
        return nil, err
    }

    transport, channels, requests, err :=
        ssh.NewClientConn(conn, sshAddr, config)
    if err != nil {
        conn.Close()
        return nil, err
    }

    return ssh.NewClient(transport, channels, requests), nil
}

ここでのパッケージ名は golang.org/x/net/proxy と golang.org/x/crypto/ssh です。proxyAddr は SOCKS リスナーを、sshAddr は最終的なサーバーを host:port として示します。

config にはアプリケーション通常の SSH ユーザー、認証方法、HostKeyCallback を渡します。knownhosts パッケージは信頼できる known_hosts ファイルからその callback を組み立てられます。宛先のホスト名を NewClientConn に渡し、ホスト鍵の照合が意図した SSH 識別子を使うようにします。

nil 引数は SOCKS の資格情報を選びません。許可された公開 SSH 宛先なら、SOCKS5 プロキシ一覧のアカウント不要の項目が、実際の SSH ホストとポートを許可していれば proxyAddr を提供できます。必要ならプロキシのアカウントを持つ proxy.Auth を渡します。SSH 認証は config に残ります。

NewClient は NewClientConn が返す受信チャネルとリクエストのストリームを受け取り、そのクライアント機構を扱います。仕事が終わったら返された SSH クライアントを閉じます。本番の接続処理には制限付きのダイヤルとハンドシェイクの方針も必要です。この補助関数はトランスポートの受け渡しを示すだけです。

関連記事