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  }