github.com/keys-pub/mattermost-server@v4.10.10+incompatible/utils/httpclient.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package utils
     5  
     6  import (
     7  	"context"
     8  	"crypto/tls"
     9  	"errors"
    10  	"net"
    11  	"net/http"
    12  	"time"
    13  )
    14  
    15  const (
    16  	connectTimeout = 3 * time.Second
    17  	requestTimeout = 30 * time.Second
    18  )
    19  
    20  var reservedIPRanges []*net.IPNet
    21  
    22  // IsReservedIP checks whether the target IP belongs to reserved IP address ranges to avoid SSRF attacks to the internal
    23  // network of the Mattermost server
    24  func IsReservedIP(ip net.IP) bool {
    25  	for _, ipRange := range reservedIPRanges {
    26  		if ipRange.Contains(ip) {
    27  			return true
    28  		}
    29  	}
    30  	return false
    31  }
    32  
    33  // IsOwnIP handles the special case that a request might be made to the public IP of the host which on Linux is routed
    34  // directly via the loopback IP to any listening sockets, effectively bypassing host-based firewalls such as firewalld
    35  func IsOwnIP(ip net.IP) (bool, error) {
    36  	interfaces, err := net.Interfaces()
    37  	if err != nil {
    38  		return false, err
    39  	}
    40  
    41  	for _, interf := range interfaces {
    42  		addresses, err := interf.Addrs()
    43  		if err != nil {
    44  			return false, err
    45  		}
    46  
    47  		for _, addr := range addresses {
    48  			var selfIP net.IP
    49  			switch v := addr.(type) {
    50  			case *net.IPNet:
    51  				selfIP = v.IP
    52  			case *net.IPAddr:
    53  				selfIP = v.IP
    54  			}
    55  
    56  			if ip.Equal(selfIP) {
    57  				return true, nil
    58  			}
    59  		}
    60  	}
    61  
    62  	return false, nil
    63  }
    64  
    65  func init() {
    66  	for _, cidr := range []string{
    67  		// See https://tools.ietf.org/html/rfc6890
    68  		"0.0.0.0/8",      // This host on this network
    69  		"10.0.0.0/8",     // Private-Use
    70  		"127.0.0.0/8",    // Loopback
    71  		"169.254.0.0/16", // Link Local
    72  		"172.16.0.0/12",  // Private-Use Networks
    73  		"192.168.0.0/16", // Private-Use Networks
    74  		"::/128",         // Unspecified Address
    75  		"::1/128",        // Loopback Address
    76  		"fc00::/7",       // Unique-Local
    77  		"fe80::/10",      // Linked-Scoped Unicast
    78  	} {
    79  		_, parsed, err := net.ParseCIDR(cidr)
    80  		if err != nil {
    81  			panic(err)
    82  		}
    83  		reservedIPRanges = append(reservedIPRanges, parsed)
    84  	}
    85  }
    86  
    87  type DialContextFunction func(ctx context.Context, network, addr string) (net.Conn, error)
    88  
    89  var AddressForbidden error = errors.New("address forbidden")
    90  
    91  func dialContextFilter(dial DialContextFunction, allowHost func(host string) bool, allowIP func(ip net.IP) bool) DialContextFunction {
    92  	return func(ctx context.Context, network, addr string) (net.Conn, error) {
    93  		host, port, err := net.SplitHostPort(addr)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  
    98  		if allowHost != nil && allowHost(host) {
    99  			return dial(ctx, network, addr)
   100  		}
   101  
   102  		ips, err := net.LookupIP(host)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  
   107  		var firstErr error
   108  		for _, ip := range ips {
   109  			select {
   110  			case <-ctx.Done():
   111  				return nil, ctx.Err()
   112  			default:
   113  			}
   114  
   115  			if allowIP == nil || !allowIP(ip) {
   116  				continue
   117  			}
   118  
   119  			conn, err := dial(ctx, network, net.JoinHostPort(ip.String(), port))
   120  			if err == nil {
   121  				return conn, nil
   122  			}
   123  			if firstErr == nil {
   124  				firstErr = err
   125  			}
   126  		}
   127  		if firstErr == nil {
   128  			return nil, AddressForbidden
   129  		}
   130  		return nil, firstErr
   131  	}
   132  }
   133  
   134  // NewHTTPClient returns a variation the default implementation of Client.
   135  // It uses a Transport with the same settings as the default Transport
   136  // but with the following modifications:
   137  // - shorter timeout for dial and TLS handshake (defined as constant
   138  //   "connectTimeout")
   139  // - timeout for the end-to-end request (defined as constant
   140  //   "requestTimeout")
   141  func NewHTTPClient(enableInsecureConnections bool, allowHost func(host string) bool, allowIP func(ip net.IP) bool) *http.Client {
   142  	dialContext := (&net.Dialer{
   143  		Timeout:   connectTimeout,
   144  		KeepAlive: 30 * time.Second,
   145  	}).DialContext
   146  
   147  	if allowHost != nil || allowIP != nil {
   148  		dialContext = dialContextFilter(dialContext, allowHost, allowIP)
   149  	}
   150  
   151  	client := &http.Client{
   152  		Transport: &http.Transport{
   153  			Proxy:                 http.ProxyFromEnvironment,
   154  			DialContext:           dialContext,
   155  			MaxIdleConns:          100,
   156  			IdleConnTimeout:       90 * time.Second,
   157  			TLSHandshakeTimeout:   connectTimeout,
   158  			ExpectContinueTimeout: 1 * time.Second,
   159  			TLSClientConfig: &tls.Config{
   160  				InsecureSkipVerify: enableInsecureConnections,
   161  			},
   162  		},
   163  		Timeout: requestTimeout,
   164  	}
   165  
   166  	return client
   167  }