@@ -, +, @@ headers so REMOTE_ADDR features work behind a proxy 172.22.0.1 1.1.1.1 --- C4/Auth.pm | 2 + C4/Context.pm | 20 +++++ C4/Installer/PerlDependencies.pm | 5 ++ Koha/Middleware/RealIP.pm | 120 +++++++++++++++++++++++++ debian/templates/koha-conf-site.xml.in | 5 ++ debian/templates/plack.psgi | 2 + etc/koha-conf.xml | 5 ++ 7 files changed, 159 insertions(+) create mode 100644 Koha/Middleware/RealIP.pm --- a/C4/Auth.pm +++ a/C4/Auth.pm @@ -57,6 +57,8 @@ BEGIN { else { exit } } + C4::Context->set_remote_address; + $debug = $ENV{DEBUG}; @ISA = qw(Exporter); @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions); --- a/C4/Context.pm +++ a/C4/Context.pm @@ -1099,6 +1099,26 @@ sub temporary_directory { return C4::Context->config('tmp_path') || File::Spec->tmpdir; } +=head3 set_remote_address + +set_remote_address should be called at the beginning of every script +that is *not* running under plack in order to the REMOTE_ADDR environment +variable to be set correctly. + +=cut + +sub set_remote_address { + if ( C4::Context->config('koha_trusted_proxies') ) { + require CGI; + my $cgi = CGI->new; + my $header = $cgi->http('HTTP_X_FORWARDED_FOR'); + + if ($header) { + require Koha::Middleware::RealIP; + $ENV{REMOTE_ADDR} = Koha::Middleware::RealIP::get_real_ip( $ENV{REMOTE_ADDR}, $header ); + } + } +} 1; --- a/C4/Installer/PerlDependencies.pm +++ a/C4/Installer/PerlDependencies.pm @@ -908,6 +908,11 @@ our $PERL_DEPS = { required => '1', min_ver => '0.37', }, + 'Net::Netmask' => { + 'usage' => 'Koha X-Forwarded-For support', + 'required' => '0', + 'min_ver' => '1.9022', + }, 'Net::Z3950::SimpleServer' => { 'usage' => 'Z39.50 responder', 'required' => '0', --- a/Koha/Middleware/RealIP.pm +++ a/Koha/Middleware/RealIP.pm @@ -0,0 +1,120 @@ +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 . + +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 ( $env->{HTTP_X_FORWARDED_FOR} ) { + my @trusted_proxy = $self->trusted_proxy ? @{ $self->trusted_proxy } : undef; + + if (@trusted_proxy) { + my $addr = get_real_ip( $env->{REMOTE_ADDR}, $env->{HTTP_X_FORWARDED_FOR}, \@trusted_proxy ); + $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 @forwarded_for = $header =~ /([^,\s]+)/g; + return $remote_addr unless @forwarded_for; + + my $trusted_proxies = get_trusted_proxies(); + + my @unconfirmed = ( @forwarded_for, $remote_addr ); + + my $real_ip; + 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 = map { Net::Netmask->new($_) } @trusted_proxies_ip; + return \@trusted_proxies; +} + + +=head1 AUTHORS + +Kyle M Hall + +=cut + +1; --- a/debian/templates/koha-conf-site.xml.in +++ a/debian/templates/koha-conf-site.xml.in @@ -345,6 +345,11 @@ __END_SRU_PUBLICSERVER__ 50 2 + + + localhost:9200 --- a/debian/templates/plack.psgi +++ a/debian/templates/plack.psgi @@ -68,8 +68,10 @@ my $apiv1 = builder { builder { enable "ReverseProxy"; enable "Plack::Middleware::Static"; + # + is required so Plack doesn't try to prefix Plack::Middleware:: enable "+Koha::Middleware::SetEnv"; + enable "+Koha::Middleware::RealIP"; mount '/opac' => $opac; mount '/intranet' => $intranet; --- a/etc/koha-conf.xml +++ a/etc/koha-conf.xml @@ -161,6 +161,11 @@ __PAZPAR2_TOGGLE_XML_POST__ 50 2 + + + __ELASTICSEARCH_SERVERS__ --