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.
141 lines
6.6 KiB
XML
141 lines
6.6 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!--
|
|
IIS site config for shopdb-flask via HttpPlatformHandler.
|
|
|
|
IIS launches waitress (a Windows-friendly WSGI server; gunicorn does NOT run
|
|
on Windows) and forwards requests to it on a private loopback port that IIS
|
|
assigns via %HTTP_PLATFORM_PORT%. One process serves both /api and the built
|
|
Vue SPA (frontend/dist), so no separate static site is needed.
|
|
|
|
Prerequisites on the box:
|
|
- HttpPlatformHandler IIS module installed
|
|
(https://www.iis.net/downloads/microsoft/httpplatformhandler)
|
|
- URL Rewrite module installed (only for the optional X-Forwarded-For rule)
|
|
- Python 3.14 + a venv at APP_ROOT\venv with requirements.txt + waitress
|
|
- Secrets live in APP_ROOT\.env (wsgi.py load_dotenv() reads it). Keep them
|
|
OUT of this file. Lock .env ACLs to the app pool identity + admins.
|
|
|
|
Replace APP_ROOT (C:\shopdb-flask below) with the real deploy path. The IIS
|
|
site's physical path MUST be APP_ROOT (where wsgi.py lives).
|
|
-->
|
|
<configuration>
|
|
<system.webServer>
|
|
|
|
<handlers>
|
|
<add name="httpplatformhandler" path="*" verb="*"
|
|
modules="httpPlatformHandler" resourceType="Unspecified" />
|
|
</handlers>
|
|
|
|
<httpPlatform
|
|
processPath="C:\shopdb-flask\venv\Scripts\waitress-serve.exe"
|
|
arguments="--port=%HTTP_PLATFORM_PORT% --host=127.0.0.1 --threads=8 --trusted-proxy=127.0.0.1 --trusted-proxy-headers=x-forwarded-for --trusted-proxy-count=1 wsgi:app"
|
|
stdoutLogEnabled="true"
|
|
stdoutLogFile="C:\shopdb-flask\logs\httpplatform"
|
|
startupTimeLimit="120"
|
|
startupRetryCount="3">
|
|
<environmentVariables>
|
|
<!-- FLASK_ENV MUST be production here or wsgi.py defaults to the dev
|
|
config (SQL echo, debug, wrong DB URL). Real secrets go in .env. -->
|
|
<environmentVariable name="FLASK_ENV" value="production" />
|
|
<environmentVariable name="PYTHONPATH" value="C:\shopdb-flask" />
|
|
<!-- Subpath method only: when this web.config sits in an IIS
|
|
Application (e.g. /ops) under an existing site instead of its own
|
|
site, tell the app its mount path. Must match the alias the
|
|
Application was created with AND the VITE_BASE_PATH the frontend
|
|
was built with ('/ops/'). Omit for the own-site method.
|
|
<environmentVariable name="MOUNT_PATH" value="/ops" />
|
|
-->
|
|
</environmentVariables>
|
|
</httpPlatform>
|
|
|
|
<!--
|
|
Forward the real client IP, so the audit log, the kiosk visitor-location
|
|
feature (IP -> business unit), the GE-Enforce IP allowlist and per-host
|
|
login rate limiting all see the caller rather than the loopback address
|
|
HttpPlatformHandler connects from.
|
|
|
|
IIS does not set X-Forwarded-For on its own. Without the rule below there
|
|
is no such header at all, and every client looks like 127.0.0.1 - so the
|
|
allowlist and the visitor-location lookup silently stop working.
|
|
|
|
ONLY CORRECT WHEN IIS IS DIRECTLY EXPOSED. It overwrites the header with
|
|
REMOTE_ADDR, which is what stops a client spoofing its own X-Forwarded-For.
|
|
Behind a reverse proxy (ARR, a load balancer) REMOTE_ADDR is the PROXY, so
|
|
this rule would destroy the real client IP - there, leave it disabled and
|
|
let the proxy set the header.
|
|
|
|
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, 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>
|
|
<rules>
|
|
<rule name="Set X-Forwarded-For" stopProcessing="false">
|
|
<match url=".*" />
|
|
<serverVariables>
|
|
<set name="HTTP_X_FORWARDED_FOR" value="{REMOTE_ADDR}" />
|
|
</serverVariables>
|
|
<action type="None" />
|
|
</rule>
|
|
</rules>
|
|
</rewrite>
|
|
SHOPDB-CLIENTIP-END -->
|
|
|
|
</system.webServer>
|
|
|
|
<!--
|
|
Installer downloads: serve /installers/* as IIS static files instead of
|
|
forwarding them to Flask. The handler above is path="*", so without this a
|
|
request for /installers/Foo.exe goes to waitress, which has no such route
|
|
(SPA fallback), and large binaries would stream through a Python thread.
|
|
|
|
This <location> clears the httpPlatformHandler for that one subpath and puts
|
|
the static file handler back, so IIS serves the bytes directly (kernel-mode,
|
|
range/resume, no Python thread held).
|
|
|
|
Requires a physical folder at APP_ROOT\installers (the site's physical path
|
|
is APP_ROOT). Drop the installer binaries there, e.g. robocopy them from the
|
|
classic wwwroot\installers. The stored installpath 'installers/Foo.exe' then
|
|
resolves to <mount>/installers/Foo.exe (e.g. /shopdb/installers/Foo.exe).
|
|
|
|
.exe/.msi are given an explicit MIME map; if the parent site has a Request
|
|
Filtering rule that denies executable extensions, also allow them there.
|
|
-->
|
|
<location path="installers">
|
|
<system.webServer>
|
|
<handlers>
|
|
<clear />
|
|
<add name="StaticFile" path="*" verb="*"
|
|
modules="StaticFileModule" resourceType="File"
|
|
requireAccess="Read" />
|
|
</handlers>
|
|
<staticContent>
|
|
<remove fileExtension=".exe" />
|
|
<mimeMap fileExtension=".exe" mimeType="application/octet-stream" />
|
|
<remove fileExtension=".msi" />
|
|
<mimeMap fileExtension=".msi" mimeType="application/octet-stream" />
|
|
</staticContent>
|
|
</system.webServer>
|
|
</location>
|
|
</configuration>
|