Как я могу переопределить методы ruby ​​и rails request.ip и request.remote_ip?

У меня есть приложение rails, и я пытаюсь перегрузить request.remote_ip и request.ip, чтобы использовать заголовок cloudflare (HTTP_CF_CONNECTING_IP), если он присутствует... Я пробовал это, но ни один из них не работает:

module Rack
  class Request
    class << self
      def ip
        @ip ||= (@env['HTTP_CF_CONNECTING_IP'] || super)
      end
    end
  end
end

module ActionDispatch
  class Request < Rack::Request
    class << self
      def remote_ip
        @remote_ip ||= (@env['HTTP_CF_CONNECTING_IP'] || super)
      end
    end
  end
end

Я не могу использовать дополнительный метод, как

def connecting_ip
  @env['HTTP_CF_CONNECTING_IP'] || request.remote_ip
end

в application_controller, потому что у меня есть некоторые другие гемы (например, devise), которые используют request.ip

Спасибо!

2 ответа

Решение

Я верю request это пример. Но вы определяете методы класса. Удалить class << self ерунда и вы вместо этого будете переопределять методы экземпляра.

Просто заметка, это звучит как-то безумно. Будь осторожен там. Фреймворки не всегда любят переставлять свои кишки.


Ваше сообщение об ошибке при использовании методов экземпляра означает, что происходит что-то еще. super вызывает реализацию суперкласса. Но когда вы снова открываете класс и переопределяете вещи, вы буквально перезаписываете исходную реализацию. Поскольку метод не существует в суперклассе, super не работает

Вместо этого вы можете использовать alias чтобы сохранить исходную реализацию, прежде чем объявить новый метод, который заменит его.

module ActionDispatch
  class Request < Rack::Request
    alias :remote_ip_orig :remote_ip
    def remote_ip
      @remote_ip ||= (@env['HTTP_CF_CONNECTING_IP'] || remote_ip_orig)
    end
  end
end

Если вы хотите использовать CloudFlare и обнаруживать истинный IP-адрес, как я делал без модуля Ngingx или Apache, такой подход к исправлению обезьян является действительно плохой идеей, это приведет к неожиданным результатам в будущем. Вам было бы намного лучше использовать Middleware, так как его следует использовать. Вот тот, который я придумал и реализовал.

module Rack
  class CloudFlareFixup
    def initialize(app)
      @app = app
    end

    def call(env)
      if env['HTTP_CF_CONNECTING_IP']
        env['HTTP_X_FORWARDED_FOR'] = env['HTTP_CF_CONNECTING_IP']
        env['REMOTE_ADDR'] = env['HTTP_CF_CONNECTING_IP']
      end
      @app.call(env)
    end
  end
end

Просто добавьте это в ваше application.rb

config.middleware.insert_before(0, Rack::CloudFlareFixup)

Вы можете увидеть полный Gist для этого на https://gist.github.com/mattheworiordan/9024372

Я перепробовал множество решений этой проблемы. Вот что я нашел:

  • cloudflare-railsgem => это устанавливает ip и remote_ip на исходный ip. Я хотел, чтобы ip представлял IP-адрес cloudflare, с которого он пришел, а remote_ip - исходный IP-адрес пользователя, чтобы это не сработало для меня. Он работает, просматривая список диапазонов IP-адресов Cloudflare каждые 12 часов и добавляя их в trust_proxies. Затем он исправляет: -ActionDispatch::Request.ip, ActionDispatch::Request.remote_ip а также Rack::Attack::Request.trusted_proxy? с доверенными прокси-серверами cloudflare, чтобы ip и remote_ip игнорировали их и использовали исходный ip пользователя, инициировавшего запрос.
  • actionpack-cloudflaregem => это намного проще. все, что он делает, устанавливается напрямуюActionDispatch.remote_ip = @env['HTTP_CF_CONNECTING_IP'] что соответствует лучшим рекомендациям Cloudflare, но, похоже, не стоило использовать гем для 1 строки кода.
  • Ручная прокатка
# config/initializers/cloudflare.rb

# These values should rarely or never change and Cloudflare should alert us before that happens.
# The list of Cloudflare proxy server ip ranges comes from https://www.cloudflare.com/ips/
CLOUDFLARE_IP_RANGES = [IPAddr.new("103.21.244.0/22"),IPAddr.new("103.22.200.0/22"),IPAddr.new("103.31.4.0/22"),IPAddr.new("104.16.0.0/12"),IPAddr.new("108.162.192.0/18"),IPAddr.new("131.0.72.0/22"),IPAddr.new("141.101.64.0/18"),IPAddr.new("162.158.0.0/15"),IPAddr.new("172.64.0.0/13"),IPAddr.new("173.245.48.0/20"),IPAddr.new("188.114.96.0/20"),IPAddr.new("190.93.240.0/20"),IPAddr.new("197.234.240.0/22"),IPAddr.new("2405:8100::/32"),IPAddr.new("2405:b500::/32"),IPAddr.new("2606:4700::/32"),IPAddr.new("2803:f800::/32"),IPAddr.new("2a06:98c0::/29"),IPAddr.new("2c0f:f248::/32")
]

# By adding the cloudflare IP ranges as trusted_proxies, rails ignores those IPs when setting remote_ip and correctly sets it to the originating IP
Rails.application.config.action_dispatch.trusted_proxies = CLOUDFLARE_IP_RANGES + ActionDispatch::RemoteIp::TRUSTED_PROXIES

Наконец, я хотел заблокировать запросы без прокси

# config/initializers/rack_attack.rb
class Rack::Attack
  class Request < ::Rack::Request
    # Create a remote_ip method for rack request by setting it equal to the cloudflare connecting ip header
    # To restore original visitor IP addresses at your origin web server, Cloudflare recommends your logs or applications
    # look at CF-Connecting-IP instead of X-Forwarded-For since CF-Connecting-IP has a consistent format containing only one IP.
    # https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-CloudFlare-handle-HTTP-Request-headers-
    def remote_ip
      @remote_ip ||= (env['HTTP_CF_CONNECTING_IP'] || ip).to_s
    end

    # This checks the request IP against Cloudflare IP ranges and the action dispatch default trusted proxies.
    # These include various local IPs like 127.0.0.1 so that local requests won't be blocked.
    def proxied?
      Rails.application.config.action_dispatch.trusted_proxies.any? { |range| range === ip }
    end
  end

  # Block all requests coming from non-Cloudflare IPs
  blocklist("block non-proxied requests in production") do |req|
    if req.proxied?
      false
    else
      req.log :warn
      true
    end
  end
end
Другие вопросы по тегам