Golangでhttp Proxy経由でのssh接続を行わせる
Pocket

ちょっと前にGolangでsshの多段プロキシSocks5プロキシ経由での接続について調べたが、今回はhttpプロキシ経由の場合どうするのかについて調べてみた。
残念ながらhttpプロキシ経由の通信(CONNECTメソッドを利用したプロキシ)で*net.Connを取得できるライブラリは無いようで、そのあたりについては書かないとならないようだ。

幸いにもサンプルコードを書かれている人がいたので、それを使ってhttpプロキシを経由したssh接続のコードを記載する。

Sponsored Links

■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使われるか?という疑問はあるけど)

 

Pocket

Written by blacknon

インフラエンジニア(…のつもり)。 仕事で使うならクライアントはWindowsよりはUNIXの方が好き。 大体いつも眠い。

Leave a Comment

メールアドレスが公開されることはありません。

*