github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/proxy.go (about)

     1  /*
     2  Copyright 2022 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package utils
    18  
    19  import (
    20  	"net/http"
    21  	"net/url"
    22  	"strings"
    23  
    24  	"github.com/gravitational/trace"
    25  	"golang.org/x/net/http/httpproxy"
    26  )
    27  
    28  // GetProxyURL gets the HTTP proxy address to use for a given address, if any.
    29  func GetProxyURL(dialAddr string) *url.URL {
    30  	addrURL, err := ParseURL(dialAddr)
    31  	if err != nil || addrURL == nil {
    32  		return nil
    33  	}
    34  
    35  	proxyFunc := httpproxy.FromEnvironment().ProxyFunc()
    36  	if addrURL.Scheme != "" {
    37  		proxyURL, err := proxyFunc(addrURL)
    38  		if err != nil {
    39  			return nil
    40  		}
    41  		return proxyURL
    42  	}
    43  
    44  	for _, scheme := range []string{"https", "http"} {
    45  		addrURL.Scheme = scheme
    46  		proxyURL, err := proxyFunc(addrURL)
    47  		if err == nil && proxyURL != nil {
    48  			return proxyURL
    49  		}
    50  	}
    51  
    52  	return nil
    53  }
    54  
    55  // ParseURL parses an absolute URL. Unlike url.Parse, absolute URLs without a scheme are allowed.
    56  func ParseURL(addr string) (*url.URL, error) {
    57  	if addr == "" {
    58  		return nil, nil
    59  	}
    60  	addrURL, err := url.Parse(addr)
    61  	if err == nil && addrURL.Host != "" {
    62  		return addrURL, nil
    63  	}
    64  
    65  	// url.Parse won't correctly parse an absolute URL without a scheme, so try again with a scheme.
    66  	addrURL, err2 := url.Parse("http://" + addr)
    67  	if err2 != nil {
    68  		return nil, trace.NewAggregate(err, err2)
    69  	}
    70  	addrURL.Scheme = ""
    71  	return addrURL, nil
    72  }
    73  
    74  // HTTPRoundTripper is a wrapper for http.Transport that
    75  // - adds extra HTTP headers to all requests, and
    76  // - downgrades requests to plain HTTP when proxy is at localhost and the wrapped http.Transport has TLSClientConfig.InsecureSkipVerify set to true.
    77  type HTTPRoundTripper struct {
    78  	*http.Transport
    79  	// extraHeaders is a map of extra HTTP headers to be included in requests.
    80  	extraHeaders map[string]string
    81  	// isProxyHTTPLocalhost indicates that the HTTP_PROXY is at "http://localhost"
    82  	isProxyHTTPLocalhost bool
    83  }
    84  
    85  // NewHTTPRoundTripper creates a new initialized HTTP roundtripper.
    86  func NewHTTPRoundTripper(transport *http.Transport, extraHeaders map[string]string) *HTTPRoundTripper {
    87  	proxyConfig := httpproxy.FromEnvironment()
    88  	return &HTTPRoundTripper{
    89  		Transport:            transport,
    90  		extraHeaders:         extraHeaders,
    91  		isProxyHTTPLocalhost: strings.HasPrefix(proxyConfig.HTTPProxy, "http://localhost"),
    92  	}
    93  }
    94  
    95  // CloseIdleConnections forwards closing of idle connections on to the wrapped
    96  // transport. This is required to ensure that the underlying [http.Transport] has
    97  // its idle connections closed per the [http.Client] docs:
    98  //
    99  //	> If the Client's Transport does not have a CloseIdleConnections method
   100  //	> then this method does nothing.
   101  func (rt *HTTPRoundTripper) CloseIdleConnections() {
   102  	rt.Transport.CloseIdleConnections()
   103  }
   104  
   105  // RoundTrip executes a single HTTP transaction. Part of the RoundTripper interface.
   106  func (rt *HTTPRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   107  	// Add extra HTTP headers.
   108  	for header, v := range rt.extraHeaders {
   109  		req.Header.Add(header, v)
   110  	}
   111  
   112  	// Use plain HTTP if proxying via http://localhost in insecure mode.
   113  	tlsConfig := rt.Transport.TLSClientConfig
   114  	if rt.isProxyHTTPLocalhost && tlsConfig != nil && tlsConfig.InsecureSkipVerify {
   115  		req.URL.Scheme = "http"
   116  	}
   117  
   118  	return rt.Transport.RoundTrip(req)
   119  }