ちょっと前に Golangでsshの多段プロキシ、 Socks5プロキシ経由での接続について調べたが、今回はhttpプロキシ経由の場合どうするのかについて調べてみた。
残念ながらhttpプロキシ経由の通信(CONNECTメソッドを利用したプロキシ)で*net.Connを取得できるライブラリは無いようで、そのあたりについては書かないとならないようだ。 幸いにも サンプルコード を書かれている人がいたので、それを使ってhttpプロキシを経由したssh接続のコードを記載する。
実行可能なコードはこちらにもおいてある。
ssh_term_http_proxy.go// proxyで使用するdirect type direct struct{} func (direct) Dial(network, addr string) (net.Conn, error) { return net.Dial(network, addr) } // http(s)用プロキシのstruct type httpProxy struct { host string haveAuth bool username string password string forward proxy.Dialer } // httpProxyのDial関数(CONNECTメソッドを呼び出している) func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { c, err := s.forward.Dial("tcp", s.host) if err != nil { return nil, err } reqURL, err := url.Parse("http://" + addr) if err != nil { c.Close() return nil, err } reqURL.Scheme = "" req, err := http.NewRequest("CONNECT", reqURL.String(), nil) if err != nil { c.Close() return nil, err } req.Close = false if s.haveAuth { req.SetBasicAuth(s.username, s.password) } req.Header.Set("User-Agent", "Poweredby Golang") err = req.Write(c) if err != nil { c.Close() return nil, err } resp, err := http.ReadResponse(bufio.NewReader(c), req) if err != nil { // TODO close resp body ? resp.Body.Close() c.Close() return nil, err } resp.Body.Close() if resp.StatusCode != 200 { c.Close() err = fmt.Errorf("Connect server using proxy error, StatusCode [%d]", resp.StatusCode) return nil, err } return c, nil } // httpプロキシの生成関数 func newHTTPProxy(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { s := new(httpProxy) s.host = uri.Host s.forward = forward if uri.User != nil { s.haveAuth = true s.username = uri.User.Username() s.password, _ = uri.User.Password() } return s, nil } // @note: // proxy1 ... プロキシサーバ // target ... プロキシサーバ経由でログインするサーバ // 認証が必要な場合は、生成するURLを以下の様にする // (http://USERNAME:PASSWORD@PROXYIP:PROXYPORT) // // 参考: // https://gist.github.com/jim3ma/3750675f141669ac4702bc9deaf31c6b func main() { // 最初にProxyのDialerTypeを登録する proxy.RegisterDialerType("http", newHTTPProxy) proxy.RegisterDialerType("https", newHTTPProxy) // ネットワークへ直接接続するプロキシの定義 directProxy := direct{} // proxy1の情報 proxy1Host := "proxy1.server.local" proxy1Port := "54321" // proxy1User := "user" // proxy1Pass := "password" // targetの情報 targetHost := "target.server.local" targetPort := "22" targetUser := "user" targetPass := "password" // sshClientConfigの作成(target) targetSshConfig := &ssh.ClientConfig{ User: targetUser, Auth: []ssh.AuthMethod{ssh.Password(targetPass)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // urlの生成 proxy1URL := "http://" + proxy1Host + ":" + proxy1Port proxy1URI, _ := url.Parse(proxy1URL) proxy1Dialer, err := proxy.FromURL(proxy1URI, directProxy) proxy1Conn, err := proxy1Dialer.Dial("tcp", net.JoinHostPort(targetHost, targetPort)) if err != nil { fmt.Println(err) } // TargetへのsshClientを作成 pConnect, pChans, pReqs, err := ssh.NewClientConn(proxy1Conn, net.JoinHostPort(targetHost, targetPort), targetSshConfig) if err != nil { fmt.Println(err) } client := ssh.NewClient(pConnect, pChans, pReqs)
net/proxyで使うためのDialerを作って、それを経由するようにしてる感じ。
今どきあまりないだろうけど、環境によってはhttpプロキシを経由しないと外部サーバに繋げないようなところもあるので、そういうときに役に立ちそうだ。 (そんな環境でGolang使われるか?という疑問はあるけど)