Skip to content

Visitors' IP addresses behind a proxy ​

Trek relies on request.remote_ip, the visitor's IP address as Rails works it out, in four places:

  • the active sessions list, on each user's profile;
  • the audit log (ip_address), when installed;
  • the rate limits on signing in and on password reset requests (5 attempts in 3 minutes per address), and on the admin's failed sign-ins (20 an hour per address);
  • the admin's allowed addresses, when set (see below).

How Rails finds the address ​

Requests rarely reach the app directly: a hosting platform's load balancer or router forwards them, and adds the visitor's address to the X-Forwarded-For header. Rails only believes that header when it comes from a proxy it trusts, by default local and private addresses (ActionDispatch::RemoteIp::TRUSTED_PROXIES). Platform routers usually connect from such private addresses, so remote_ip is the visitor's.

Behind a CDN or a web application firewall ​

A CDN or a firewall in front of the app (Cloudflare, Imperva, Bunny…) adds another hop, from public addresses Rails doesn't trust. remote_ip is then the CDN's address for every request:

  • every session and every audit log row shows the same IP;
  • everyone shares the rate limits: a few failed sign-ins anywhere lock everyone out for 3 minutes, and 20 for up to an hour.

List the CDN's address ranges, which it publishes, in config/application.rb:

ruby
# The CDN's ranges, from its documentation (e.g. https://www.cloudflare.com/ips/): without them,
# remote_ip is the CDN's address instead of the visitor's.
config.action_dispatch.trusted_proxies =
  ActionDispatch::RemoteIp::TRUSTED_PROXIES + %w[
    203.0.113.0/24
    2001:db8::/32
  ].map { |range| IPAddr.new(range) }

Keep Rails' own TRUSTED_PROXIES in the list. Only trust ranges you know: a trusted proxy is believed about the address it forwards, so a range too wide would let anyone choose the IP Trek records. When the CDN changes its ranges, update the list.

Checking ​

Sign in from your own connection, then compare the address on your profile's Active sessions tab with the one a service such as https://ifconfig.me shows you. If it's your platform's or your CDN's address instead, add their ranges as above.

Passkeys ​

A passkey is tied to the site's domain, and the browser signs the origin it was used on. Trek takes both from the request (request.host and request.base_url), so Rails must see the address the visitor typed: the original Host header, and https. Most platforms forward them (X-Forwarded-Host, X-Forwarded-Proto). If adding a passkey or signing in with one fails behind a proxy that terminates TLS without saying so, set config.assume_ssl = true in config/environments/production.rb.

Restricting the admin to IP addresses ​

Some clients require the back office to be reachable from their network only. List the addresses and ranges allowed in ADMIN_ALLOWED_IPS, separated by commas or spaces:

sh
ADMIN_ALLOWED_IPS="203.0.113.0/24, 2001:db8::/32, 198.51.100.7"

From anywhere else, /admin and everything below it, the sign-in page included, answers like an unknown URL: the app's 404 page, rather than a redirect to the sign-in page, so it doesn't even show there's an admin. The rest of the site is unaffected. Unset or empty, the admin is open to all, as by default. The variable is read when the app starts: restart it after a change. An entry that's neither an address nor a range stops it from starting.

Trek::AdminIpAllowlist, a middleware Trek's engine adds, checks request.remote_ip: behind a CDN or a firewall, add their ranges to trusted_proxies first (above), or every request seems to come from the CDN, and the admin is closed to everyone, or open to all if the CDN's range is allowed. Check the address Rails sees as above, from an allowed network, before setting the variable.

Released under the MIT License.