When running a Sinatra application we will typically place an Nginx web proxy in front of the application. One drawback of this is obtaining the IP address of the connecting client when using the Ruby Rack method request.ip. This method shown below:

def ip
  remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
  remote_addrs = reject_trusted_ip_addresses(remote_addrs)

  return remote_addrs.first if remote_addrs.any?

  forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
    .map { |ip| strip_port(ip) }

  return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
end

Can be found in the lib/rack/request.rb file. As you can see it makes use of trusted ip addresses to attempt to reject “trusted” upstream proxies, however the method that performs these checks is somewhat limited:

def trusted_proxy?(ip)
  Rack::Request.ip_filter.call(ip)
end

The ip_filter is a lambda: lambda { |ip| ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i }, so we are restricting our trusted range of addresses to just those used on private IP ranges. Not ideal in a more complex production environment.

The solution without coding up something to replace request.ip? Well you can monkey patch the method. In Sinatra the Rack::Request object is actually as you might expect Sinatra::Request. Our approach to the money patch is this:

module Sinatra
  class Request

    def trusted_proxy?(ip)                                                                                             
      super || upstream_proxy?(ip)
    end

    private

    def upstream_proxy?(ip)
      proxies = defined?(Settings) ? Array(Settings.upstream_proxies) : []
      proxies.include? ip
    end   
 
  end
end

We regularly make use of the Ruby Config gem for our application configuration and So we’re pulling the proxies out of that configuration object if it exists. This means we can simply define our upstream proxy in our settings.local.yml file like this:

---
upstream_proxies:
  - 0.0.0.0
  - 0.0.0.0

This approach avoids breaking the original method, but allows us to work with our real world infrastructure. Our Nginx proxy configuration therefore only needs a line like:

proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;