Apache-ForwardedFor

 view release on metacpan or  search on metacpan

lib/Apache/ForwardedFor.pm  view on Meta::CPAN


    my $r = shift;

    my $fwd_ips = $r->header_in('X-Forwarded-For');
    # $TRACE && warn(__PACKAGE__." bypassed - no X-Forward-For header") and
    return DECLINED unless $fwd_ips;

    # warn(__PACKAGE__." X-Forwarded-For header: $fwd_ips") if $TRACE;

    # Block based on Remove / Add AcceptForwarder values
    my %deny =map { $_ => 1 } $r->dir_config->get('ForwardedForDeny');
    if (exists $deny{$r->connection->remote_ip}) {
        # warn(__PACKAGE__." handling for IP ".$r->connection->remote_ip." refused by RemoveAcceptForwarder directive") if $TRACE;
        return DECLINED;
    }

    my %accept=map { $_ => 1 } $r->dir_config->get('ForwardedForAccept');
    if (!exists $accept{$r->connection->remote_ip} && keys %accept) {
        # warn(__PACKAGE__." handling for IP ".$r->connection->remote_ip." refused by AddAcceptForwarder directive") if $TRACE;
        return DECLINED;
    }

    # Extract the desired IP address
    if (my($ip) = $fwd_ips =~ /^([\d\.]+)/) {
        # warn(__PACKAGE__." original remote_ip: ".$r->connection->remote_ip) if $TRACE;
        Apache->connection->remote_ip($ip);
        # warn(__PACKAGE__." new remote_ip: ".$r->connection->remote_ip) if $TRACE;
    } else {
        # do nothing if no ip is in forwarded-for header
        # warn(__PACKAGE__." remote_ip: $ip unchanged") if $TRACE;
    }

    # Return declined to continue handling at this phase...
    DECLINED;

}


=head1 NAME

Apache::ForwardedFor - Re-set remote_ip to incoming client's ip when running mod_perl behind a reverse proxy server. 
In other words, copy the first IP from B<X-Forwarded-For> header, which was set by your reverse proxy server, 
to the B<remote_ip> connection property.

=head1 SYNOPSIS

  in httpd.conf

  PerlModule                 Apache::ForwardedFor
  PerlPostReadRequestHandler Apache::ForwardedFor

  PerlSetVar  ForwardedForAccept 192.168.1.1
  PerlAddVar  ForwardedForAccept 192.168.1.2

=head1 DESCRIPTION

We often want to run Apache behind a reverse proxy so that we
can delegate light-weight (static content) requests to a small
httpd and proxy heavy-weight requests (dynamic mod_perl generated
content) to a big httpd. This is a well known technique to overcome
the memory contraints of running a busy mod_perl site.

A small problem when doing this is that our "remote_ip" for the
backend (mod_perl) httpd is that of the front-end proxy'ing httpd.
This is not a good representation of the end client's real IP
address - making it difficult to implement IP-based access control
and tracking usage through your logs.

Before: 

 +--------+     +-------------+     +----------------+
 | Client | <-> | httpd/proxy | <-> | httpd/mod_perl |
 +--------+     +-------------+     +----------------+
  My IP           My IP               My IP
   2.3.4.5         2.9.1.2             192.168.1.2
                  remote_ip           remote_ip
                   2.3.4.5             2.9.1.2

After:

 +--------+     +-------------+     +----------------+
 | Client | <-> | httpd/proxy | <-> | httpd/mod_perl |
 +--------+     +-------------+     +----------------+
  My IP           My IP               My IP
   2.3.4.5         2.9.1.2             192.168.1.2
                  remote_ip           remote_ip
                   2.3.4.5             2.3.4.5

This program takes advantage of the existance of the X-Forwarded-For
or header which is automatically added by software such as mod_proxy and Squid.
Obviously you can imagine that if a savvy user sets their own X-Forwarded-For
header that they could potentially be considered coming from a trusted
IP.

To ensure some measure of security: 1 - make sure you can trust the 
httpd/proxy machine (ie/ its in your organization); 2 - set this module to 
accept X-Forwarded-For headers only from this machine.

From my understanding of the X-Forwarded-For header - each proxy server
will prepend the remote_ip to this header. That means that if the request passes
through several proxies we want to pick up only the last proxy's change - which
is the first IP found in this header.

=head1 USAGE

At this time you simply need to load the module and add it to the
PerlPostReadRequestHandler phase of your mod_perl-enabled httpd.

=head1 APACHE CONFIGURATION

The following can be set using either the B<PerlSetVar> or B<PerlAddVar> directives.

i.e.
  PerlSetVar ForwardedForDeny      127.0.0.1
  PerlAddVar ForwardedForAccept    192.168.1.1
  PerlAddVar ForwardedForAccept    192.168.1.2
  PerlAddVar ForwardedForAccept    192.168.1.3

=head2 ForwardedForAccept IPaddress

By using either the B<PerlSetVar> or B<PerlAddVar> directive you can



( run in 2.647 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )