Problem/Motivation
Bug:
If you have multiple sites and if your Drupal is behind a reverse proxy, the matching site path is not resolved, the settings are not initialized, and you always end up on the install page (with wrong urls generated).
Why?
1) When the request is handled, one of the first function to be called is DrupalKernel->initializeSettings()
.
2) To initialize the settings, Drupal needs to determine the site path to read the settings.php
file so the functionDrupalKernel::findSitePath()
is called.
3) In this function, when you have multiple sites, the function Request->getHttpHost()
is called on the request (the http host is needed to match it with the mapping (dir => http host) you have made in the sites.php
file).
Problem:
1) The http host depends on the trusted proxies configuration (cf Symfony Request->getHttpHost()
code).
2) The trusted proxies are only set by the ReverseProxyMiddleware
middleware so they have not yet been set in the request. The container has not yet been initialized.
Note:
This cannot be resolved easily because there is a circular dependency (cf attached schema).
1) To find the site path, Drupal needs the valid http host (so the trusted proxies must have been set on the request).
2) To set the trusted proxies, the settings must have been initiliazed (because currently the reverse proxy settings are in the site settings.php
file).
3) To initialize the site settings, Drupal needs the site path.
My two cents:
1) Setting the request trusted proxies in a HttpKernel
middleware is a design error.
2) The reverse proxy settings can only be global and not by site. As long as Drupal depends on the http host to find the site, a configuration by site is not possible. I geuss that's ok for the vast majority of project using multiple sites and reverse proxy.
Proposed resolution
Ideas:
1) Set the trusted proxies on the Request as soon as possible. We need to make sure that the trusted proxies are set before any real usage of the Request. The safest solution is to do it at the very beginning of the request handle. If you look at Symfony doc about how to configure Symfony to work behind a reverse proxy (https://symfony.com/doc/current/deployment/proxies.html), they advise the user to set the trusted proxies directly in the front controller (so asap).
2) Deprecate the ReverseProxyMiddleware
class.
Proposed patch:
Now:
1) Introduce a new configuration file sites/reverse_proxy_settings.php
that behaves exactly like the current reverse proxy settings.
2) Introduce a new method DrupalKernel::setTrustedProxies()
that is called as soon as the request is handled.
3) The new method reads the new configuration file and set the trusted proxies on the request.
4) The new method uses the ReverseProxyMiddleware::setSettingsOnRequest()
method during the deprecation period (so the same configuration options are used and the behavior is exactly the same).
5) Deprecate the ReverseProxyMiddleware
class and trigger a deprecation notice when the reverse proxy settings still come from the site settings.php
file (this is the case when you have only one site).
Once the deprecation period is over:
1) Remove the ReverseProxyMiddleware
class.
2) Copy the code of the ReverseProxyMiddleware::setSettingsOnRequest()
method in the new DrupalKernel::setTrustedProxies()
method.
3) Remove the call to ReverseProxyMiddleware::setSettingsOnRequest()
in the install.core.inc
file as it will not be needed anymore.
Alternatives:
1) Maybe introducing a new configuration file is not the best solution and that the reverse proxy configuration could be in the existing sites.php
file directly?
2) Maybe we should remove all reverse proxy configuration from Drupal and and let the users manage the trusted proxy themselves in their front controller (like in Symfony projects).
Remaining tasks
1) Discuss the issue.
2) Review the proposed resolution and patch.
3) Eventually Write tests and update the documentation.
Going further:
Instead of creating a new specific file just for the reverse proxy settings, this could be an opportunity to create a global settings file that all sites settings would inherit. The settings of each site would be merged with the global ones. Those general settings could be used before the site path has been resolved for other purposes. It is already common to create a settings.common.php
file that you require in all the sites settings.php
files.