2019-11-29 06:59:40 -05:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
2017-05-31 10:34:05 -04:00
2018-09-26 12:42:51 -04:00
package httpservice
2017-05-31 10:34:05 -04:00
import (
2017-08-09 16:49:07 -04:00
"context"
2017-05-31 10:34:05 -04:00
"crypto/tls"
2017-08-09 16:49:07 -04:00
"errors"
2024-12-19 12:55:42 -05:00
"fmt"
2017-05-31 10:34:05 -04:00
"net"
"net/http"
2024-12-19 12:55:42 -05:00
"net/netip"
"net/url"
2025-07-22 08:49:32 -04:00
"strings"
2017-05-31 10:34:05 -04:00
"time"
2024-12-19 12:55:42 -05:00
"golang.org/x/net/http/httpproxy"
2017-05-31 10:34:05 -04:00
)
const (
2018-12-12 11:39:14 -05:00
ConnectTimeout = 3 * time . Second
RequestTimeout = 30 * time . Second
2017-05-31 10:34:05 -04:00
)
2017-08-09 16:49:07 -04:00
var reservedIPRanges [ ] * net . IPNet
2019-03-01 10:22:24 -05:00
// IsReservedIP checks whether the target IP belongs to reserved IP address ranges to avoid SSRF attacks to the internal
// network of the Mattermost server
2017-11-22 10:15:03 -05:00
func IsReservedIP ( ip net . IP ) bool {
2017-08-09 16:49:07 -04:00
for _ , ipRange := range reservedIPRanges {
if ipRange . Contains ( ip ) {
return true
}
}
return false
}
2019-03-01 10:22:24 -05:00
// IsOwnIP handles the special case that a request might be made to the public IP of the host which on Linux is routed
// directly via the loopback IP to any listening sockets, effectively bypassing host-based firewalls such as firewalld
func IsOwnIP ( ip net . IP ) ( bool , error ) {
interfaces , err := net . Interfaces ( )
if err != nil {
return false , err
}
for _ , interf := range interfaces {
addresses , err := interf . Addrs ( )
if err != nil {
return false , err
}
for _ , addr := range addresses {
var selfIP net . IP
switch v := addr . ( type ) {
case * net . IPNet :
selfIP = v . IP
case * net . IPAddr :
selfIP = v . IP
}
if ip . Equal ( selfIP ) {
return true , nil
}
}
}
return false , nil
}
2018-10-03 13:28:44 -04:00
var defaultUserAgent string
2017-08-09 16:49:07 -04:00
func init ( ) {
for _ , cidr := range [ ] string {
2024-07-25 10:08:56 -04:00
// Strings taken from https://github.com/doyensec/safeurl/blob/main/ip.go
"10.0.0.0/8" , /* Private network - RFC 1918 */
"172.16.0.0/12" , /* Private network - RFC 1918 */
"192.168.0.0/16" , /* Private network - RFC 1918 */
"127.0.0.0/8" , /* Loopback - RFC 1122, Section 3.2.1.3 */
"0.0.0.0/8" , /* Current network (only valid as source address) - RFC 1122, Section 3.2.1.3 */
"169.254.0.0/16" , /* Link-local - RFC 3927 */
"192.0.0.0/24" , /* IETF Protocol Assignments - RFC 5736 */
"192.0.2.0/24" , /* TEST-NET-1, documentation and examples - RFC 5737 */
"198.51.100.0/24" , /* TEST-NET-2, documentation and examples - RFC 5737 */
"203.0.113.0/24" , /* TEST-NET-3, documentation and examples - RFC 5737 */
"192.88.99.0/24" , /* IPv6 to IPv4 relay (includes 2002::/16) - RFC 3068 */
"198.18.0.0/15" , /* Network benchmark tests - RFC 2544 */
"224.0.0.0/4" , /* IP multicast (former Class D network) - RFC 3171 */
"240.0.0.0/4" , /* Reserved (former Class E network) - RFC 1112, Section 4 */
"255.255.255.255/32" , /* Broadcast - RFC 919, Section 7 */
"100.64.0.0/10" , /* Shared Address Space - RFC 6598 */
// ipv6 sourced from https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
"::/128" , /* Unspecified Address - RFC 4291 */
"::1/128" , /* Loopback - RFC 4291 */
"100::/64" , /* Discard prefix - RFC 6666 */
"2001::/23" , /* IETF Protocol Assignments - RFC 2928 */
"2001:2::/48" , /* Benchmarking - RFC5180 */
"2001:db8::/32" , /* Addresses used in documentation and example source code - RFC 3849 */
"2001::/32" , /* Teredo tunneling - RFC4380 - RFC8190 */
"fc00::/7" , /* Unique local address - RFC 4193 - RFC 8190 */
"fe80::/10" , /* Link-local address - RFC 4291 */
"ff00::/8" , /* Multicast - RFC 3513 */
"2002::/16" , /* 6to4 - RFC 3056 */
"64:ff9b::/96" , /* IPv4/IPv6 translation - RFC 6052 */
"2001:10::/28" , /* Deprecated (previously ORCHID) - RFC 4843 */
"2001:20::/28" , /* ORCHIDv2 - RFC7343 */
2017-08-09 16:49:07 -04:00
} {
_ , parsed , err := net . ParseCIDR ( cidr )
if err != nil {
panic ( err )
}
reservedIPRanges = append ( reservedIPRanges , parsed )
}
2021-04-12 09:30:48 -04:00
defaultUserAgent = "Mattermost-Bot/1.1"
2017-08-09 16:49:07 -04:00
}
type DialContextFunction func ( ctx context . Context , network , addr string ) ( net . Conn , error )
2023-08-22 03:15:51 -04:00
var ErrAddressForbidden = errors . New ( "address forbidden, you may need to set AllowedUntrustedInternalConnections to allow an integration access to your internal network" )
2017-08-09 16:49:07 -04:00
2025-07-22 08:49:32 -04:00
// dialContextFilter wraps a dial function to filter connections based on host and IP validation.
// It first checks if the host is allowed, then resolves the hostname to IPs and validates each one.
// Returns detailed error messages when connections are rejected for security reasons.
func dialContextFilter ( dial DialContextFunction , allowHost func ( host string ) bool , allowIP func ( ip net . IP ) error ) DialContextFunction {
2017-08-09 16:49:07 -04:00
return func ( ctx context . Context , network , addr string ) ( net . Conn , error ) {
host , port , err := net . SplitHostPort ( addr )
if err != nil {
return nil , err
}
if allowHost != nil && allowHost ( host ) {
return dial ( ctx , network , addr )
}
ips , err := net . LookupIP ( host )
if err != nil {
return nil , err
}
2025-07-22 08:49:32 -04:00
var firstDialErr error
var forbiddenReasons [ ] string
2017-08-09 16:49:07 -04:00
for _ , ip := range ips {
select {
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
default :
}
2025-07-22 08:49:32 -04:00
if allowIP == nil {
forbiddenReasons = append ( forbiddenReasons , fmt . Sprintf ( "IP %s is not allowed" , ip ) )
continue
}
if err := allowIP ( ip ) ; err != nil {
forbiddenReasons = append ( forbiddenReasons , err . Error ( ) )
2017-08-09 16:49:07 -04:00
continue
}
conn , err := dial ( ctx , network , net . JoinHostPort ( ip . String ( ) , port ) )
if err == nil {
return conn , nil
}
2025-07-22 08:49:32 -04:00
if firstDialErr == nil {
firstDialErr = err
2017-08-09 16:49:07 -04:00
}
}
2025-07-22 08:49:32 -04:00
if firstDialErr == nil {
// If we didn't find an allowed IP address, return an error explaining why
if len ( forbiddenReasons ) > 0 {
return nil , fmt . Errorf ( "%s: %s" , ErrAddressForbidden . Error ( ) , strings . Join ( forbiddenReasons , "; " ) )
}
2023-08-22 03:15:51 -04:00
return nil , ErrAddressForbidden
2017-08-09 16:49:07 -04:00
}
2025-07-22 08:49:32 -04:00
return nil , firstDialErr
2017-08-09 16:49:07 -04:00
}
}
2024-12-19 12:55:42 -05:00
func getProxyFn ( ) func ( r * http . Request ) ( * url . URL , error ) {
proxyFromEnvFn := httpproxy . FromEnvironment ( ) . ProxyFunc ( )
return func ( r * http . Request ) ( * url . URL , error ) {
// TODO: Consider removing this code once MM-61938 is fixed upstream.
if r . URL != nil {
if addr , err := netip . ParseAddr ( r . URL . Hostname ( ) ) ; err == nil && addr . Is6 ( ) && addr . Zone ( ) != "" {
2025-07-22 08:49:32 -04:00
return nil , fmt . Errorf ( "invalid IPv6 address in URL: %q" , addr )
2024-12-19 12:55:42 -05:00
}
}
return proxyFromEnvFn ( r . URL )
}
}
2025-07-22 08:49:32 -04:00
// NewTransport creates a new MattermostTransport with detailed error messages for IP check failures
func NewTransport ( enableInsecureConnections bool , allowHost func ( host string ) bool , allowIP func ( ip net . IP ) error ) * MattermostTransport {
2017-08-09 16:49:07 -04:00
dialContext := ( & net . Dialer {
2018-12-12 11:39:14 -05:00
Timeout : ConnectTimeout ,
2017-08-09 16:49:07 -04:00
KeepAlive : 30 * time . Second ,
} ) . DialContext
if allowHost != nil || allowIP != nil {
dialContext = dialContextFilter ( dialContext , allowHost , allowIP )
}
2017-05-31 10:34:05 -04:00
2018-12-12 11:39:14 -05:00
return & MattermostTransport {
& http . Transport {
2024-12-19 12:55:42 -05:00
Proxy : getProxyFn ( ) ,
2017-08-09 16:49:07 -04:00
DialContext : dialContext ,
2017-05-31 10:34:05 -04:00
MaxIdleConns : 100 ,
IdleConnTimeout : 90 * time . Second ,
2018-12-12 11:39:14 -05:00
TLSHandshakeTimeout : ConnectTimeout ,
2017-05-31 10:34:05 -04:00
ExpectContinueTimeout : 1 * time . Second ,
TLSClientConfig : & tls . Config {
InsecureSkipVerify : enableInsecureConnections ,
} ,
} ,
}
2018-12-12 11:39:14 -05:00
}