@@ -, +, @@
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__
--