From 5a428285088fb77557e2728483f44375f7f1a282 Mon Sep 17 00:00:00 2001 From: Tristin Stagg Date: Thu, 12 Jun 2025 18:36:36 +0000 Subject: [PATCH] Bug 39789: Add new "require_trusted_proxy_for_headers" 1) call will check the remote address against the list of trusted proxies first, if configured to do so, right off the bat. This allows us to not have to do this if we do not want to. Particularly useful if i were to tell an Koha server that it could only receive web requests from Cloudflare, or even if i was able to do that at the reverse proxy level. It's configurable so it can be disabled/enabled. a) If trusted, then it parses the header that was determined (from configuration or default X-Forwarded-For) b) Otherwise, it will fall back to the remote address 2) If not configured to check remote address against a trusted proxy, it will just parse the header that was determined. a) If it does not find a value, it will simply return the remote address. 3) "get_real_ip" no longer checks against trusted proxies, as that is done beforehand. --- Koha/Middleware/RealIP.pm | 42 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/Koha/Middleware/RealIP.pm b/Koha/Middleware/RealIP.pm index c4bf85184d..9861ab1362 100644 --- a/Koha/Middleware/RealIP.pm +++ b/Koha/Middleware/RealIP.pm @@ -51,13 +51,32 @@ sub call { my $self = shift; my $env = shift; - # If no reverse_proxy_ip_header, just use HTTP_X_FORWARDED_FOR as the header to check, simplifies logic and is a bit faster that way my $reverse_proxy_ip_header = C4::Context->config('reverse_proxy_ip_header') || 'HTTP_X_FORWARDED_FOR'; + my $require_trusted = C4::Context->config('require_trusted_proxy_for_headers'); - # Check the env for the custom header, if the custom header is not set, fall back to HTTP_X_FORWARDED_FOR my $header = $env->{$reverse_proxy_ip_header} || $env->{'HTTP_X_FORWARDED_FOR'}; + my $addr = $env->{REMOTE_ADDR}; + if ($header) { - my $addr = get_real_ip( $env->{REMOTE_ADDR}, $header ); + if ($require_trusted) { + + # Only trust header if REMOTE_ADDR is a trusted proxy + my $trusted_proxies = get_trusted_proxies(); + my $is_trusted = 0; + foreach my $netmask (@$trusted_proxies) { + if ( $netmask->match($addr) ) { + $is_trusted = 1; + last; + } + } + if ($is_trusted) { + $addr = get_real_ip( $env->{REMOTE_ADDR}, $header ); + } + } else { + + # Always trust the header + $addr = get_real_ip( $env->{REMOTE_ADDR}, $header ); + } $ENV{REMOTE_ADDR} = $addr; $env->{REMOTE_ADDR} = $addr; } @@ -80,21 +99,10 @@ sub get_real_ip { my @forwarded_for = $header =~ /([^,\s]+)/g; return $remote_addr unless @forwarded_for; - my $trusted_proxies = get_trusted_proxies(); - #X-Forwarded-For: , , - my $real_ip = shift @forwarded_for; - my @unconfirmed = ( @forwarded_for, $remote_addr ); - - while ( my $addr = pop @unconfirmed ) { - my $has_matched = 0; - foreach my $netmask (@$trusted_proxies) { - $has_matched++, last if $netmask->match($addr); - } - $real_ip = $addr, last unless $has_matched; - } - - return $real_ip; + my $real_ip = shift @forwarded_for; + return $real_ip if $real_ip; + return $remote_addr; } =head2 get_trusted_proxies -- 2.49.0