From 95b0b77c1367ffb21d021acc8dec18fee27f5f5a Mon Sep 17 00:00:00 2001 From: cproudlock Date: Tue, 4 Aug 2026 20:04:19 -0400 Subject: [PATCH] Allow HTTP_X_FORWARDED_FOR at server level instead of declaring it per-application The stage 5 smoke test failure was a locked config section, but not one of the two the installer unlocks. A diagnostic collected from the server returned: HTTP 500.52 - URL Rewrite Module Error Module RewriteModule, Handler httpplatformhandler Error Code 0x80070021 Config Error: This configuration section cannot be used at this path. Config File: \\?\C:\shopdb-flask\web.config handlers and httpPlatform were both overrideMode Allow and locked false, so the unlock had worked. The section at fault was a third one, system.webServer/rewrite/allowedServerVariables, which ships overrideModeDefault="Deny". web.config declared locally for the X-Forwarded-For rule, and IIS rejects that declaration outright, failing the entire configuration before httpPlatformHandler ran. python was therefore never launched and C:\shopdb-flask\logs stayed empty, which reads as a dead application or a permissions fault and is neither. Unlocking the section would let every site on the machine declare arbitrary server variables. The installer now adds the single variable to the server-level allow list, checking first because a duplicate add is an error, and web.config no longer declares it. The rewrite rule is unchanged. Verified by applying the installer's own uncommenting to the template and parsing the result: one rewrite element, no allowedServerVariables, the rule still setting HTTP_X_FORWARDED_FOR from REMOTE_ADDR. shopdb-diagnose.py checked only the two sections the installer unlocks, so it could not have named this one; the IIS error page did. It now reports the lock state of the rewrite sections as well. --- deploy/windows/installer/shopdb-install.ps1 | 33 +++++++++++++++++++++ deploy/windows/shopdb-diagnose.py | 11 ++++++- deploy/windows/web.config | 23 ++++++++++---- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/deploy/windows/installer/shopdb-install.ps1 b/deploy/windows/installer/shopdb-install.ps1 index c0e80c4..16b1b43 100644 --- a/deploy/windows/installer/shopdb-install.ps1 +++ b/deploy/windows/installer/shopdb-install.ps1 @@ -1915,6 +1915,39 @@ If not, re-run matching how the application is published today: pass } finally { $ErrorActionPreference = $prevEap } } + # URL Rewrite: a rule may only set a server variable that is ALLOWED, and + # system.webServer/rewrite/allowedServerVariables ships with + # overrideModeDefault="Deny". An block in the app's + # own web.config is therefore refused outright - IIS answered 500.52 with + # 0x80070021, "this configuration section cannot be used at this path", + # before it ever reached httpPlatformHandler. python was never launched, the + # stdout log stayed empty, and the stage 5 smoke test reported it as a dead + # application when the config had been rejected wholesale. + # + # Allow the ONE variable at server level rather than unlocking the section. + # Unlocking would let every site on this machine declare arbitrary server + # variables; this grants exactly HTTP_X_FORWARDED_FOR. + if (($ClientIpSource -eq 'direct') -and (-not $WhatIfOnly)) { + $varName = 'HTTP_X_FORWARDED_FOR' + $section = 'system.webServer/rewrite/allowedServerVariables' + $prevEap = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + # Adding a duplicate entry is an error, so look before writing. + $current = (& $appcmd list config /section:$section 2>&1 | Out-String) + if ($current -match [regex]::Escape($varName)) { + Write-Log "$varName is already allowed for URL Rewrite" 'OK' + } else { + Write-Log "allowing $varName for URL Rewrite (server level)" + & $appcmd set config /section:$section "/+[name='$varName']" /commit:apphost 2>&1 | + ForEach-Object { Write-Log " $_" } + if ($LASTEXITCODE -ne 0) { + Write-Log " could not allow $varName - IIS will return 500.52 and the app will not start" 'WARN' + } + } + } finally { $ErrorActionPreference = $prevEap } + } + # Stage 2 stops the pool on an upgrade so its files can be replaced. Nothing # else starts it again, so the smoke test would fail against a stopped site # and report it as a broken install. diff --git a/deploy/windows/shopdb-diagnose.py b/deploy/windows/shopdb-diagnose.py index d2c2279..eaf63e5 100644 --- a/deploy/windows/shopdb-diagnose.py +++ b/deploy/windows/shopdb-diagnose.py @@ -257,7 +257,16 @@ def main(): report.block('modules: httpPlatformHandler present?', run([appcmd, 'list', 'modules'])) # overrideMode tells us whether the unlock actually took effect. - for section in ('system.webServer/handlers', 'system.webServer/httpPlatform'): + # + # allowedServerVariables is in this list because it caused a 500.52 that + # the first two sections could not explain: it is Deny by default, so an + # block in the app's web.config is rejected + # before httpPlatformHandler runs. Checking only the sections we unlock + # would have missed the one we do not. + for section in ('system.webServer/handlers', + 'system.webServer/httpPlatform', + 'system.webServer/rewrite/allowedServerVariables', + 'system.webServer/rewrite/rules'): report.block('lock state of %s' % section, run([appcmd, 'list', 'config', '/section:%s' % section, '/text:*'])) diff --git a/deploy/windows/web.config b/deploy/windows/web.config index 8d0a5c8..f365fcc 100644 --- a/deploy/windows/web.config +++ b/deploy/windows/web.config @@ -67,15 +67,28 @@ It ships DISABLED because it needs the URL Rewrite module; enabled without it, IIS returns HTTP 500.19 ("configuration section not well-formed"). + There is deliberately NO block below. Setting a + server variable requires that variable to be allowed, but the section + system.webServer/rewrite/allowedServerVariables ships with + overrideModeDefault="Deny", so declaring it in an application's own + web.config is refused outright: IIS answered 500.52 with error 0x80070021, + "this configuration section cannot be used at this path", BEFORE it ever + reached httpPlatformHandler - so python was never launched and the stdout + log stayed empty, which looks like an application fault and is not one. + + The installer instead allows the single variable at server level, which + grants exactly HTTP_X_FORWARDED_FOR rather than unlocking the section and + letting every site on the machine declare arbitrary server variables. + The installer handles both: -ClientIpSource direct installs URL Rewrite - from the bundle and enables this; -ClientIpSource proxy leaves it alone. - By hand: install URL Rewrite, then delete the two marker lines below. + from the bundle, allows the variable, and enables this; -ClientIpSource + proxy leaves it alone. By hand: install URL Rewrite, run + appcmd set config /section:system.webServer/rewrite/allowedServerVariables ^ + /+"[name='HTTP_X_FORWARDED_FOR']" /commit:apphost + then delete the two marker lines below. -->