From 60bd068d664864c03adf44e88896fa24a7c2e00a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Sep 2024 14:33:41 +0200 Subject: [PATCH] [FrameworkBundle][HttpKernel] Add support for `SYMFONY_TRUSTED_PROXIES`, `SYMFONY_TRUSTED_HEADERS`, `SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER` and `SYMFONY_TRUSTED_HOSTS` env vars --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 17 ++++++------- .../FrameworkExtension.php | 25 +++---------------- .../DependencyInjection/ConfigurationTest.php | 11 +++----- src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + src/Symfony/Component/HttpKernel/Kernel.php | 25 ++++++++++++++++--- .../Component/HttpKernel/Tests/KernelTest.php | 19 ++++++++++++++ 7 files changed, 57 insertions(+), 42 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index f6fb34737cd56..e9a7186a4165e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -16,6 +16,7 @@ CHANGELOG * Deprecate `session.sid_length` and `session.sid_bits_per_character` config options * Add the ability to use an existing service as a lock/semaphore resource * Add support for configuring multiple serializer instances via the configuration + * Add support for `SYMFONY_TRUSTED_PROXIES`, `SYMFONY_TRUSTED_HEADERS`, `SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER` and `SYMFONY_TRUSTED_HOSTS` env vars 7.1 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 0914a0d22789b..aeb36ca5f551c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -90,7 +90,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->scalarNode('trust_x_sendfile_type_header') ->info('Set true to enable support for xsendfile in binary file responses.') - ->defaultFalse() + ->defaultValue('%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%') ->end() ->scalarNode('ide')->defaultValue($this->debug ? '%env(default::SYMFONY_IDE)%' : null)->end() ->booleanNode('test')->end() @@ -108,26 +108,23 @@ public function getConfigTreeBuilder(): TreeBuilder ->prototype('scalar')->end() ->end() ->arrayNode('trusted_hosts') - ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() + ->beforeNormalization()->ifString()->then(static fn ($v) => $v ? [$v] : [])->end() ->prototype('scalar')->end() + ->defaultValue(['%env(default::SYMFONY_TRUSTED_HOSTS)%']) ->end() ->variableNode('trusted_proxies') ->beforeNormalization() ->ifTrue(fn ($v) => 'private_ranges' === $v || 'PRIVATE_SUBNETS' === $v) ->then(fn () => IpUtils::PRIVATE_SUBNETS) ->end() + ->defaultValue(['%env(default::SYMFONY_TRUSTED_PROXIES)%']) ->end() ->arrayNode('trusted_headers') ->fixXmlConfig('trusted_header') ->performNoDeepMerging() - ->defaultValue(['x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto']) - ->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end() - ->enumPrototype() - ->values([ - 'forwarded', - 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix', - ]) - ->end() + ->beforeNormalization()->ifString()->then(static fn ($v) => $v ? [$v] : [])->end() + ->prototype('scalar')->end() + ->defaultValue(['%env(default::SYMFONY_TRUSTED_HEADERS)%']) ->end() ->scalarNode('error_controller') ->defaultValue('error_controller') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 17c779276c379..8011c964f7fcb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -313,14 +313,14 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']); - $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); + $container->setParameter('kernel.trusted_hosts', [0] === array_keys($config['trusted_hosts']) ? $config['trusted_hosts'][0] : $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); $container->setParameter('kernel.error_controller', $config['error_controller']); if (($config['trusted_proxies'] ?? false) && ($config['trusted_headers'] ?? false)) { - $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); - $container->setParameter('kernel.trusted_headers', $this->resolveTrustedHeaders($config['trusted_headers'])); + $container->setParameter('kernel.trusted_proxies', \is_array($config['trusted_proxies']) && [0] === array_keys($config['trusted_proxies']) ? $config['trusted_proxies'][0] : $config['trusted_proxies']); + $container->setParameter('kernel.trusted_headers', [0] === array_keys($config['trusted_headers']) ? $config['trusted_headers'][0] : $config['trusted_headers']); } if (!$container->hasParameter('debug.file_link_format')) { @@ -3114,25 +3114,6 @@ private function registerHtmlSanitizerConfiguration(array $config, ContainerBuil } } - private function resolveTrustedHeaders(array $headers): int - { - $trustedHeaders = 0; - - foreach ($headers as $h) { - $trustedHeaders |= match ($h) { - 'forwarded' => Request::HEADER_FORWARDED, - 'x-forwarded-for' => Request::HEADER_X_FORWARDED_FOR, - 'x-forwarded-host' => Request::HEADER_X_FORWARDED_HOST, - 'x-forwarded-proto' => Request::HEADER_X_FORWARDED_PROTO, - 'x-forwarded-port' => Request::HEADER_X_FORWARDED_PORT, - 'x-forwarded-prefix' => Request::HEADER_X_FORWARDED_PREFIX, - default => 0, - }; - } - - return $trustedHeaders; - } - public function getXsdValidationBasePath(): string|false { return \dirname(__DIR__).'/Resources/config/schema'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 73e6c20256fdf..c569a852d93b3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -704,19 +704,16 @@ protected static function getBundleDefaultConfig() return [ 'http_method_override' => false, 'handle_all_throwables' => true, - 'trust_x_sendfile_type_header' => false, + 'trust_x_sendfile_type_header' => '%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%', 'ide' => '%env(default::SYMFONY_IDE)%', 'default_locale' => 'en', 'enabled_locales' => [], 'set_locale_from_accept_language' => false, 'set_content_language_from_locale' => false, 'secret' => 's3cr3t', - 'trusted_hosts' => [], - 'trusted_headers' => [ - 'x-forwarded-for', - 'x-forwarded-port', - 'x-forwarded-proto', - ], + 'trusted_hosts' => ['%env(default::SYMFONY_TRUSTED_HOSTS)%'], + 'trusted_proxies' => ['%env(default::SYMFONY_TRUSTED_PROXIES)%'], + 'trusted_headers' => ['%env(default::SYMFONY_TRUSTED_HEADERS)%'], 'csrf_protection' => [ 'enabled' => false, ], diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 1f1dfcd81225e..1fc103b48dc1a 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Remove `@internal` flag and add `@final` to `ServicesResetter` * Add support for `SYMFONY_DISABLE_RESOURCE_TRACKING` env var + * Add support for configuring trusted proxies/headers/hosts via env vars 7.1 --- diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 38a8c330c957b..7e8b002079c10 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -393,7 +393,7 @@ protected function initializeContainer(): void $class = $this->getContainerClass(); $buildDir = $this->warmupDir ?: $this->getBuildDir(); $skip = $_SERVER['SYMFONY_DISABLE_RESOURCE_TRACKING'] ?? ''; - $skip = filter_var($skip, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) ?? explode(',', $skip); + $skip = filter_var($skip, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) ?? explode(',', $skip); $cache = new ConfigCache($buildDir.'/'.$class.'.php', $this->debug, null, \is_array($skip) && ['*'] !== $skip ? $skip : ($skip ? [] : null)); $cachePath = $cache->getPath(); @@ -745,11 +745,30 @@ private function preBoot(): ContainerInterface $container = $this->container; if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts = $container->getParameter('kernel.trusted_hosts')) { - Request::setTrustedHosts($trustedHosts); + Request::setTrustedHosts(\is_array($trustedHosts) ? $trustedHosts : preg_split('/\s*+,\s*+(?![^{]*})/', $trustedHosts)); } if ($container->hasParameter('kernel.trusted_proxies') && $container->hasParameter('kernel.trusted_headers') && $trustedProxies = $container->getParameter('kernel.trusted_proxies')) { - Request::setTrustedProxies(\is_array($trustedProxies) ? $trustedProxies : array_map('trim', explode(',', $trustedProxies)), $container->getParameter('kernel.trusted_headers')); + $trustedHeaders = $container->getParameter('kernel.trusted_headers'); + + if (\is_string($trustedHeaders)) { + $trustedHeaders = array_map('trim', explode(',', $trustedHeaders)); + } + + if (\is_array($trustedHeaders)) { + $trustedHeaderSet = 0; + + foreach ($trustedHeaders as $header) { + if (!\defined($const = Request::class.'::HEADER_'.strtr(strtoupper($header), '-', '_'))) { + throw new \InvalidArgumentException(\sprintf('The trusted header "%s" is not supported.', $header)); + } + $trustedHeaderSet |= \constant($const); + } + } else { + $trustedHeaderSet = $trustedHeaders ?? (Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO); + } + + Request::setTrustedProxies(\is_array($trustedProxies) ? $trustedProxies : array_map('trim', explode(',', $trustedProxies)), $trustedHeaderSet); } return $container; diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index e379e41bf5900..df4608a8e08d7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -523,6 +523,25 @@ public function getContainerClass(): string $this->assertMatchesRegularExpression('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*TestDebugContainer$/', $kernel->getContainerClass()); } + public function testTrustedParameters() + { + $kernel = new CustomProjectDirKernel(function (ContainerBuilder $container) { + $container->setParameter('kernel.trusted_hosts', '^a{2,3}.com$, ^b{2,}.com$'); + $container->setParameter('kernel.trusted_proxies', 'a,b'); + $container->setParameter('kernel.trusted_headers', 'x-forwarded-for'); + }); + $kernel->boot(); + + try { + $this->assertSame(['{^a{2,3}.com$}i', '{^b{2,}.com$}i'], Request::getTrustedHosts()); + $this->assertSame(['a', 'b'], Request::getTrustedProxies()); + $this->assertSame(Request::HEADER_X_FORWARDED_FOR, Request::getTrustedHeaderSet()); + } finally { + Request::setTrustedHosts([]); + Request::setTrustedProxies([], 0); + } + } + /** * Returns a mock for the BundleInterface. */