CloudflareRails

repository·main·Indexed 43 days ago

https://github.com/modosc/cloudflare-rails

A Ruby gem that configures Rails to correctly identify client IP addresses when traffic is proxied through Cloudflare. It mitigates IP spoofing by verifying incoming requests against Cloudflare's IP ranges and provides a `#cloudflare?` method on `Rack::Request` to identify and block traffic that bypasses the Cloudflare WAF.

Tokens
1.8K
Snippets
7
Records
10
Agent score
55%

What's inside CloudflareRails

  1. Install cloudflare-rails

    main

    Add the gem to your Gemfile within a specific environment group (like :production, :staging, or :beta) to ensure it is not loaded in :development or :test.

    group :production do
      gem 'cloudflare-rails'
    end

    Then run bundle to install.

    Prerequisite: You must have a cache_store configured in your Rails application for the gem to fetch and cache Cloudflare IP lists.

    group :production do
      # or :staging or :beta or whatever environments you are using cloudflare in.
      # you probably don't want this for :test or :development
      gem 'cloudflare-rails'
    end
  2. Configure Kamal 2 for CloudflareRails

    main

    If you deploy using Kamal 2, kamal-proxy will not forward headers to your Rails app while using SSL unless explicitly configured. To ensure cloudflare-rails works correctly in a Kamal-deployed app using SSL, add forward_headers: true to your proxy section in your deploy.yml.

    proxy:
      ssl: true
      host: example.com
      forward_headers: true
  3. Configure Cloudflare cache parameters

    main

    You can customize how long the Cloudflare IP lists are cached and the timeout for fetching them by setting config.cloudflare.expires_in and config.cloudflare.timeout in your Rails configuration.

    Defaults:

    • expires_in: 12.hours
    • timeout: 5.seconds
    config.cloudflare.expires_in = 12.hours
    config.cloudflare.timeout = 5.seconds
  4. How CloudflareRails identifies Cloudflare traffic

    main

    The CloudflareRails::CheckTrustedProxies module provides logic to determine if a request is coming through Cloudflare. It works by patching Rack::Request::Helpers to ensure that request.ip is correctly resolved within Rack and Rails.

    It identifies Cloudflare traffic by:

    1. Checking if the REMOTE_ADDR or entries in the X-Forwarded-For header match known Cloudflare IP ranges (retrieved via CloudflareRails::Importer.cloudflare_ips).
    2. Using a 'trusted prefix' approach: it iterates through the list of IP addresses (starting from REMOTE_ADDR and moving backwards through X-Forwarded-For) and stops as soon as it encounters an IP that is not a trusted proxy. If any of the IPs in this trusted chain are Cloudflare IPs, the request is considered to be coming from Cloudflare.
  5. Use the #cloudflare? method to block non-Cloudflare traffic

    main

    The gem adds a #cloudflare? method to Rack::Request. This method returns true if the request originates from Cloudflare (or a trusted proxy that has Cloudflare in the X-Forwarded-For chain) and false otherwise.

    You can use this to block traffic that bypasses your Cloudflare WAF. For example, using Rack::Attack:

    Rack::Attack.blocklist('CloudFlare WAF bypass') do |req|
      !req.cloudflare?
    end

    Behavioral Note: The method returns true if Cloudflare is the immediate REMOTE_ADDR or if it appears in the X-Forwarded-For header behind trusted proxies. However, it returns false if an untrusted IP appears to the left of Cloudflare in the X-Forwarded-For header.

  6. Configure CloudflareRails settings

    main

    The gem automatically adds a cloudflare configuration object to your Rails application. You can customize the default behavior by setting values on config.cloudflare.

    Available configuration keys:

    • expires_in: The duration for which proxy information is cached. Defaults to 12.hours.
    • timeout: The timeout duration. Defaults to 5.seconds.

    Example configuration in config/application.rb or an initializer:

    module YourApp
      class Application < Rails::Application
        config.cloudflare.expires_in = 6.hours
        config.cloudflare.timeout = 10.seconds
      end
    end
    config.cloudflare.expires_in = 6.hours
    config.cloudflare.timeout = 10.seconds
  7. Fetch Cloudflare IP ranges with CloudflareRails::Importer

    main

    The CloudflareRails::Importer class provides methods to retrieve the current list of Cloudflare IPv4 and IPv6 IP ranges. It supports fetching directly from Cloudflare's API or using a cached version to improve performance and reduce network overhead.

    Available Methods

    • CloudflareRails::Importer.cloudflare_ips(refresh: false): Returns a frozen array containing both IPv4 and IPv6 IPAddr objects. If refresh: true is passed, it bypasses the local cache and attempts a fresh fetch.
    • CloudflareRails::Importer.ips_v4: Fetches only IPv4 ranges directly from Cloudflare.
    • CloudflareRails::Importer.ips_v6: Fetches only IPv6 ranges directly from Cloudflare.
    • CloudflareRails::Importer.fetch_with_cache(type): Fetches the specified type (:ips_v4 or :ips_v6) and stores it in Rails.cache using the duration configured in Rails.application.config.cloudflare.expires_in.

    Error Handling and Fallbacks

    If a network error or an invalid response occurs during the fetch, the importer logs the error to Rails.logger.error and returns a hardcoded set of fallback IP ranges defined in CloudflareRails::FallbackIps.

    # Get all Cloudflare IPs (cached by default)
    ips = CloudflareRails::Importer.cloudflare_ips
    
    # Force a fresh fetch from Cloudflare
    ips = CloudflareRails::Importer.cloudflare_ips(refresh: true)
  8. Handle ResponseError in CloudflareRails::Importer

    main

    When a request to Cloudflare fails (i.e., the response is not a Net::HTTPSuccess), the importer raises a CloudflareRails::Importer::ResponseError. This error object contains the original Net::HTTPResponse object, allowing you to inspect the status code or body for debugging purposes.

    begin
      CloudflareRails::Importer.ips_v4
    rescue CloudflareRails::Importer::ResponseError => e
      puts "Failed with status: #{e.response.code}"
    end