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