Problem/Motivation
D7 had the ability to nominate an alternative HTTP header from which to extract the client IP e.g. several CDNs add a True-Client-IP header or similar:
https://git.drupalcode.org/project/drupal/-/blob/7.81/sites/default/defa...
D8/9 instead recommend this:
https://git.drupalcode.org/project/drupal/-/blob/9.2.1/sites/default/def...
* In order for this setting to be used you must specify every possible
* reverse proxy IP address in $settings['reverse_proxy_addresses'].
* If a complete list of reverse proxies is not available in your
* environment (for example, if you use a CDN) you may set the
* $_SERVER['REMOTE_ADDR'] variable directly in settings.php.
* Be aware, however, that it is likely that this would allow IP
* address spoofing unless more advanced precautions are taken.
However, the Symfony Request has already been initialised by the time that settings.php is parsed, so it's too late to change the REMOTE_ADDR as the Request has its own copy of the relevant values from the _SERVER superglobal, from which it extracts the client IP.
Therefore, following the instructions in settings.php does not work.
Steps to reproduce
Assign a test value to $_SERVER['REMOTE_ADDR'] variable directly in settings.php per the instructions.
Observe that this value is not in the Request's server property, and will not be returned by e.g.
\Drupal::requestStack()->getCurrentRequest()->getClientIp();
(This module can be useful for debugging proxy headers etc..: https://www.drupal.org/project/trusted_proxy_headers_debug ).
I believe this is because (from index.php):
$kernel = new DrupalKernel('prod', $autoloader);
$request = Request::createFromGlobals(); <== the Request hoovers up values from $_SERVER
$response = $kernel->handle($request); <== the Drupal Kernel parses settings.php here
$response->send();
$kernel->terminate($request, $response);
Proposed resolution
I'm not yet sure how a site should be configured to derive the Client IP from a value supplied by a CDN if the headers don't fit within what Symfony supports (e.g. FORWARDED / X-Forwarded-For etc..).
Remaining tasks
Figure out a solution.
User interface changes
n/a
API changes
?
Data model changes
n/a
Release notes snippet
tbc
Issue fork drupal-3223280
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
longwaveSymfony's docs suggest you should override HTTP_X_FORWARDED_* in index.php:
https://symfony.com/doc/4.4/deployment/proxies.html#custom-headers-when-...
Instead of doing this in PHP, you could also do this in the web server configuration, so the header gets translated correctly before PHP even sees it. This is still a docs bug though if it doesn't work as described.
Comment #3
mcdruid commentedhttps://www.drupal.org/project/reverse_proxy_header addresses this issue.
Looks like the developer / maintainer reached the same conclusion about settings.php being too late to manipulate header values in
$_SERVER.Should we do something about this in core other than just fixing the docs? What would we change the recommendation in the docs to?
It seems like a regression that D7 can do this cleanly (acknowledging the reason for the change is down to Symfony doing the heavy lifting here as opposed to core's own code).
Comment #4
willempje2 commentedI think you should change the value the other way around.
Do not change $_SERVER['REMOTE_ADDR'] but use the remote REMOTE_ADDR to set the reverse_proxy_addresses.
Like so: $settings['reverse_proxy_addresses'] = [$_SERVER['REMOTE_ADDR']];
Comment #5
mcdruid commentedDoing something like this:
...would often work okay I think, but it may not always work and could in fact be risky.
If just one proxy is involved in the CDN's handling of the request, the approach of adding the REMOTE_ADDR as a trusted proxy will probably work fine (this will be the CDN's IP), and Drupal will correctly derive the client IP from the X-Forwarded-For (XFF) header.
However, if more than one proxy is involved in the CDN's handling of the request (which is perhaps not typical, but I'd say not uncommon), doing this won't work.
Even worse, if it's possible to bypass the CDN and send requests directly to the origin (again not typical / ideal, but not unheard of in the real world), adding REMOTE_ADDR as a trusted proxy may open Drupal up to extracting a spoofed IP from the XFF header.
You could work around that by only adding REMOTE_ADDR as a trusted proxy if other conditions are satisfied (e.g. checking for other headers from the CDN). This is the sort of thing the existing comments probably mean when they say "this would allow IP address spoofing unless more advanced precautions are taken."
Generally this seems like a workaround, and a regression in terms of no longer being able to simply set up a configuration which says "this header from the CDN will contain the client IP".
Comment #6
mcdruid commentedAgain this is a workaround rather than an elegant solution, but if the CDN is supplying a client IP in an alternative HTTP header, and we can only alter the list of trusted proxies... so long as that client IP appears in the X-Forwarded-For header, we can add any IPs to the right of client IP as trusted proxies (in addition to the IP in REMOTE_ADDR).
It's actually very similar to the idea of marking the REMOTE_ADDR IP as a trusted proxy, but covers more scenarios.
I've done some basic testing of this approach within settings.php and it seems to work.
Whether this is something we should consider adding to core, and if so how... I'm not certain.
Comment #7
manarth commentedTo elaborate Drew's comment with code, here's an example implementation.
Comment #11
deviantintegral commentedI just got bit by the incorrect documentation on setting $_SERVER['REMOTE_ADDR'].
Given there are various workarounds, and https://www.drupal.org/project/reverse_proxy_header exists, I agree we should just fix the docs on this.
Comment #13
deviantintegral commentedI've opened an MR with a first pass at correcting the docs.
Comment #14
andypostComment #15
deviantintegral commentedComment #16
longwaveComment #17
deviantintegral commentedThe only existing docs I found are at https://www.drupal.org/node/425990 but it's also in a "To be migrated" section: https://www.drupal.org/to-be-migrated.
I imagine when docs are moved, redirects are created, so it's probably safe to link there and update it?
Comment #19
moshe weitzman commentedPlease lets not hold up useful doc changes for trivial improvements.
Comment #20
catchWe can add short note to https://www.drupal.org/node/425990 and link to that. It would have taken very little time for someone to do that or just confirm #17 was OK, instead this has been held up further by status pong.
Comment #21
sboto commentedPlease merge the merge request of 'deviantintegral' after all this time the wrong comment still exists.
https://git.drupalcode.org/project/drupal/-/merge_requests/3218
Comment #22
aliartos commentedBased on https://symfony.com/doc/current/deployment/proxies.html we can add a proposed configuration:
$settings['reverse_proxy_addresses'] = ['127.0.0.1','REMOTE_ADDR'];
Comment #23
mcdruid commentedAdding REMOTE_ADDR as a trusted proxy may work in some cases, but it can also cause problems such as making it possible to spoof the client IP via X-Forwarded-For (per #5 and the existing cautionary note in the comments).