Class: FirebaseHostingClientIp::Middleware
- Inherits:
-
Object
- Object
- FirebaseHostingClientIp::Middleware
- Defined in:
- lib/firebase_hosting_client_ip/middleware.rb,
sig/firebase_hosting_client_ip.rbs
Overview
Makes request.remote_ip return the client IP reported by Firebase Hosting.
Firebase Hosting fronts the application with Fastly, which sets Fastly-Client-IP to the address of the end user. That header is the only trustworthy source in this architecture: behind Cloud Run, the values Rails derives on its own (from X-Forwarded-For) resolve to a Google front-end address, and are attacker-controlled if the service can be reached directly.
The middleware runs after ActionDispatch::RemoteIp and replaces the lazy value that middleware installed at env, which is exactly what ActionDispatch::Request#remote_ip reads. REMOTE_ADDR is never modified, so request.ip continues to report the real peer address.
When the header is absent or invalid, behavior is governed by FirebaseHostingClientIp.config.missing_header_fallback.
Constant Summary collapse
- REMOTE_IP_KEY =
"action_dispatch.remote_ip"- HEADER =
"HTTP_FASTLY_CLIENT_IP"- DISALLOWED =
Characters that make IPAddr accept something which is not a bare client address. IPAddr.new is more permissive than this contract needs: it parses CIDR ranges ("192.0.2.1/24"), bracketed IPv6 ("[2001:db8::42]"), and zone identifiers ("2001:db8::42%eth0"). None of those are values Rails should hand out as request.remote_ip, so they are rejected before parsing rather than repaired.
%r{[/\[\]%]}
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#initialize(app) ⇒ Middleware
constructor
A new instance of Middleware.
-
#validated_fastly_ip(env) ⇒ String?
Returns the trimmed header value verbatim when it is a single valid host address, nil otherwise.
Constructor Details
#initialize(app) ⇒ Middleware
33 34 35 |
# File 'lib/firebase_hosting_client_ip/middleware.rb', line 33 def initialize(app) @app = app end |
Instance Method Details
#call(env) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/firebase_hosting_client_ip/middleware.rb', line 37 def call(env) ip = validated_fastly_ip(env) fallback = FirebaseHostingClientIp.config.missing_header_fallback if ip env[REMOTE_IP_KEY] = ip elsif fallback != Configuration::PASSTHROUGH env[REMOTE_IP_KEY] = fallback end @app.call(env) end |
#validated_fastly_ip(env) ⇒ String?
Returns the trimmed header value verbatim when it is a single valid host address, nil otherwise.
This validates without transforming: IPAddr is used only as a gate, and the string handed to Rails keeps the header's own spelling rather than a canonicalized form. Resolving the trusted header is this gem's only responsibility; imposing an IP canonicalization policy is not.
59 60 61 62 63 64 65 66 67 |
# File 'lib/firebase_hosting_client_ip/middleware.rb', line 59 def validated_fastly_ip(env) raw = env[HEADER]&.strip return nil if raw.nil? || raw.empty? || raw.match?(DISALLOWED) IPAddr.new(raw) raw rescue IPAddr::Error nil end |