@@ -, +, @@
headers so REMOTE_ADDR features work behind a proxy
172.22.0.1 1.1.1.1
---
C4/Context.pm | 20 +++++
Koha/Middleware/RealIP.pm | 118 +++++++++++++++++++++++++
debian/templates/koha-conf-site.xml.in | 5 ++
debian/templates/plack.psgi | 2 +
etc/koha-conf.xml | 5 ++
offline_circ/process.pl | 2 +
svc/cataloguing/metasearch | 2 +
tools/background-job-progress.pl | 2 +
tools/batchMod.pl | 2 +
tools/batch_record_modification.pl | 2 +
tools/manage-marc-import.pl | 2 +
tools/stage-marc-import.pl | 2 +
tools/upload-cover-image.pl | 2 +
13 files changed, 166 insertions(+)
create mode 100644 Koha/Middleware/RealIP.pm
--- a/C4/Context.pm
+++ a/C4/Context.pm
@@ -1078,6 +1078,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/Koha/Middleware/RealIP.pm
+++ a/Koha/Middleware/RealIP.pm
@@ -0,0 +1,118 @@
+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 );
+
+=head3 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() );
+}
+
+=head3 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 } if $self->trusted_proxy;
+
+ 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);
+}
+
+=head3 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;
+}
+
+=head3 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
+
+
+
localhost:9200
--- a/offline_circ/process.pl
+++ a/offline_circ/process.pl
@@ -24,6 +24,8 @@ use CGI qw ( -utf8 );
use C4::Auth;
use C4::Circulation;
+C4::Context->set_remote_address;
+
my $query = CGI->new;
my ($template, $loggedinuser, $cookie) = get_template_and_user({
--- a/svc/cataloguing/metasearch
+++ a/svc/cataloguing/metasearch
@@ -23,6 +23,8 @@ use C4::Service;
use Encode qw( encode_utf8 );
use Koha::MetaSearcher;
+C4::Context->set_remote_address;
+
my ( $query, $response ) = C4::Service->init( catalogue => 1 );
my ( $query_string, $servers ) = C4::Service->require_params( 'q', 'servers' );
--- a/tools/background-job-progress.pl
+++ a/tools/background-job-progress.pl
@@ -29,6 +29,8 @@ use C4::BackgroundJob;
use CGI::Cookie; # need to check cookies before
# having CGI parse the POST request
+C4::Context->set_remote_address;
+
my $input = CGI->new;
my %cookies = CGI::Cookie->fetch;
my ($auth_status, $sessionID) = check_cookie_auth($cookies{'CGISESSID'}->value, { tools => '*' });
--- a/tools/batchMod.pl
+++ a/tools/batchMod.pl
@@ -41,6 +41,8 @@ use Koha::Items;
use Koha::ItemTypes;
use Koha::Patrons;
+C4::Context->set_remote_address;
+
my $input = new CGI;
my $dbh = C4::Context->dbh;
my $error = $input->param('error');
--- a/tools/batch_record_modification.pl
+++ a/tools/batch_record_modification.pl
@@ -34,6 +34,8 @@ use Koha::Biblios;
use Koha::MetadataRecord::Authority;
use Koha::Virtualshelves;
+C4::Context->set_remote_address;
+
my $input = new CGI;
our $dbh = C4::Context->dbh;
my $op = $input->param('op') // q|form|;
--- a/tools/manage-marc-import.pl
+++ a/tools/manage-marc-import.pl
@@ -37,6 +37,8 @@ use C4::BackgroundJob;
use C4::Labels::Batch;
use Koha::BiblioFrameworks;
+C4::Context->set_remote_address;
+
my $script_name = "/cgi-bin/koha/tools/manage-marc-import.pl";
my $input = new CGI;
--- a/tools/stage-marc-import.pl
+++ a/tools/stage-marc-import.pl
@@ -43,6 +43,8 @@ use C4::BackgroundJob;
use C4::MarcModificationTemplates;
use Koha::Plugins;
+C4::Context->set_remote_address;
+
my $input = new CGI;
my $fileID = $input->param('uploadedfileid');
--- a/tools/upload-cover-image.pl
+++ a/tools/upload-cover-image.pl
@@ -49,6 +49,8 @@ use C4::Images;
use Koha::UploadedFiles;
use C4::Log;
+C4::Context->set_remote_address;
+
my $debug = 1;
my $input = new CGI;
--