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 }