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  }