storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/handlers/proxy.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2018 MinIO, 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 handlers
    18  
    19  import (
    20  	"net"
    21  	"net/http"
    22  	"regexp"
    23  	"strings"
    24  )
    25  
    26  var (
    27  	// De-facto standard header keys.
    28  	xForwardedFor    = http.CanonicalHeaderKey("X-Forwarded-For")
    29  	xForwardedHost   = http.CanonicalHeaderKey("X-Forwarded-Host")
    30  	xForwardedPort   = http.CanonicalHeaderKey("X-Forwarded-Port")
    31  	xForwardedProto  = http.CanonicalHeaderKey("X-Forwarded-Proto")
    32  	xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
    33  	xRealIP          = http.CanonicalHeaderKey("X-Real-IP")
    34  )
    35  
    36  var (
    37  	// RFC7239 defines a new "Forwarded: " header designed to replace the
    38  	// existing use of X-Forwarded-* headers.
    39  	// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
    40  	forwarded = http.CanonicalHeaderKey("Forwarded")
    41  	// Allows for a sub-match of the first value after 'for=' to the next
    42  	// comma, semi-colon or space. The match is case-insensitive.
    43  	forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)(.*)`)
    44  	// Allows for a sub-match for the first instance of scheme (http|https)
    45  	// prefixed by 'proto='. The match is case-insensitive.
    46  	protoRegex = regexp.MustCompile(`(?i)^(;|,| )+(?:proto=)(https|http)`)
    47  )
    48  
    49  // GetSourceScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
    50  // Forwarded headers (in that order).
    51  func GetSourceScheme(r *http.Request) string {
    52  	var scheme string
    53  
    54  	// Retrieve the scheme from X-Forwarded-Proto.
    55  	if proto := r.Header.Get(xForwardedProto); proto != "" {
    56  		scheme = strings.ToLower(proto)
    57  	} else if proto = r.Header.Get(xForwardedScheme); proto != "" {
    58  		scheme = strings.ToLower(proto)
    59  	} else if proto := r.Header.Get(forwarded); proto != "" {
    60  		// match should contain at least two elements if the protocol was
    61  		// specified in the Forwarded header. The first element will always be
    62  		// the 'for=', which we ignore, subsequently we proceed to look for
    63  		// 'proto=' which should precede right after `for=` if not
    64  		// we simply ignore the values and return empty. This is in line
    65  		// with the approach we took for returning first ip from multiple
    66  		// params.
    67  		if match := forRegex.FindStringSubmatch(proto); len(match) > 1 {
    68  			if match = protoRegex.FindStringSubmatch(match[2]); len(match) > 1 {
    69  				scheme = strings.ToLower(match[2])
    70  			}
    71  		}
    72  	}
    73  
    74  	return scheme
    75  }
    76  
    77  // GetSourceIPFromHeaders retrieves the IP from the X-Forwarded-For, X-Real-IP
    78  // and RFC7239 Forwarded headers (in that order)
    79  func GetSourceIPFromHeaders(r *http.Request) string {
    80  	var addr string
    81  
    82  	if fwd := r.Header.Get(xForwardedFor); fwd != "" {
    83  		// Only grab the first (client) address. Note that '192.168.0.1,
    84  		// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
    85  		// the first may represent forwarding proxies earlier in the chain.
    86  		s := strings.Index(fwd, ", ")
    87  		if s == -1 {
    88  			s = len(fwd)
    89  		}
    90  		addr = fwd[:s]
    91  	} else if fwd := r.Header.Get(xRealIP); fwd != "" {
    92  		// X-Real-IP should only contain one IP address (the client making the
    93  		// request).
    94  		addr = fwd
    95  	} else if fwd := r.Header.Get(forwarded); fwd != "" {
    96  		// match should contain at least two elements if the protocol was
    97  		// specified in the Forwarded header. The first element will always be
    98  		// the 'for=' capture, which we ignore. In the case of multiple IP
    99  		// addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only
   100  		// extract the first, which should be the client IP.
   101  		if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
   102  			// IPv6 addresses in Forwarded headers are quoted-strings. We strip
   103  			// these quotes.
   104  			addr = strings.Trim(match[1], `"`)
   105  		}
   106  	}
   107  
   108  	return addr
   109  }
   110  
   111  // GetSourceIP retrieves the IP from the request headers
   112  // and falls back to r.RemoteAddr when necessary.
   113  func GetSourceIP(r *http.Request) string {
   114  	addr := GetSourceIPFromHeaders(r)
   115  	if addr != "" {
   116  		return addr
   117  	}
   118  
   119  	// Default to remote address if headers not set.
   120  	addr, _, _ = net.SplitHostPort(r.RemoteAddr)
   121  	return addr
   122  }