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