Koha-clms/Koha/Middleware/RealIP.pm

133 lines
3.7 KiB
Perl

package Koha::Middleware::RealIP;
# Copyright 2019 ByWater Solutions and the Koha Dev Team
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use parent qw(Plack::Middleware);
use C4::Context;
use Net::Netmask;
use Plack::Util::Accessor qw( trusted_proxy );
=head1 METHODS
=head2 prepare_app
This method generates and stores the list of trusted ip's as Netmask objects
at the time Plack starts up, obviating the need to regerenate them on each request.
=cut
sub prepare_app {
my $self = shift;
$self->trusted_proxy( get_trusted_proxies() );
}
=head2 call
This method is called for each request, and will ensure the correct remote address
is set in the REMOTE_ADDR environment variable.
=cut
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';
# 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'};
if ($header) {
my $addr = get_real_ip( $env->{REMOTE_ADDR}, $header );
$ENV{REMOTE_ADDR} = $addr;
$env->{REMOTE_ADDR} = $addr;
}
return $self->app->($env);
}
=head2 get_real_ip
my $address = get_real_ip( $remote_addres, $x_forwarded_for_header );
This method takes the current remote address and the x-forwarded-for header string,
determines the correct external ip address, and returns it.
=cut
sub get_real_ip {
my ( $remote_addr, $header ) = @_;
my $enable_trusted_proxy_for_headers = C4::Context->config('enable_trusted_proxy_for_headers') // 1;
return $remote_addr unless $enable_trusted_proxy_for_headers;
my @forwarded_for = $header =~ /([^,\s]+)/g;
return $remote_addr unless @forwarded_for;
my $trusted_proxies = get_trusted_proxies();
#X-Forwarded-For: <client>, <proxy1>, <proxy2>
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;
}
=head2 get_trusted_proxies
This method returns an arrayref of Net::Netmask objects for all
the trusted proxies given to Koha.
=cut
sub get_trusted_proxies {
my $proxies_conf = C4::Context->config('koha_trusted_proxies');
return unless $proxies_conf;
my @trusted_proxies_ip = split( / /, $proxies_conf );
my @trusted_proxies = ();
foreach my $ip (@trusted_proxies_ip) {
my $mask = Net::Netmask->new2($ip);
if ($mask) {
push( @trusted_proxies, $mask );
} else {
warn "$Net::Netmask::error";
}
}
return \@trusted_proxies;
}
=head1 AUTHORS
Kyle M Hall <kyle@bywatersolutions.com>
=cut
1;