From 1c4d62e867ba20a399d553dcbafb0c4ff5b5a588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Hoffmann?= Date: Sat, 1 Jun 2024 11:27:28 +0200 Subject: [PATCH 1/4] BUGFIX: add recursive directory creation in setup db command --- Classes/Command/SetupCommandController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Classes/Command/SetupCommandController.php b/Classes/Command/SetupCommandController.php index 89b3535..32ecb83 100644 --- a/Classes/Command/SetupCommandController.php +++ b/Classes/Command/SetupCommandController.php @@ -20,11 +20,11 @@ use Neos\Setup\Domain\CliEnvironment; use Neos\Setup\Domain\HealthcheckEnvironment; use Neos\Setup\Infrastructure\HealthChecker; -use Neos\Setup\RequestHandler\SetupCliRequestHandler; use Neos\Utility\Arrays; use Neos\Setup\Exception as SetupException; use Neos\Setup\Infrastructure\Database\DatabaseConnectionService; use Symfony\Component\Yaml\Yaml; +use Neos\Utility\Files; class SetupCommandController extends CommandController { @@ -185,6 +185,7 @@ private function writeSettings(string $filename, string $path, $settings): strin $previousSettings = []; } $newSettings = Arrays::setValueByPath($previousSettings, $path, $settings); + Files::createDirectoryRecursively(dirname($filename)); file_put_contents($filename, YAML::dump($newSettings, 10, 2)); return YAML::dump(Arrays::setValueByPath([], $path, $settings), 10, 2); } From 9c50a2dbfe96138325a3cdd43e1191a47d80e5ce Mon Sep 17 00:00:00 2001 From: Sebastian Kurfuerst Date: Wed, 5 Nov 2025 20:33:18 +0100 Subject: [PATCH 2/4] FEATURE: Detect if trusted proxies are set up correctly --- .../Healthcheck/TrustedProxiesHealthcheck.php | 207 ++++++++++++++++++ Configuration/Settings.yaml | 3 + 2 files changed, 210 insertions(+) create mode 100644 Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php diff --git a/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php b/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php new file mode 100644 index 0000000..a42e617 --- /dev/null +++ b/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php @@ -0,0 +1,207 @@ + mappingKey (clientIp, host, port, proto) or null for detection-only headers + * Priority is determined by array order for headers with the same mappingKey + */ + private const REVERSE_PROXY_HEADERS = [ + // Standard reverse proxy headers with mapping + 'X-Forwarded-For' => 'clientIp', + 'X-Forwarded-Host' => 'host', + 'X-Forwarded-Port' => 'port', + 'X-Forwarded-Proto' => 'proto', + 'X-Real-IP' => 'clientIp', + 'Forwarded' => null, // RFC 7239 - detection only + // Additional clientIp headers (priority order) + 'True-Client-IP' => 'clientIp', + 'X-Client-IP' => 'clientIp', + 'Client-IP' => 'clientIp', + // Cloud provider-specific headers (detection only) + 'CF-RAY' => null, + 'CF-IPCountry' => null, + 'X-Amzn-Trace-Id' => null, + 'X-Original-URL' => null, + 'X-ARR-ClientCert' => null, + 'X-Cloud-Trace-Context' => null, + 'X-Forwarded-Client-Cert' => null, + ]; + + public function __construct( + private ConfigurationManager $configurationManager, + ) { + } + + public function getTitle(): string + { + return 'Trusted Proxies Configuration'; + } + + public function execute(HealthcheckEnvironment $environment): Health + { + if ($environment->executionEnvironment instanceof WebEnvironment) { + $trustedProxiesConfig = $this->configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, 'Neos.Flow.http.trustedProxies'); + $configuredHeaders = $trustedProxiesConfig['headers'] ?? []; + $configuredProxies = $trustedProxiesConfig['proxies'] ?? []; + + if (is_string($configuredProxies)) { + $configuredProxies = array_map('trim', explode(',', $configuredProxies)); + } + + $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? null; + + // Check if any reverse proxy header is present + $detectedProxyHeaders = []; + foreach (self::REVERSE_PROXY_HEADERS as $header => $mappingKey) { + if (isset($_SERVER['HTTP_' . strtoupper(str_replace('-', '_', $header))])) { + $detectedProxyHeaders[] = $header; + } + } + + if (count($detectedProxyHeaders) > 0) { + $message = "Reverse proxy headers detected: " . implode(', ', $detectedProxyHeaders) . "

"; + } else { + $message = "No reverse proxy headers detected.

"; + } + + + if (!empty($detectedProxyHeaders)) { + // Reverse proxy headers detected + // -> config is OK ($isRemoteAddrTrusted==true) if the current REMOTE_ADDR matches any configured trusted proxy + $isProxyConfigured = !empty($configuredProxies); + $isRemoteAddrTrusted = false; + + if ($isProxyConfigured && $remoteAddr) { + $isRemoteAddrTrusted = self::matchesProxyPattern($remoteAddr, $configuredProxies); + } + + if (!$isProxyConfigured || !$isRemoteAddrTrusted) { + // Trusted proxies not configured or don't match REMOTE_ADDR + + if (!$isProxyConfigured) { + $message .= "Trusted proxies are not configured. "; + } else { + $message .= "The current REMOTE_ADDR {$remoteAddr} does not match any configured trusted proxies {${implode(',', $configuredProxies)}}. "; + } + + $message .= "You need to configure trusted proxies to ensure URLs can be properly built.

"; + $message .= "Configure via Settings.yaml:

"; + $message .= "
Neos:\n";
+                    $message .= "  Flow:\n";
+                    $message .= "    http:\n";
+                    $message .= "      trustedProxies:\n";
+
+                    if ($remoteAddr) {
+                        $message .= "        proxies: ['{$remoteAddr}']\n";
+                    } else {
+                        $message .= "        proxies: ['']\n";
+                    }
+
+                    // Generate headers configuration mapping based on detected headers
+                    $headersMapping = self::generateHeadersMapping($detectedProxyHeaders);
+                    if (!empty($headersMapping)) {
+                        $message .= "        headers:\n";
+                        foreach ($headersMapping as $key => $header) {
+                            $message .= "          {$key}: '{$header}'\n";
+                        }
+                    }
+
+                    $message .= "    
\n"; + + + $message .= "

Or use the FLOW_HTTP_TRUSTED_PROXIES environment variable.
"; + $message .= 'See Documentation on trusted proxies for further details.'; + + return new Health($message, Status::WARNING()); + } else { + // Trusted proxies properly configured + return new Health( + "Reverse proxy configuration appears correct.
" . + "Detected headers: " . implode(', ', $detectedProxyHeaders) . "
" . + "REMOTE_ADDR ({$remoteAddr}) is configured as a trusted proxy.", + Status::OK() + ); + } + } else { + // No reverse proxy headers detected + if (!empty($configuredProxies)) { + return new Health( + "No reverse proxy headers detected in the request, but trusted proxies are configured.

" . + "If you are not running behind a reverse proxy, you should remove the trusted proxies configuration.
" . + "Otherwise, ensure your reverse proxy is properly configured to send the expected headers.", + Status::WARNING() + ); + } else { + return new Health( + "No reverse proxy headers detected. Running in direct connection mode (no reverse proxy).
" . + "Trusted proxies configuration is not set, which is correct for this setup.", + Status::OK() + ); + } + } + } + + // Fallback for CLI environment + return new Health( + <<<'MSG' + If you are behind a reverse proxy, you need to configure trusted proxies, to ensure URLs can be + properly built. This is possible via Settings.yaml at Neos.Flow.http.trustedProxies, + or the FLOW_HTTP_TRUSTED_PROXIES environment variable. + + See https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/Http.html#trusted-proxies + for further details. + + You can also run the web-based setup wizard at /setup, which checks if trusted proxies are set up correctly. + MSG, + Status::UNKNOWN() + ); + } + + private static function matchesProxyPattern(string $remoteAddr, array $configuredProxies): bool + { + foreach ($configuredProxies as $ipPattern) { + if ($ipPattern === '*') { + return true; + } + if (IpUtility::cidrMatch($remoteAddr, $ipPattern)) { + return true; + } + } + return false; + } + + /** + * Generate headers Neos config mapping based on detected headers + */ + private static function generateHeadersMapping(array $detectedHeaders): array + { + $mapping = []; + + // Process headers in priority order (as defined in REVERSE_PROXY_HEADERS) + // First match wins for each mappingKey + foreach (self::REVERSE_PROXY_HEADERS as $header => $mappingKey) { + // Skip headers without a mapping key (detection only) or already mapped keys (as these then have higher priorities) + if ($mappingKey === null || isset($mapping[$mappingKey])) { + continue; + } + + if (in_array($header, $detectedHeaders, true)) { + $mapping[$mappingKey] = $header; + } + } + + return $mapping; + } +} diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 82559dd..f9bf1bf 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -16,6 +16,9 @@ Neos: doctrine: position: 'start 100' className: Neos\Setup\Infrastructure\Healthcheck\DoctrineHealthcheck + trustedProxies: + position: 'start 50' + className: Neos\Setup\Infrastructure\Healthcheck\TrustedProxiesHealthcheck # # The database drivers that are supported by migrations From 85d2885a1b225451917f9f59f8cbfee7b9cc81f2 Mon Sep 17 00:00:00 2001 From: Sebastian Kurfuerst Date: Thu, 6 Nov 2025 10:54:45 +0100 Subject: [PATCH 3/4] fix the styling of health check and fix a crash --- .../Healthcheck/TrustedProxiesHealthcheck.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php b/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php index a42e617..e301387 100644 --- a/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php +++ b/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php @@ -93,7 +93,7 @@ public function execute(HealthcheckEnvironment $environment): Health if (!$isProxyConfigured) { $message .= "Trusted proxies are not configured. "; } else { - $message .= "The current REMOTE_ADDR {$remoteAddr} does not match any configured trusted proxies {${implode(',', $configuredProxies)}}. "; + $message .= "The current REMOTE_ADDR {$remoteAddr} does not match any configured trusted proxies " . implode(',', $configuredProxies) . ". "; } $message .= "You need to configure trusted proxies to ensure URLs can be properly built.

"; @@ -121,7 +121,7 @@ public function execute(HealthcheckEnvironment $environment): Health $message .= " \n"; - $message .= "

Or use the FLOW_HTTP_TRUSTED_PROXIES environment variable.
"; + $message .= "Alternatively, set the FLOW_HTTP_TRUSTED_PROXIES={$remoteAddr} environment variable.
"; $message .= 'See Documentation on trusted proxies for further details.'; return new Health($message, Status::WARNING()); @@ -139,13 +139,13 @@ public function execute(HealthcheckEnvironment $environment): Health if (!empty($configuredProxies)) { return new Health( "No reverse proxy headers detected in the request, but trusted proxies are configured.

" . - "If you are not running behind a reverse proxy, you should remove the trusted proxies configuration.
" . + "If you are not running behind a reverse proxy, you should remove the trusted proxies configuration in Settings.yaml, path Neos.Flow.http.trustedProxies.proxies; and remove the environment variable FLOW_HTTP_TRUSTED_PROXIES.
" . "Otherwise, ensure your reverse proxy is properly configured to send the expected headers.", Status::WARNING() ); } else { return new Health( - "No reverse proxy headers detected. Running in direct connection mode (no reverse proxy).
" . + "No reverse proxy headers detected. Running in direct connection mode.
" . "Trusted proxies configuration is not set, which is correct for this setup.", Status::OK() ); From 5fc0b77f882ce75e02de039250ee581d7ca55158 Mon Sep 17 00:00:00 2001 From: Sebastian Kurfuerst Date: Sun, 9 Nov 2025 12:34:55 +0100 Subject: [PATCH 4/4] extend proxy detection header list --- .../Healthcheck/TrustedProxiesHealthcheck.php | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php b/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php index e301387..7138500 100644 --- a/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php +++ b/Classes/Infrastructure/Healthcheck/TrustedProxiesHealthcheck.php @@ -25,18 +25,35 @@ class TrustedProxiesHealthcheck implements HealthcheckInterface 'X-Forwarded-Proto' => 'proto', 'X-Real-IP' => 'clientIp', 'Forwarded' => null, // RFC 7239 - detection only + // Additional clientIp headers (priority order) 'True-Client-IP' => 'clientIp', 'X-Client-IP' => 'clientIp', 'Client-IP' => 'clientIp', - // Cloud provider-specific headers (detection only) + + // Cloudflare + 'CF-Connecting-IP' => 'clientIp', + 'CF-Visitor' => null, 'CF-RAY' => null, 'CF-IPCountry' => null, + + // AWS 'X-Amzn-Trace-Id' => null, - 'X-Original-URL' => null, - 'X-ARR-ClientCert' => null, + 'X-Amz-Cf-Id' => null, + 'CloudFront-Viewer-Address' => 'clientIp', + + // Google Cloud 'X-Cloud-Trace-Context' => null, - 'X-Forwarded-Client-Cert' => null, + + // Azure + 'X-Azure-ClientIP' => 'clientIp', + 'X-ARR-ClientIP' => 'clientIp', + + // Fastly / Other CDNs + 'Fastly-Client-IP' => 'clientIp', + 'X-Forwarded-Ssl' => null, + 'X-Original-Forwarded-For' => 'clientIp', + 'X-Original-Host' => 'host', ]; public function __construct(