Documentation
¶
Overview ¶
Package realclientip provides strategies for obtaining the "real" client IP from HTTP requests.
Example (Middleware) ¶
package main import ( "context" "fmt" "io/ioutil" "log" "net/http" "net/http/httptest" "github.com/realclientip/realclientip-go" ) func main() { // Choose the right strategy for our network configuration strat, err := realclientip.NewRightmostNonPrivateStrategy("X-Forwarded-For") if err != nil { log.Fatal("realclientip.NewRightmostNonPrivateStrategy returned error (bad input)") } // Place our middleware before the handler handlerWithMiddleware := clientIPMiddleware(strat, http.HandlerFunc(handler)) httpServer := httptest.NewServer(handlerWithMiddleware) defer httpServer.Close() req, _ := http.NewRequest("GET", httpServer.URL, nil) req.Header.Add("X-Forwarded-For", "1.1.1.1, 2.2.2.2, 3.3.3.3, 192.168.1.1") client := &http.Client{} resp, err := client.Do(req) if err != nil { log.Fatal(err) } b, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Printf("%s", b) } type clientIPCtxKey struct{} // Adds the "real" client IP to the request context under the clientIPCtxKey{} key. // If the client IP couldn't be obtained, the value will be an empty string. // We could use the RightmostNonPrivateStrategy concrete type, but instead we'll pass // around the Strategy interface, in case we decide to change our strategy in the future. func clientIPMiddleware(strat realclientip.Strategy, next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { clientIP := strat.ClientIP(r.Header, r.RemoteAddr) if clientIP == "" { // Write error log. Consider aborting the request depending on use. log.Fatal("Failed to find client IP") } r = r.WithContext(context.WithValue(r.Context(), clientIPCtxKey{}, clientIP)) next.ServeHTTP(w, r) } return http.HandlerFunc(fn) } func handler(w http.ResponseWriter, r *http.Request) { clientIP := r.Context().Value(clientIPCtxKey{}) fmt.Fprintln(w, "your IP:", clientIP) }
Output: your IP: 3.3.3.3
Example (Playground) ¶
// We'll make a fake request req, _ := http.NewRequest("GET", "https://example.com", nil) req.Header.Add("X-Forwarded-For", "1.1.1.1, 2001:db8:cafe::99%eth0, 3.3.3.3, 192.168.1.1") req.Header.Add("Forwarded", `For=fe80::abcd;By=fe80::1234, Proto=https;For=::ffff:188.0.2.128, For="[2001:db8:cafe::17]:4848", For=fc00::1`) req.Header.Add("X-Real-IP", "4.4.4.4") req.RemoteAddr = "192.168.1.2:8888" var strat realclientip.Strategy strat = realclientip.RemoteAddrStrategy{} fmt.Printf("\n%+v\n", strat) fmt.Println(strat.ClientIP(req.Header, req.RemoteAddr)) // 192.168.1.2 strat, _ = realclientip.NewSingleIPHeaderStrategy("X-Real-IP") fmt.Printf("\n%+v\n", strat) fmt.Println(strat.ClientIP(req.Header, req.RemoteAddr)) // 4.4.4.4 strat, _ = realclientip.NewLeftmostNonPrivateStrategy("Forwarded") fmt.Printf("\n%+v\n", strat) fmt.Println(strat.ClientIP(req.Header, req.RemoteAddr)) // 188.0.2.128 strat, _ = realclientip.NewRightmostNonPrivateStrategy("X-Forwarded-For") fmt.Printf("\n%+v\n", strat) fmt.Println(strat.ClientIP(req.Header, req.RemoteAddr)) // 3.3.3.3 strat, _ = realclientip.NewRightmostTrustedCountStrategy("Forwarded", 2) fmt.Printf("\n%+v\n", strat) fmt.Println(strat.ClientIP(req.Header, req.RemoteAddr)) // 2001:db8:cafe::17 trustedRanges, _ := realclientip.AddressesAndRangesToIPNets([]string{"192.168.0.0/16", "3.3.3.3"}...) strat, _ = realclientip.NewRightmostTrustedRangeStrategy("X-Forwarded-For", trustedRanges) fmt.Printf("\n%+v\n", strat) fmt.Println(strat.ClientIP(req.Header, req.RemoteAddr)) // 2001:db8:cafe::99%eth0 ipAddr, _ := realclientip.ParseIPAddr(strat.ClientIP(req.Header, req.RemoteAddr)) fmt.Println(ipAddr.IP) // 2001:db8:cafe::99 strat = realclientip.NewChainStrategy( realclientip.Must(realclientip.NewSingleIPHeaderStrategy("Cf-Connecting-IP")), realclientip.RemoteAddrStrategy{}, ) fmt.Printf("\n%+v\n", strat) fmt.Println(strat.ClientIP(req.Header, req.RemoteAddr)) // 192.168.1.2
Output: {} 192.168.1.2 {headerName:X-Real-Ip} 4.4.4.4 {headerName:Forwarded} 188.0.2.128 {headerName:X-Forwarded-For} 3.3.3.3 {headerName:Forwarded trustedCount:2} 2001:db8:cafe::17 {headerName:X-Forwarded-For trustedRanges:[192.168.0.0/16 3.3.3.3/32] 2001:db8:cafe::99%eth0 2001:db8:cafe::99 {strategies:[realclientip.SingleIPHeaderStrategy{headerName:Cf-Connecting-Ip} realclientip.RemoteAddrStrategy{}]} 192.168.1.2
Index ¶
- func AddressesAndRangesToIPNets(ranges ...string) ([]net.IPNet, error)
- func MustParseIPAddr(ipStr string) net.IPAddr
- func ParseIPAddr(ipStr string) (net.IPAddr, error)
- func SplitHostZone(s string) (host, zone string)
- type ChainStrategy
- type LeftmostNonPrivateStrategy
- type RemoteAddrStrategy
- type RightmostNonPrivateStrategy
- type RightmostTrustedCountStrategy
- type RightmostTrustedRangeStrategy
- type SingleIPHeaderStrategy
- type Strategy
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AddressesAndRangesToIPNets ¶
AddressesAndRangesToIPNets converts a slice of strings with IPv4 and IPv6 addresses and CIDR ranges (prefixes) to net.IPNet instances. If net.ParseCIDR or net.ParseIP fail, an error will be returned. Zones in addresses or ranges are not allowed and will result in an error. This is because: a) net.ParseCIDR will fail to parse a range with a zone, and b) netip.ParsePrefix will succeed but silently throw away the zone; then netip.Prefix.Contains will return false for any IP with a zone, causing confusion and bugs.
func MustParseIPAddr ¶
MustParseIPAddr panics if ParseIPAddr fails.
func ParseIPAddr ¶
ParseIPAddr parses the given string into a net.IPAddr, which is a useful type for dealing with IPs have zones. The Go stdlib net package is lacking such a function. This will also discard any port number from the input.
func SplitHostZone ¶
SplitHostZone splits a "host%zone" string into its components. If there is no zone, host is the original input and zone is empty.
Types ¶
type ChainStrategy ¶ added in v1.0.0
type ChainStrategy struct {
// contains filtered or unexported fields
}
ChainStrategy attempts to use the given strategies in order. If the first one returns an empty string, the second one is tried, and so on, until a good IP is found or the strategies are exhausted. A common use for this is if a server is both directly connected to the internet and expecting a header to check. It might be called like:
NewChainStrategy(Must(LeftmostNonPrivateStrategy("X-Forwarded-For")), RemoteAddrStrategy)
func NewChainStrategy ¶ added in v1.0.0
func NewChainStrategy(strategies ...Strategy) ChainStrategy
NewChainStrategy creates a ChainStrategy that attempts to use the given strategies to derive the client IP, stopping when the first one succeeds.
func (ChainStrategy) ClientIP ¶ added in v1.0.0
func (strat ChainStrategy) ClientIP(headers http.Header, remoteAddr string) string
ClientIP derives the client IP using this strategy. headers is expected to be like http.Request.Header. remoteAddr is expected to be like http.Request.RemoteAddr. The returned IP may contain a zone identifier. If all chained strategies fail to derive a valid IP, an empty string is returned.
func (ChainStrategy) String ¶ added in v1.0.0
func (strat ChainStrategy) String() string
type LeftmostNonPrivateStrategy ¶
type LeftmostNonPrivateStrategy struct {
// contains filtered or unexported fields
}
LeftmostNonPrivateStrategy derives the client IP from the leftmost valid and non-private IP address in the X-Fowarded-For for Forwarded header. This strategy should be used when a valid, non-private IP closest to the client is desired. Note that this MUST NOT BE USED FOR SECURITY PURPOSES. This IP can be TRIVIALLY SPOOFED.
func NewLeftmostNonPrivateStrategy ¶ added in v1.0.0
func NewLeftmostNonPrivateStrategy(headerName string) (LeftmostNonPrivateStrategy, error)
NewLeftmostNonPrivateStrategy creates a LeftmostNonPrivateStrategy. headerName must be "X-Forwarded-For" or "Forwarded".
func (LeftmostNonPrivateStrategy) ClientIP ¶ added in v1.0.0
func (strat LeftmostNonPrivateStrategy) ClientIP(headers http.Header, _ string) string
ClientIP derives the client IP using this strategy. headers is expected to be like http.Request.Header. The returned IP may contain a zone identifier. If no valid IP can be derived, empty string will be returned.
type RemoteAddrStrategy ¶
type RemoteAddrStrategy struct{}
RemoteAddrStrategy returns the client socket IP, stripped of port. This strategy should be used if the server accept direct connections, rather than through a reverse proxy.
func (RemoteAddrStrategy) ClientIP ¶ added in v1.0.0
func (strat RemoteAddrStrategy) ClientIP(_ http.Header, remoteAddr string) string
ClientIP derives the client IP using this strategy. remoteAddr is expected to be like http.Request.RemoteAddr. The returned IP may contain a zone identifier. If no valid IP can be derived, empty string will be returned. This should only happen if remoteAddr has been modified to something illegal, or if the server is accepting connections on a Unix domain socket (in which case RemoteAddr is "@").
type RightmostNonPrivateStrategy ¶
type RightmostNonPrivateStrategy struct {
// contains filtered or unexported fields
}
RightmostNonPrivateStrategy derives the client IP from the rightmost valid, non-private/non-internal IP address in the X-Fowarded-For for Forwarded header. This strategy should be used when all reverse proxies between the internet and the server have private-space IP addresses.
func NewRightmostNonPrivateStrategy ¶ added in v1.0.0
func NewRightmostNonPrivateStrategy(headerName string) (RightmostNonPrivateStrategy, error)
NewRightmostNonPrivateStrategy creates a RightmostNonPrivateStrategy. headerName must be "X-Forwarded-For" or "Forwarded".
func (RightmostNonPrivateStrategy) ClientIP ¶ added in v1.0.0
func (strat RightmostNonPrivateStrategy) ClientIP(headers http.Header, _ string) string
ClientIP derives the client IP using this strategy. headers is expected to be like http.Request.Header. The returned IP may contain a zone identifier. If no valid IP can be derived, empty string will be returned.
type RightmostTrustedCountStrategy ¶
type RightmostTrustedCountStrategy struct {
// contains filtered or unexported fields
}
RightmostTrustedCountStrategy derives the client IP from the valid IP address added by the first trusted reverse proxy to the X-Forwarded-For or Forwarded header. This Strategy should be used when there is a fixed number of trusted reverse proxies that are appending IP addresses to the header.
func NewRightmostTrustedCountStrategy ¶ added in v1.0.0
func NewRightmostTrustedCountStrategy(headerName string, trustedCount int) (RightmostTrustedCountStrategy, error)
NewRightmostTrustedCountStrategy creates a RightmostTrustedCountStrategy. headerName must be "X-Forwarded-For" or "Forwarded". trustedCount is the number of trusted reverse proxies. The IP returned will be the (trustedCount-1)th from the right. For example, if there's only one trusted proxy, this strategy will return the last (rightmost) IP address.
func (RightmostTrustedCountStrategy) ClientIP ¶ added in v1.0.0
func (strat RightmostTrustedCountStrategy) ClientIP(headers http.Header, _ string) string
ClientIP derives the client IP using this strategy. headers is expected to be like http.Request.Header. The returned IP may contain a zone identifier. If no valid IP can be derived, empty string will be returned.
type RightmostTrustedRangeStrategy ¶
type RightmostTrustedRangeStrategy struct {
// contains filtered or unexported fields
}
RightmostTrustedRangeStrategy derives the client IP from the rightmost valid IP address in the X-Forwarded-For or Forwarded header which is not in a set of trusted IP ranges. This strategy should be used when the IP ranges of the reverse proxies between the internet and the server are known. If a third-party WAF, CDN, etc., is used, you SHOULD use a method of verifying its access to your origin that is stronger than checking its IP address (e.g., using authenticated pulls). Failure to do so can result in scenarios like: You use AWS CloudFront in front of a server you host elsewhere. An attacker creates a CF distribution that points at your origin server. The attacker uses Lambda@Edge to spoof the Host and X-Forwarded-For headers. Now your "trusted" reverse proxy is no longer trustworthy.
func NewRightmostTrustedRangeStrategy ¶ added in v1.0.0
func NewRightmostTrustedRangeStrategy(headerName string, trustedRanges []net.IPNet) (RightmostTrustedRangeStrategy, error)
NewRightmostTrustedRangeStrategy creates a RightmostTrustedRangeStrategy. headerName must be "X-Forwarded-For" or "Forwarded". trustedRanges must contain all trusted reverse proxies on the path to this server. trustedRanges can be private/internal or external (for example, if a third-party reverse proxy is used).
func (RightmostTrustedRangeStrategy) ClientIP ¶ added in v1.0.0
func (strat RightmostTrustedRangeStrategy) ClientIP(headers http.Header, _ string) string
ClientIP derives the client IP using this strategy. headers is expected to be like http.Request.Header. The returned IP may contain a zone identifier. If no valid IP can be derived, empty string will be returned.
func (RightmostTrustedRangeStrategy) String ¶ added in v1.0.0
func (strat RightmostTrustedRangeStrategy) String() string
type SingleIPHeaderStrategy ¶
type SingleIPHeaderStrategy struct {
// contains filtered or unexported fields
}
SingleIPHeaderStrategy derives an IP address from a single-IP header. A non-exhaustive list of such single-IP headers is: X-Real-IP, CF-Connecting-IP, True-Client-IP, Fastly-Client-IP, X-Azure-ClientIP, X-Azure-SocketIP. This strategy should be used when the given header is added by a trusted reverse proxy. You must ensure that this header is not spoofable (as is possible with Akamai's use of True-Client-IP, Fastly's default use of Fastly-Client-IP, and Azure's X-Azure-ClientIP). See the single-IP wiki page for more info: https://github.com/realclientip/realclientip-go/wiki/Single-IP-Headers
func NewSingleIPHeaderStrategy ¶ added in v1.0.0
func NewSingleIPHeaderStrategy(headerName string) (SingleIPHeaderStrategy, error)
NewSingleIPHeaderStrategy creates a SingleIPHeaderStrategy that uses the headerName request header to get the client IP.
func (SingleIPHeaderStrategy) ClientIP ¶ added in v1.0.0
func (strat SingleIPHeaderStrategy) ClientIP(headers http.Header, _ string) string
ClientIP derives the client IP using this strategy. headers is expected to be like http.Request.Header. The returned IP may contain a zone identifier. If no valid IP can be derived, empty string will be returned.
type Strategy ¶
type Strategy interface { // ClientIP returns empty string if there is no derivable IP. In many cases this // should be treated as a misconfiguration error, unless the strategy is attempting to // get an untrustworthy or optional value. // All implementations of this method must be threadsafe. ClientIP(headers http.Header, remoteAddr string) string }
Strategy is satisfied by all of the specific strategies in this package. It can be used instead of the concrete types if the strategy is to be determined at runtime, depending on configuration, for example.