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  }