Class: ActionDispatch::RemoteIp

Inherits:
Object
  • Object
show all
Defined in:
lib/action_dispatch/middleware/remote_ip.rb

Overview

This middleware calculates the IP address of the remote client that is making the request. It does this by checking various headers that could contain the address, and then picking the last-set address that is not on the list of trusted IPs. This follows the precedent set by e.g. the Tomcat server, with reasoning explained at length by @gingerlime. A more detailed explanation of the algorithm is given at GetIp#calculate_ip.

Some Rack servers concatenate repeated headers, like HTTP RFC 2616 requires. Some Rack servers simply drop preceding headers, and only report the value that was given in the last header. If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn) then you should test your Rack server to make sure your data is good.

IF YOU DON’T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. This middleware assumes that there is at least one proxy sitting around and setting headers with the client’s remote IP address. If you don’t use a proxy, because you are hosted on e.g. Heroku without SSL, any client can claim to have any IP address by setting the X-Forwarded-For header. If you care about that, then you need to explicitly drop or ignore those headers sometime before this middleware runs.

Defined Under Namespace

Classes: GetIp, IpSpoofAttackError

Constant Summary collapse

TRUSTED_PROXIES =

The default trusted IPs list simply includes IP addresses that are guaranteed by the IP specification to be private addresses. Those will not be the ultimate client IP in production, and so are discarded. See en.wikipedia.org/wiki/Private_network for details.

%r{
  ^127\.0\.0\.1$                | # localhost IPv4
  ^::1$                         | # localhost IPv6
  ^fc00:                        | # private IPv6 range fc00
  ^10\.                         | # private IPv4 range 10.x.x.x
  ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
  ^192\.168\.                     # private IPv4 range 192.168.x.x
}x

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, check_ip_spoofing = true, custom_proxies = nil) ⇒ RemoteIp

Create a new RemoteIp middleware instance.

The check_ip_spoofing option is on by default. When on, an exception is raised if it looks like the client is trying to lie about its own IP address. It makes sense to turn off this check on sites aimed at non-IP clients (like WAP devices), or behind proxies that set headers in an incorrect or confusing way (like AWS ELB).

The custom_trusted argument can take a regex, which will be used instead of TRUSTED_PROXIES, or a string, which will be used in addition to TRUSTED_PROXIES. Any proxy setup will put the value you want in the middle (or at the beginning) of the X-Forwarded-For list, with your proxy servers after it. If your proxies aren’t removed, pass them in via the custom_trusted parameter. That way, the middleware will ignore those IP addresses, and return the one that you want.



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/action_dispatch/middleware/remote_ip.rb', line 57

def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
  @app = app
  @check_ip = check_ip_spoofing
  @proxies = case custom_proxies
    when Regexp
      custom_proxies
    when nil
      TRUSTED_PROXIES
    else
      Regexp.union(TRUSTED_PROXIES, custom_proxies)
    end
end

Instance Attribute Details

#check_ipObject (readonly)

Returns the value of attribute check_ip.



40
41
42
# File 'lib/action_dispatch/middleware/remote_ip.rb', line 40

def check_ip
  @check_ip
end

#proxiesObject (readonly)

Returns the value of attribute proxies.



40
41
42
# File 'lib/action_dispatch/middleware/remote_ip.rb', line 40

def proxies
  @proxies
end

Instance Method Details

#call(env) ⇒ Object

Since the IP address may not be needed, we store the object here without calculating the IP to keep from slowing down the majority of requests. For those requests that do need to know the IP, the GetIp#calculate_ip method will calculate the memoized client IP address.



74
75
76
77
# File 'lib/action_dispatch/middleware/remote_ip.rb', line 74

def call(env)
  env["action_dispatch.remote_ip"] = GetIp.new(env, self)
  @app.call(env)
end