github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/handlers/proxy.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  // Originally from https://github.com/gorilla/handlers with following license
    19  // https://raw.githubusercontent.com/gorilla/handlers/master/LICENSE, forked
    20  // and heavily modified for MinIO's internal needs.
    21  
    22  package handlers
    23  
    24  import (
    25  	"net"
    26  	"net/http"
    27  	"regexp"
    28  	"strings"
    29  )
    30  
    31  var (
    32  	// De-facto standard header keys.
    33  	xForwardedFor    = http.CanonicalHeaderKey("X-Forwarded-For")
    34  	xForwardedHost   = http.CanonicalHeaderKey("X-Forwarded-Host")
    35  	xForwardedPort   = http.CanonicalHeaderKey("X-Forwarded-Port")
    36  	xForwardedProto  = http.CanonicalHeaderKey("X-Forwarded-Proto")
    37  	xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
    38  	xRealIP          = http.CanonicalHeaderKey("X-Real-IP")
    39  )
    40  
    41  var (
    42  	// RFC7239 defines a new "Forwarded: " header designed to replace the
    43  	// existing use of X-Forwarded-* headers.
    44  	// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
    45  	forwarded = http.CanonicalHeaderKey("Forwarded")
    46  	// Allows for a sub-match of the first value after 'for=' to the next
    47  	// comma, semi-colon or space. The match is case-insensitive.
    48  	forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)(.*)`)
    49  	// Allows for a sub-match for the first instance of scheme (http|https)
    50  	// prefixed by 'proto='. The match is case-insensitive.
    51  	protoRegex = regexp.MustCompile(`(?i)^(;|,| )+(?:proto=)(https|http)`)
    52  )
    53  
    54  // GetSourceScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
    55  // Forwarded headers (in that order).
    56  func GetSourceScheme(r *http.Request) string {
    57  	var scheme string
    58  
    59  	// Retrieve the scheme from X-Forwarded-Proto.
    60  	if proto := r.Header.Get(xForwardedProto); proto != "" {
    61  		scheme = strings.ToLower(proto)
    62  	} else if proto = r.Header.Get(xForwardedScheme); proto != "" {
    63  		scheme = strings.ToLower(proto)
    64  	} else if proto := r.Header.Get(forwarded); proto != "" {
    65  		// match should contain at least two elements if the protocol was
    66  		// specified in the Forwarded header. The first element will always be
    67  		// the 'for=', which we ignore, subsequently we proceed to look for
    68  		// 'proto=' which should precede right after `for=` if not
    69  		// we simply ignore the values and return empty. This is in line
    70  		// with the approach we took for returning first ip from multiple
    71  		// params.
    72  		if match := forRegex.FindStringSubmatch(proto); len(match) > 1 {
    73  			if match = protoRegex.FindStringSubmatch(match[2]); len(match) > 1 {
    74  				scheme = strings.ToLower(match[2])
    75  			}
    76  		}
    77  	}
    78  
    79  	return scheme
    80  }
    81  
    82  // GetSourceIPFromHeaders retrieves the IP from the X-Forwarded-For, X-Real-IP
    83  // and RFC7239 Forwarded headers (in that order)
    84  func GetSourceIPFromHeaders(r *http.Request) string {
    85  	var addr string
    86  
    87  	if fwd := r.Header.Get(xForwardedFor); fwd != "" {
    88  		// Only grab the first (client) address. Note that '192.168.0.1,
    89  		// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
    90  		// the first may represent forwarding proxies earlier in the chain.
    91  		s := strings.Index(fwd, ", ")
    92  		if s == -1 {
    93  			s = len(fwd)
    94  		}
    95  		addr = fwd[:s]
    96  	} else if fwd := r.Header.Get(xRealIP); fwd != "" {
    97  		// X-Real-IP should only contain one IP address (the client making the
    98  		// request).
    99  		addr = fwd
   100  	} else if fwd := r.Header.Get(forwarded); fwd != "" {
   101  		// match should contain at least two elements if the protocol was
   102  		// specified in the Forwarded header. The first element will always be
   103  		// the 'for=' capture, which we ignore. In the case of multiple IP
   104  		// addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only
   105  		// extract the first, which should be the client IP.
   106  		if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
   107  			// IPv6 addresses in Forwarded headers are quoted-strings. We strip
   108  			// these quotes.
   109  			addr = strings.Trim(match[1], `"`)
   110  		}
   111  	}
   112  
   113  	return addr
   114  }
   115  
   116  // GetSourceIPRaw retrieves the IP from the request headers
   117  // and falls back to r.RemoteAddr when necessary.
   118  // however returns without bracketing.
   119  func GetSourceIPRaw(r *http.Request) string {
   120  	addr := GetSourceIPFromHeaders(r)
   121  	if addr == "" {
   122  		addr = r.RemoteAddr
   123  	}
   124  
   125  	// Default to remote address if headers not set.
   126  	raddr, _, _ := net.SplitHostPort(addr)
   127  	if raddr == "" {
   128  		return addr
   129  	}
   130  	return raddr
   131  }
   132  
   133  // GetSourceIP retrieves the IP from the request headers
   134  // and falls back to r.RemoteAddr when necessary.
   135  func GetSourceIP(r *http.Request) string {
   136  	addr := GetSourceIPRaw(r)
   137  	if strings.ContainsRune(addr, ':') {
   138  		return "[" + addr + "]"
   139  	}
   140  	return addr
   141  }