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 }