From 2aa7c3a6fa1cd31057878fe08f4357fd1de0ef90 Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Thu, 14 Feb 2019 19:02:31 +1000 Subject: [PATCH] =?UTF-8?q?Issue=20#3030501=20by=20alexpott,=20G=C3=A1bor?= =?UTF-8?q?=20Hojtsy,=20Berdir:=20[Symfony=204]=20Drupal\Core\StackMiddlew?= =?UTF-8?q?are\ReverseProxyMiddleware=20calls=20Symfony\Component\HttpFoun?= =?UTF-8?q?dation\Request::setTrustedHeaderName()=20which=20does=20not=20e?= =?UTF-8?q?xist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReverseProxyMiddleware.php | 43 +++++---- .../ReverseProxyMiddlewareTest.php | 88 +++++++++++++++---- sites/default/default.settings.php | 62 ++++++------- 3 files changed, 129 insertions(+), 64 deletions(-) diff --git a/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php b/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php index cf4cd89cb52c..ded2b92c31d4 100644 --- a/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php +++ b/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php @@ -58,24 +58,35 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = public static function setSettingsOnRequest(Request $request, Settings $settings) { // Initialize proxy settings. if ($settings->get('reverse_proxy', FALSE)) { - $ip_header = $settings->get('reverse_proxy_header', 'X_FORWARDED_FOR'); - $request::setTrustedHeaderName($request::HEADER_X_FORWARDED_FOR, $ip_header); - - $proto_header = $settings->get('reverse_proxy_proto_header', 'X_FORWARDED_PROTO'); - $request::setTrustedHeaderName($request::HEADER_X_FORWARDED_PROTO, $proto_header); - - $host_header = $settings->get('reverse_proxy_host_header', 'X_FORWARDED_HOST'); - $request::setTrustedHeaderName($request::HEADER_X_FORWARDED_HOST, $host_header); - - $port_header = $settings->get('reverse_proxy_port_header', 'X_FORWARDED_PORT'); - $request::setTrustedHeaderName($request::HEADER_X_FORWARDED_PORT, $port_header); - - $forwarded_header = $settings->get('reverse_proxy_forwarded_header', 'FORWARDED'); - $request::setTrustedHeaderName($request::HEADER_FORWARDED, $forwarded_header); - $proxies = $settings->get('reverse_proxy_addresses', []); if (count($proxies) > 0) { - $request::setTrustedProxies($proxies, Request::HEADER_X_FORWARDED_ALL | Request::HEADER_FORWARDED); + $deprecated_settings = [ + 'reverse_proxy_header' => Request::HEADER_X_FORWARDED_FOR, + 'reverse_proxy_proto_header' => Request::HEADER_X_FORWARDED_PROTO, + 'reverse_proxy_host_header' => Request::HEADER_X_FORWARDED_HOST, + 'reverse_proxy_port_header' => Request::HEADER_X_FORWARDED_PORT, + 'reverse_proxy_forwarded_header' => Request::HEADER_FORWARDED, + ]; + + $all = $settings->getAll(); + // Set the default value. This is the most relaxed setting possible and + // not recommended for production. + $trusted_header_set = Request::HEADER_X_FORWARDED_ALL | Request::HEADER_FORWARDED; + foreach ($deprecated_settings as $deprecated_setting => $bit_value) { + if (array_key_exists($deprecated_setting, $all)) { + @trigger_error(sprintf("The '%s' setting in settings.php is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use the 'reverse_proxy_trusted_headers' setting instead. See https://www.drupal.org/node/3030558", $deprecated_setting), E_USER_DEPRECATED); + $request::setTrustedHeaderName($bit_value, $all[$deprecated_setting]); + if ($all[$deprecated_setting] === NULL) { + // If the value is NULL do not trust the header. + $trusted_header_set &= ~$bit_value; + } + } + } + + $request::setTrustedProxies( + $proxies, + $settings->get('reverse_proxy_trusted_headers', $trusted_header_set) + ); } } } diff --git a/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php b/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php index 9350327c1003..ba9877ce3133 100644 --- a/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php +++ b/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Site\Settings; use Drupal\Core\StackMiddleware\ReverseProxyMiddleware; +use Drupal\Tests\Traits\ExpectDeprecationTrait; use Drupal\Tests\UnitTestCase; use Symfony\Component\HttpFoundation\Request; @@ -13,6 +14,7 @@ * @group StackMiddleware */ class ReverseProxyMiddlewareTest extends UnitTestCase { + use ExpectDeprecationTrait; /** * @var \Symfony\Component\HttpKernel\HttpKernelInterface|\PHPUnit_Framework_MockObject_MockObject @@ -47,30 +49,83 @@ public function testNoProxy() { * Tests that subscriber sets trusted headers when reverse proxy is set. * * @dataProvider reverseProxyEnabledProvider + */ + public function testReverseProxyEnabled($provided_settings, $expected_trusted_header_set) { + // Enable reverse proxy and add test values. + $settings = new Settings(['reverse_proxy' => 1] + $provided_settings); + $this->trustedHeadersAreSet($settings, $expected_trusted_header_set); + } + + /** + * Data provider for testReverseProxyEnabled. + */ + public function reverseProxyEnabledProvider() { + return [ + 'Proxy with default trusted headers' => [ + ['reverse_proxy_addresses' => ['127.0.0.2', '127.0.0.3']], + Request::HEADER_FORWARDED | Request::HEADER_X_FORWARDED_ALL, + ], + 'Proxy with AWS trusted headers' => [ + [ + 'reverse_proxy_addresses' => ['127.0.0.2', '127.0.0.3'], + 'reverse_proxy_trusted_headers' => Request::HEADER_X_FORWARDED_AWS_ELB, + ], + Request::HEADER_X_FORWARDED_AWS_ELB, + ], + 'Proxy with custom trusted headers' => [ + [ + 'reverse_proxy_addresses' => ['127.0.0.2', '127.0.0.3'], + 'reverse_proxy_trusted_headers' => Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST, + ], + Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST, + ], + ]; + } + + /** + * Tests that subscriber sets trusted headers when reverse proxy is set. * + * @dataProvider reverseProxyEnabledProviderLegacy * @group legacy - * - * @expectedDeprecation The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead. */ - public function testReverseProxyEnabled($provided_settings) { + public function testReverseProxyEnabledLegacy($provided_settings, $expected_trusted_header_set, array $expected_deprecations) { + $this->expectedDeprecations($expected_deprecations); // Enable reverse proxy and add test values. $settings = new Settings(['reverse_proxy' => 1] + $provided_settings); - $this->trustedHeadersAreSet($settings); + $this->trustedHeadersAreSet($settings, $expected_trusted_header_set); } /** * Data provider for testReverseProxyEnabled. */ - public function reverseProxyEnabledProvider() { + public function reverseProxyEnabledProviderLegacy() { return [ - [ + 'Proxy with deprecated custom headers' => [ [ - 'reverse_proxy_header' => 'X_FORWARDED_FOR_CUSTOMIZED', - 'reverse_proxy_proto_header' => 'X_FORWARDED_PROTO_CUSTOMIZED', - 'reverse_proxy_host_header' => 'X_FORWARDED_HOST_CUSTOMIZED', - 'reverse_proxy_port_header' => 'X_FORWARDED_PORT_CUSTOMIZED', - 'reverse_proxy_forwarded_header' => 'FORWARDED_CUSTOMIZED', 'reverse_proxy_addresses' => ['127.0.0.2', '127.0.0.3'], + 'reverse_proxy_host_header' => NULL, + 'reverse_proxy_forwarded_header' => NULL, + ], + // For AWS configuration forwarded and x_forwarded_host headers are not + // trusted. + Request::HEADER_X_FORWARDED_AWS_ELB, + [ + 'The \'reverse_proxy_host_header\' setting in settings.php is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use the \'reverse_proxy_trusted_headers\' setting instead. See https://www.drupal.org/node/3030558', + 'The \'reverse_proxy_forwarded_header\' setting in settings.php is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use the \'reverse_proxy_trusted_headers\' setting instead. See https://www.drupal.org/node/3030558', + 'The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', + ], + ], + 'Proxy with deprecated custom header' => [ + [ + 'reverse_proxy_addresses' => ['127.0.0.2', '127.0.0.3'], + 'reverse_proxy_forwarded_header' => NULL, + ], + // The forwarded header is not trusted which is the same as trusting all + // the x_forwarded headers. + Request::HEADER_X_FORWARDED_ALL, + [ + 'The \'reverse_proxy_forwarded_header\' setting in settings.php is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use the \'reverse_proxy_trusted_headers\' setting instead. See https://www.drupal.org/node/3030558', + 'The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', ], ], ]; @@ -85,18 +140,17 @@ public function reverseProxyEnabledProvider() { * * @param \Drupal\Core\Site\Settings $settings * The settings object that holds reverse proxy configuration. + * @param int $expected_trusted_header_set + * The expected bit value returned by + * \Symfony\Component\HttpFoundation\Request::getTrustedHeaderSet() */ - protected function trustedHeadersAreSet(Settings $settings) { + protected function trustedHeadersAreSet(Settings $settings, $expected_trusted_header_set) { $middleware = new ReverseProxyMiddleware($this->mockHttpKernel, $settings); $request = new Request(); $middleware->handle($request); - $this->assertSame($settings->get('reverse_proxy_header'), $request->getTrustedHeaderName($request::HEADER_X_FORWARDED_FOR)); - $this->assertSame($settings->get('reverse_proxy_proto_header'), $request->getTrustedHeaderName($request::HEADER_X_FORWARDED_PROTO)); - $this->assertSame($settings->get('reverse_proxy_host_header'), $request->getTrustedHeaderName($request::HEADER_X_FORWARDED_HOST)); - $this->assertSame($settings->get('reverse_proxy_port_header'), $request->getTrustedHeaderName($request::HEADER_X_FORWARDED_PORT)); - $this->assertSame($settings->get('reverse_proxy_forwarded_header'), $request->getTrustedHeaderName($request::HEADER_FORWARDED)); $this->assertSame($settings->get('reverse_proxy_addresses'), $request->getTrustedProxies()); + $this->assertSame($expected_trusted_header_set, $request->getTrustedHeaderSet()); } } diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 5bcecc267fab..ae64d2ffe89a 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -342,11 +342,10 @@ * configuration requires the IP addresses of all remote proxies to be * specified in $settings['reverse_proxy_addresses'] to work correctly. * - * Enable this setting to get Drupal to determine the client IP from - * the X-Forwarded-For header (or $settings['reverse_proxy_header'] if set). - * If you are unsure about this setting, do not have a reverse proxy, - * or Drupal operates in a shared hosting environment, this setting - * should remain commented out. + * Enable this setting to get Drupal to determine the client IP from the + * X-Forwarded-For header. If you are unsure about this setting, do not have a + * reverse proxy, or Drupal operates in a shared hosting environment, this + * setting should remain commented out. * * In order for this setting to be used you must specify every possible * reverse proxy IP address in $settings['reverse_proxy_addresses']. @@ -365,34 +364,35 @@ # $settings['reverse_proxy_addresses'] = ['a.b.c.d', ...]; /** - * Set this value if your proxy server sends the client IP in a header - * other than X-Forwarded-For. - */ -# $settings['reverse_proxy_header'] = 'X_CLUSTER_CLIENT_IP'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Proto. - */ -# $settings['reverse_proxy_proto_header'] = 'X_FORWARDED_PROTO'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Host. - */ -# $settings['reverse_proxy_host_header'] = 'X_FORWARDED_HOST'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Port. + * Reverse proxy trusted headers. + * + * Sets which headers to trust from your reverse proxy. + * + * Common values are: + * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL + * - \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED + * + * Note the default value of + * @code + * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED + * @endcode + * is not secure by default. The value should be set to only the specific + * headers the reverse proxy uses. For example: + * @code + * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL + * @endcode + * This would trust the following headers: + * - X_FORWARDED_FOR + * - X_FORWARDED_HOST + * - X_FORWARDED_PROTO + * - X_FORWARDED_PORT + * + * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL + * @see \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED + * @see \Symfony\Component\HttpFoundation\Request::setTrustedProxies */ -# $settings['reverse_proxy_port_header'] = 'X_FORWARDED_PORT'; +# $settings['reverse_proxy_trusted_headers'] = \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED; -/** - * Set this value if your proxy server sends the client protocol in a header - * other than Forwarded. - */ -# $settings['reverse_proxy_forwarded_header'] = 'FORWARDED'; /** * Page caching: -- GitLab