ruby-on-rails `remote_ip`返回代理的IP地址,而不是客户端的IP地址

jv4diomz  于 2023-05-02  发布在  Ruby
关注(0)|答案(1)|浏览(255)

我在代理后面运行Ruby on Rails应用程序。代理按预期设置并向应用程序发送X-Forwarded-For头。它可能看起来像这样:

X-Forwarded-For = '1.1.1.1, 2.2.2.2'

当我调用remote_ip时,Rails返回2.2.2.2。当应用程序写入日志条目时也是如此,然后将请求记录为IP地址2.2.2.2
源代码中记录了此行为:

# In order to find the first address that is (probably) accurate, we
# take the list of IPs, remove known and trusted proxies, and then take
# the last address left, which was presumably set by one of those proxies.

到目前为止没有意外或令人惊讶的行为。
但我的问题的第一部分是,该列表中的最后一个IP地址是代理本身的IP。这意味着我实际上希望remote_ip返回1.1.1.1而不是2.2.2.2。因为1.1.1.1将是真实的客户端的IP地址。
Ruby on Rails为这个需求提供的解决方案是将2.2.2.2添加到TRUSTED_PROXIES,如ActionDispatch::RemoteIp中所述。当2.2.2.2被添加到可信代理时,Rails将从X-Forwarded-For中的IP地址列表中删除这些可信代理,并返回剩余IP列表中的最后一个,即1.1.1.1
但问题的第二部分是我使用的是基于云的托管代理。它的IP可以随时更改,它的IP可以是Amazon AWS上EC2示例可用的任何IP。这使得在TRUSTED_PROXIES中添加其IP很容易出错,因为它随时可能过时。并且没有更改代理行为的选项。例如,我不能告诉代理不要将自己的IP添加到X-Forwarded-For头中。
有什么办法解决这个问题吗?

mzmfm0qo

mzmfm0qo1#

我决定使用一个额外的中间件来解决这个问题,我将它添加到应用程序的中间件堆栈中,正好在ActionDispatch::RemoteIp之前,它为Rails应用程序提取remote_ip
中间件只在检查应用程序实际上在代理之后运行之后才操作X-Forwarded-For头,我可以通过特定头的存在来检查。我只交换了最后两个条目,以使所有IP地址仍然可用,以便进行调试,并且现在检查默认行为太多。

# in config/initializers/proxy_remote_ip_fix.rb
class ProxyRemoteIpFix
  # There is a special header only set when running behind the proxy. Use this 
  # header to decide when to enable the `X-Forwared-For` rewrite.
  PROXY_IDENTIFING_HEADER = 'HTTP_X_PROXY_WAS_HERE'
  X_FORWARDED_FOR_HEADER  = 'HTTP_X_FORWARDED_FOR'

  def initialize(app)
    @app = app
  end

  def call(env)
    manipulate_x_forwarded_for_header(env) if behind_proxy?(env)

    @app.call(env)
  end

  private

  # We can assume that the last entry in the list is the IP of the proxy
  # and the second last entry has a high chance to be the client's
  # real IP address.
  #
  # To trick Ruby on Rails to pick the second last IP instead of the last when
  # calling `remote_ip` we simply swap their positions in the list. We do not
  # remove the proxy instance's IP completely to still keep its IP in the header
  # for debugging reasons.
  def manipulate_x_forwarded_for_header(env)
    ips = ips_from_forwarded_for_header(env)
    return unless ips.size > 1

    # swap the last two entries
    ips[-1], ips[-2] = ips[-2], ips[-1]

    env[X_FORWARDED_FOR_HEADER] = ips.join(', ')
  end

  def ips_from_forwarded_for_header(env)
    return [] unless env.key?(X_FORWARDED_FOR_HEADER)

    env[X_FORWARDED_FOR_HEADER].split(',').map(&:strip)
  end

  def behind_proxy?(env)
    env.key?(PROXY_IDENTIFING_HEADER)
  end
end

# ProxyRemoteIpFix must be added before the ActionDispatch::RemoteIp,
# because its behavior changes the way how the ActionDispatch::RemoteIp works.
Rails.application.config.middleware.insert_before(
  ActionDispatch::RemoteIp, ProxyRemoteIpFix
)

相关问题