You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							120 lines
						
					
					
						
							4.5 KiB
						
					
					
				
			
		
		
	
	
							120 lines
						
					
					
						
							4.5 KiB
						
					
					
				| package handlers
 | |
| 
 | |
| import (
 | |
| 	"net/http"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// De-facto standard header keys.
 | |
| 	xForwardedFor    = http.CanonicalHeaderKey("X-Forwarded-For")
 | |
| 	xForwardedHost   = http.CanonicalHeaderKey("X-Forwarded-Host")
 | |
| 	xForwardedProto  = http.CanonicalHeaderKey("X-Forwarded-Proto")
 | |
| 	xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
 | |
| 	xRealIP          = http.CanonicalHeaderKey("X-Real-IP")
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// RFC7239 defines a new "Forwarded: " header designed to replace the
 | |
| 	// existing use of X-Forwarded-* headers.
 | |
| 	// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
 | |
| 	forwarded = http.CanonicalHeaderKey("Forwarded")
 | |
| 	// Allows for a sub-match of the first value after 'for=' to the next
 | |
| 	// comma, semi-colon or space. The match is case-insensitive.
 | |
| 	forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`)
 | |
| 	// Allows for a sub-match for the first instance of scheme (http|https)
 | |
| 	// prefixed by 'proto='. The match is case-insensitive.
 | |
| 	protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`)
 | |
| )
 | |
| 
 | |
| // ProxyHeaders inspects common reverse proxy headers and sets the corresponding
 | |
| // fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
 | |
| // for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme
 | |
| // for the scheme (http|https), X-Forwarded-Host for the host and the RFC7239
 | |
| // Forwarded header, which may include both client IPs and schemes.
 | |
| //
 | |
| // NOTE: This middleware should only be used when behind a reverse
 | |
| // proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
 | |
| // configured not to) strip these headers from client requests, or where these
 | |
| // headers are accepted "as is" from a remote client (e.g. when Go is not behind
 | |
| // a proxy), can manifest as a vulnerability if your application uses these
 | |
| // headers for validating the 'trustworthiness' of a request.
 | |
| func ProxyHeaders(h http.Handler) http.Handler {
 | |
| 	fn := func(w http.ResponseWriter, r *http.Request) {
 | |
| 		// Set the remote IP with the value passed from the proxy.
 | |
| 		if fwd := getIP(r); fwd != "" {
 | |
| 			r.RemoteAddr = fwd
 | |
| 		}
 | |
| 
 | |
| 		// Set the scheme (proto) with the value passed from the proxy.
 | |
| 		if scheme := getScheme(r); scheme != "" {
 | |
| 			r.URL.Scheme = scheme
 | |
| 		}
 | |
| 		// Set the host with the value passed by the proxy
 | |
| 		if r.Header.Get(xForwardedHost) != "" {
 | |
| 			r.Host = r.Header.Get(xForwardedHost)
 | |
| 		}
 | |
| 		// Call the next handler in the chain.
 | |
| 		h.ServeHTTP(w, r)
 | |
| 	}
 | |
| 
 | |
| 	return http.HandlerFunc(fn)
 | |
| }
 | |
| 
 | |
| // getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
 | |
| // Forwarded headers (in that order).
 | |
| func getIP(r *http.Request) string {
 | |
| 	var addr string
 | |
| 
 | |
| 	if fwd := r.Header.Get(xForwardedFor); fwd != "" {
 | |
| 		// Only grab the first (client) address. Note that '192.168.0.1,
 | |
| 		// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
 | |
| 		// the first may represent forwarding proxies earlier in the chain.
 | |
| 		s := strings.Index(fwd, ", ")
 | |
| 		if s == -1 {
 | |
| 			s = len(fwd)
 | |
| 		}
 | |
| 		addr = fwd[:s]
 | |
| 	} else if fwd := r.Header.Get(xRealIP); fwd != "" {
 | |
| 		// X-Real-IP should only contain one IP address (the client making the
 | |
| 		// request).
 | |
| 		addr = fwd
 | |
| 	} else if fwd := r.Header.Get(forwarded); fwd != "" {
 | |
| 		// match should contain at least two elements if the protocol was
 | |
| 		// specified in the Forwarded header. The first element will always be
 | |
| 		// the 'for=' capture, which we ignore. In the case of multiple IP
 | |
| 		// addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only
 | |
| 		// extract the first, which should be the client IP.
 | |
| 		if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
 | |
| 			// IPv6 addresses in Forwarded headers are quoted-strings. We strip
 | |
| 			// these quotes.
 | |
| 			addr = strings.Trim(match[1], `"`)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return addr
 | |
| }
 | |
| 
 | |
| // getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
 | |
| // Forwarded headers (in that order).
 | |
| func getScheme(r *http.Request) string {
 | |
| 	var scheme string
 | |
| 
 | |
| 	// Retrieve the scheme from X-Forwarded-Proto.
 | |
| 	if proto := r.Header.Get(xForwardedProto); proto != "" {
 | |
| 		scheme = strings.ToLower(proto)
 | |
| 	} else if proto = r.Header.Get(xForwardedScheme); proto != "" {
 | |
| 		scheme = strings.ToLower(proto)
 | |
| 	} else if proto = r.Header.Get(forwarded); proto != "" {
 | |
| 		// match should contain at least two elements if the protocol was
 | |
| 		// specified in the Forwarded header. The first element will always be
 | |
| 		// the 'proto=' capture, which we ignore. In the case of multiple proto
 | |
| 		// parameters (invalid) we only extract the first.
 | |
| 		if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 {
 | |
| 			scheme = strings.ToLower(match[1])
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return scheme
 | |
| }
 | |
| 
 |