Copy Cloudflare's True-Client-IP header to X-Forwarded-For

ref: https://3.basecamp.com/2914079/buckets/37331921/todos/9243121525
This commit is contained in:
Mike Dalessio
2025-11-03 13:39:55 -05:00
parent b677273ef7
commit 8538c21d4e
2 changed files with 58 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
#
# Cloudflare sets a True-Client-IP header, which for most 37signals apps gets copied to
# X-Forwarded-For by an iRule on the F5 load balancers:
#
# https://github.com/basecamp/f5-tf/blob/1543f7bfa3961a79e397f80cf041d75567f1b2f8/ams-base/iRules/manage_x_forwarded.tcl
#
# However, for Fizzy the F5s are configured to do passthrough, so the header value isn't being
# copied for us. Let's do that bit of work here, before Rails' RemoteIp middleware.
#
class TrackTrueClientIp
def initialize(app)
@app = app
end
def call(env)
if env["HTTP_TRUE_CLIENT_IP"].present?
env["HTTP_X_FORWARDED_FOR"] = env["HTTP_TRUE_CLIENT_IP"]
end
@app.call(env)
end
end
Rails.application.config.middleware.insert_before ActionDispatch::RemoteIp, TrackTrueClientIp
+34
View File
@@ -0,0 +1,34 @@
require "test_helper"
class TrackTrueClientIpTest < ActiveSupport::TestCase
setup do
@app = ->(env) { [ 200, {}, [ "OK" ] ] }
@middleware = TrackTrueClientIp.new(@app)
end
test "sets X-Forwarded-For header when True-Client-IP header is present" do
env = { "HTTP_TRUE_CLIENT_IP" => "123.123.123.123" }
@middleware.call(env)
assert_equal "123.123.123.123", env["HTTP_X_FORWARDED_FOR"]
end
test "does not modify environment when True-Client-IP header is absent" do
env = {}
@middleware.call(env)
assert_nil env["HTTP_X_FORWARDED_FOR"]
env = { "HTTP_X_FORWARDED_FOR" => "234.234.234.234" }
@middleware.call(env)
assert_equal "234.234.234.234", env["HTTP_X_FORWARDED_FOR"]
end
test "calls the next middleware in the stack" do
called = false
app = ->(env) { called = true; [ 200, {}, [ "OK" ] ] }
middleware = TrackTrueClientIp.new(app)
middleware.call({})
assert called
end
end