github.com/turbot/go-exec-communicator@v0.0.0-20230412124734-9374347749b6/ssh/http_proxy.go (about) 1 package ssh 2 3 import ( 4 "bufio" 5 "fmt" 6 "net" 7 "net/http" 8 "net/url" 9 "time" 10 11 "golang.org/x/net/proxy" 12 ) 13 14 // Dialer implements for SSH over HTTP Proxy. 15 type proxyDialer struct { 16 proxy proxyInfo 17 // forwarding Dialer 18 forward proxy.Dialer 19 } 20 21 type proxyInfo struct { 22 // HTTP Proxy host or host:port 23 host string 24 // HTTP Proxy scheme 25 scheme string 26 // An immutable encapsulation of username and password details for a URL 27 userInfo *url.Userinfo 28 } 29 30 func newProxyInfo(host, scheme, username, password string) *proxyInfo { 31 p := &proxyInfo{ 32 host: host, 33 scheme: scheme, 34 } 35 36 p.userInfo = url.UserPassword(username, password) 37 38 if p.scheme == "" { 39 p.scheme = "http" 40 } 41 42 return p 43 } 44 45 func (p *proxyInfo) url() *url.URL { 46 return &url.URL{ 47 Scheme: p.scheme, 48 User: p.userInfo, 49 Host: p.host, 50 } 51 } 52 53 func (p *proxyDialer) Dial(network, addr string) (net.Conn, error) { 54 // Dial the proxy host 55 c, err := p.forward.Dial(network, p.proxy.host) 56 57 if err != nil { 58 return nil, err 59 } 60 61 err = c.SetDeadline(time.Now().Add(15 * time.Second)) 62 if err != nil { 63 return nil, err 64 } 65 66 // Generate request URL to host accessed through the proxy 67 reqUrl := &url.URL{ 68 Scheme: "", 69 Host: addr, 70 } 71 72 // Create a request object using the CONNECT method to instruct the proxy server to tunnel a protocol other than HTTP. 73 req, err := http.NewRequest("CONNECT", reqUrl.String(), nil) 74 if err != nil { 75 c.Close() 76 return nil, err 77 } 78 79 // If http proxy requires authentication, configure settings for basic authentication. 80 if p.proxy.userInfo.String() != "" { 81 username := p.proxy.userInfo.Username() 82 password, _ := p.proxy.userInfo.Password() 83 req.SetBasicAuth(username, password) 84 req.Header.Add("Proxy-Authorization", req.Header.Get("Authorization")) 85 } 86 87 // Do not close the connection after sending this request and reading its response. 88 req.Close = false 89 90 // Writes the request in the form expected by an HTTP proxy. 91 err = req.Write(c) 92 if err != nil { 93 c.Close() 94 return nil, err 95 } 96 97 res, err := http.ReadResponse(bufio.NewReader(c), req) 98 99 if err != nil { 100 res.Body.Close() 101 c.Close() 102 return nil, err 103 } 104 105 res.Body.Close() 106 107 if res.StatusCode != http.StatusOK { 108 c.Close() 109 return nil, fmt.Errorf("Connection Error: StatusCode: %d", res.StatusCode) 110 } 111 112 return c, nil 113 } 114 115 // NewHttpProxyDialer generate Http Proxy Dialer 116 func newHttpProxyDialer(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { 117 var proxyUserName, proxyPassword string 118 if u.User != nil { 119 proxyUserName = u.User.Username() 120 proxyPassword, _ = u.User.Password() 121 } 122 123 pd := &proxyDialer{ 124 proxy: *newProxyInfo(u.Host, u.Scheme, proxyUserName, proxyPassword), 125 forward: forward, 126 } 127 128 return pd, nil 129 } 130 131 // RegisterDialerType register schemes used by `proxy.FromURL` 132 func RegisterDialerType() { 133 proxy.RegisterDialerType("http", newHttpProxyDialer) 134 proxy.RegisterDialerType("https", newHttpProxyDialer) 135 } 136 137 // NewHttpProxyConn create a connection to connect through the proxy server. 138 func newHttpProxyConn(p *proxyInfo, targetAddr string) (net.Conn, error) { 139 pd, err := proxy.FromURL(p.url(), proxy.Direct) 140 141 if err != nil { 142 return nil, err 143 } 144 145 proxyConn, err := pd.Dial("tcp", targetAddr) 146 147 if err != nil { 148 return nil, err 149 } 150 151 return proxyConn, err 152 }