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 <allowedServerVariables>
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.
This commit is contained in:
cproudlock
2026-08-04 20:04:19 -04:00
parent 10ee3a3c58
commit 95b0b77c13
3 changed files with 61 additions and 6 deletions

View File

@@ -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 <allowedServerVariables> 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.

View File

@@ -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
# <allowedServerVariables> 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:*']))

View File

@@ -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 <allowedServerVariables> 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.
-->
<!-- SHOPDB-CLIENTIP-BEGIN
<rewrite>
<allowedServerVariables>
<add name="HTTP_X_FORWARDED_FOR" />
</allowedServerVariables>
<rules>
<rule name="Set X-Forwarded-For" stopProcessing="false">
<match url=".*" />