github.com/weaveworks/common@v0.0.0-20230728070032-dd9e68f319d5/middleware/source_ips.go (about) 1 package middleware 2 3 import ( 4 "fmt" 5 "net" 6 "net/http" 7 "regexp" 8 "strings" 9 ) 10 11 // Parts copied and changed from gorilla mux proxy_headers.go 12 13 var ( 14 // De-facto standard header keys. 15 xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") 16 xRealIP = http.CanonicalHeaderKey("X-Real-IP") 17 ) 18 19 var ( 20 // RFC7239 defines a new "Forwarded: " header designed to replace the 21 // existing use of X-Forwarded-* headers. 22 // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43 23 forwarded = http.CanonicalHeaderKey("Forwarded") 24 // Allows for a sub-match of the first value after 'for=' to the next 25 // comma, semi-colon or space. The match is case-insensitive. 26 forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`) 27 ) 28 29 // SourceIPExtractor extracts the source IPs from a HTTP request 30 type SourceIPExtractor struct { 31 // The header to search for 32 header string 33 // A regex that extracts the IP address from the header. 34 // It should contain at least one capturing group the first of which will be returned. 35 regex *regexp.Regexp 36 } 37 38 // NewSourceIPs creates a new SourceIPs 39 func NewSourceIPs(header, regex string) (*SourceIPExtractor, error) { 40 if (header == "" && regex != "") || (header != "" && regex == "") { 41 return nil, fmt.Errorf("either both a header field and a regex have to be given or neither") 42 } 43 re, err := regexp.Compile(regex) 44 if err != nil { 45 return nil, fmt.Errorf("invalid regex given") 46 } 47 48 return &SourceIPExtractor{ 49 header: header, 50 regex: re, 51 }, nil 52 } 53 54 // extractHost returns the Host IP address without any port information 55 func extractHost(address string) string { 56 hostIP := net.ParseIP(address) 57 if hostIP != nil { 58 return hostIP.String() 59 } 60 var err error 61 hostStr, _, err := net.SplitHostPort(address) 62 if err != nil { 63 // Invalid IP address, just return it so it shows up in the logs 64 return address 65 } 66 return hostStr 67 } 68 69 // Get returns any source addresses we can find in the request, comma-separated 70 func (sips SourceIPExtractor) Get(req *http.Request) string { 71 fwd := extractHost(sips.getIP(req)) 72 if fwd == "" { 73 if req.RemoteAddr == "" { 74 return "" 75 } 76 return extractHost(req.RemoteAddr) 77 } 78 // If RemoteAddr is empty just return the header 79 if req.RemoteAddr == "" { 80 return fwd 81 } 82 remoteIP := extractHost(req.RemoteAddr) 83 if fwd == remoteIP { 84 return remoteIP 85 } 86 // If both a header and RemoteAddr are present return them both, stripping off any port info from the RemoteAddr 87 return fmt.Sprintf("%v, %v", fwd, remoteIP) 88 } 89 90 // getIP retrieves the IP from the RFC7239 Forwarded headers, 91 // X-Real-IP and X-Forwarded-For (in that order) or from the 92 // custom regex. 93 func (sips SourceIPExtractor) getIP(r *http.Request) string { 94 var addr string 95 96 // Use the custom regex only if it was setup 97 if sips.header != "" { 98 hdr := r.Header.Get(sips.header) 99 if hdr == "" { 100 return "" 101 } 102 allMatches := sips.regex.FindAllStringSubmatch(hdr, 1) 103 if len(allMatches) == 0 { 104 return "" 105 } 106 firstMatch := allMatches[0] 107 // Check there is at least 1 submatch 108 if len(firstMatch) < 2 { 109 return "" 110 } 111 return firstMatch[1] 112 } 113 114 if fwd := r.Header.Get(forwarded); fwd != "" { 115 // match should contain at least two elements if the protocol was 116 // specified in the Forwarded header. The first element will always be 117 // the 'for=' capture, which we ignore. In the case of multiple IP 118 // addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only 119 // extract the first, which should be the client IP. 120 if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 { 121 // IPv6 addresses in Forwarded headers are quoted-strings. We strip 122 // these quotes. 123 addr = strings.Trim(match[1], `"`) 124 } 125 } else if fwd := r.Header.Get(xRealIP); fwd != "" { 126 // X-Real-IP should only contain one IP address (the client making the 127 // request). 128 addr = fwd 129 } else if fwd := strings.ReplaceAll(r.Header.Get(xForwardedFor), " ", ""); fwd != "" { 130 // Only grab the first (client) address. Note that '192.168.0.1, 131 // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after 132 // the first may represent forwarding proxies earlier in the chain. 133 s := strings.Index(fwd, ",") 134 if s == -1 { 135 s = len(fwd) 136 } 137 addr = fwd[:s] 138 } 139 140 return addr 141 }