Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request ips wrong order when using a load balancer #115

Closed
peppeocchi opened this issue Nov 8, 2018 · 13 comments
Closed

Request ips wrong order when using a load balancer #115

peppeocchi opened this issue Nov 8, 2018 · 13 comments

Comments

@peppeocchi
Copy link

Hi, I recently upgrade one application from Laravel 5.2 to Laravel 5.7
Before the upgrade I was using this library (v3.3) with the configuration below

    'proxies' => '**',
...
    'headers' => [
        (defined('Illuminate\Http\Request::HEADER_FORWARDED') ? Illuminate\Http\Request::HEADER_FORWARDED : 'forwarded') => null,
        Illuminate\Http\Request::HEADER_CLIENT_IP    => 'X_FORWARDED_FOR',
        Illuminate\Http\Request::HEADER_CLIENT_HOST  => null,
        Illuminate\Http\Request::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
        Illuminate\Http\Request::HEADER_CLIENT_PORT  => 'X_FORWARDED_PORT',
    ]

After the upgrade, I added the TrustProxies middleware below

<?php

namespace App\Http\Middleware;

use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array
     */
    protected $proxies = '**';

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers = Request::HEADER_X_FORWARDED_AWS_ELB;
}

The issue I am having might not be related to this, but basically after the upgrade

$request->ip() // the ec2 ip
$request->ips() // [ec2 ip, client ip]

It seems the order or the ips is inverted, I should be getting first the client ip, then any other ip.

Is this a configuration issue on my side or is there anything else I am missing?

@fideloper
Copy link
Owner

Hi!

To my knowledge, nothing in this package would change that behavior. At the end of the day, this package's responsibility is purely calling $request->setTrustedProxies(). It's possible that affects something in the underlying Symfony classes (they also provide this functionality). However it might just be the order Symfony puts the Ip() if multiple are given anyway!

I'm guessing you'll get the same when calling the following (which I think the Laravel methods you used just wrap):

$request->getClientIp(); 
$request->getClientIps();

This might be a bug to file upstream in Symfony. The code I'm seeing in Symfony\Component\HttpFoundation is indeed grabbing the first IP:

    public function getClientIp()
    {
        $ipAddresses = $this->getClientIps();

        return $ipAddresses[0];
    }

@fideloper
Copy link
Owner

The cal to getClientIps() seems a little sketchy to me also:

public function getClientIps()
    {
        $ip = $this->server->get('REMOTE_ADDR');

        if (!$this->isFromTrustedProxy()) {
            return array($ip);
        }

        return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: array($ip);
    }

I'm not sure why that's calling self::HEADER_X_FORWARDED_FOR explicitly when there are options to use other headers. Could be a bug, but their code around this has always been obtuse.

@NFarrington
Copy link

This behaviour is expected when updating from TrustedProxy v3.3 to v4+, as the meaning of ** has been removed (1d09591#diff-04ebeb9e366e3ee7a4bd392a708e21bcL85), and made synonymous with * (9618897#diff-04ebeb9e366e3ee7a4bd392a708e21bcR76). This means from v4+, only the REMOTE_ADDR is trusted when using **, rather than all the proxy IPs in the chain.

The Symfony getClientIps() returns the "most trusted" proxy first, not the first IP in the chain:

     * In the returned array the most trusted IP address is first, and the
     * least trusted one last. The "real" client IP address is the last one,
     * but this is also the least trusted one. Trusted proxies are stripped.

To have getClientIp() return the original IP, all proxies in the chain must be trusted. To replicate the old behaviour of **, set $proxies = ['0.0.0.0/0', '2000:0:0:0:0:0:0:0/3'].

@mailtomsk
Copy link

This behaviour is expected when updating from TrustedProxy v3.3 to v4+, as the meaning of ** has been removed (1d09591#diff-04ebeb9e366e3ee7a4bd392a708e21bcL85), and made synonymous with * (9618897#diff-04ebeb9e366e3ee7a4bd392a708e21bcR76). This means from v4+, only the REMOTE_ADDR is trusted when using **, rather than all the proxy IPs in the chain.

The Symfony getClientIps() returns the "most trusted" proxy first, not the first IP in the chain:

     * In the returned array the most trusted IP address is first, and the
     * least trusted one last. The "real" client IP address is the last one,
     * but this is also the least trusted one. Trusted proxies are stripped.

To have getClientIp() return the original IP, all proxies in the chain must be trusted. To replicate the old behaviour of **, set $proxies = ['0.0.0.0/0', '2000:0:0:0:0:0:0:0/3'].

This is worked perfectly. Thanks a lot..

@barryvdh
Copy link
Contributor

Thanks. I had the same problem with Laravel Vapor.

@BadChoice
Copy link

This really helped!! It was driving me crazy as the api rating was not working but everything else did

@bytestream
Copy link

I think the README needs updating because otherwise this is painful...

  1. The code says ** is deprecated yet the README says to use it
  2. As pointed out above ** doesn't do what it says in the README, and you need to use the workaround mentioned

@fideloper
Copy link
Owner

fideloper commented Apr 26, 2020 via email

@bytestream
Copy link

I'd be happy to send a PR if you just want to replace ** with the above fix :) if you've got larger changes in mind then no worries!

@fideloper
Copy link
Owner

fideloper commented Apr 26, 2020 via email

@fideloper
Copy link
Owner

Took a first stab at a simpler/better readme.md but PR's welcome for formatting/explanation changes.

@peppeocchi
Copy link
Author

I did notice that when using the workaround, you don't get anymore the chain of ips but only the client ip. I found it really useful when you have multiple ips in the chain to see what the route was (example, Cloudflare -> CloudFront -> ELB -> EC2, I used to get at least 3 ips when calling "$request->ips()" and now I am only getting 1). Anybody else noticed that? And is there any other workaround to get back the chain of ips? Or should I just use $proxies = '*'; and then reverse the ips to get the client ip?

@NFarrington
Copy link

... you don't get anymore the chain of ips but only the client ip. I found it really useful when you have multiple ips in the chain to see what the route was (example, Cloudflare -> CloudFront -> ELB -> EC2)

Laravel's $request->ips() relies on the Symfony getClientIps() method, which strips any trusted proxies from the return value. For most people (I assume), the idea of knowing and trusting an intermediate proxy is that you then don't have to worry about that proxy and its IP address, as it's not relevant to the application's functionality.

Incidentally, I imagine this is why getClientIps() is marked with the comment Use this method carefully; you should use getClientIp() instead. - in most cases, if you've configured your trusted proxies correctly, getClientIp() will be the IP you want, and anything else could be (or, is even likely to be) a falsehood, e.g. a manually crafted X-Forwarded-For header from the end-user.

If you need all the IPs in the chain for logging/monitoring/curiosity, the basic route would be:

$ips = [];
foreach (explode(',', $request->headers->get('X_FORWARDED_FOR')) as $ip) {
    $ips[] = trim($ip);
}
$ips[] = $request->server->get('REMOTE_ADDR');

drakeford97 added a commit to drakeford97/Laravel-Bridge that referenced this issue May 25, 2022
LukeTowers added a commit to wintercms/storm that referenced this issue Aug 4, 2022
This reinstates the behaviour originally present in fideveloper/trustedproxy where setting ** as the value for app.trustedProxies would allow all proxies vs * which would only allow the most recent one in a chain of proxies (as determined by $_SERVER['REMOTE_ADDR']). See fideloper/TrustedProxy@6018dfb for when & why it was originally added.

The '**' wildcard was removed in v4 of that package (fideloper/TrustedProxy@1d09591) with no explanation and was never added back in when Laravel merged it into the core in laravel/framework#38295.

This causes problems for environments where you have multiple proxies in a chain (i.e. Amazon CloudFront in front of Amazon ELB). These problems are documented in fideloper/TrustedProxy#115 & fideloper/TrustedProxy#107, and spawned fideloper/TrustedProxy#142 & https://github.com/ge-tracker/laravel-vapor-trusted-proxies to resolve them.

Ultimately, this commit serves to reintroduce the original behaviour of fideveloper/trustproxies v3 and make it so that you can use `**` as the value for app.trustProxies in order to get the correct client IP address when running Winter on Laravel Vapor.
phoenix933 added a commit to phoenix933/laravel-bridge that referenced this issue Dec 3, 2023
pwony pushed a commit to pwony/vapor-core that referenced this issue Mar 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants