Files
admin/.wiki/concepts/winacme-iis-owin-catchall-http01.md
vitya 3c7ca3416b feat(iis-migration): win-acme HTTP-01 auto-renewal on RUVDS IIS
Stood up a permanent self-renewing Let's Encrypt pipeline on the RUVDS
IIS host, replacing the manual traefik acme.json -> PFX import and
closing the 2026-07-22 cert-expiry deadline (new 25-SAN cert valid to
2026-09-03, SYSTEM scheduled task renews 55 days before expiry).

Key obstacle: the MoreThenCms OWIN catch-all (owin:HandleAllRequests)
swallowed /.well-known/acme-challenge/. Solved by carving the challenge
path into a separate IIS application in a No-Managed-Code app pool, plus
patching win-acme's Web_Config.xml template to remove the inherited Owin
handler. Staging + prod validation green for all 25 hostnames; live TLS
smoke confirms the new cert is served (incl degraded maljarka/rimiz).

- scripts/iis-migration-to-ruvds/03-ruvds-winacme.ps1 (idempotent setup)
- scripts/iis-migration-to-ruvds/winacme-Web_Config.xml (patched template)
- .wiki/concepts/winacme-iis-owin-catchall-http01.md (recipe + gotchas)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:08:41 +03:00

95 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: win-acme HTTP-01 авто-renewal на IIS под OWIN-catch-all CMS
status: live
tags: [windows, iis, ruvds, letsencrypt, win-acme, acme, http-01, owin, cms, ops]
related: [[ruvds-iis-host]], [[traefik-acme-json-to-iis-cert-import]], [[windows-server-2025-core-bootstrap]], [[iis-migration-2026-05-19-postmortem]]
---
# win-acme HTTP-01 авто-renewal на IIS под OWIN-catch-all CMS
Постоянный self-renewing LE-pipeline на RUVDS IIS-хосте ([[ruvds-iis-host]]) для сайта `snolla`
(25 hostname через SNI). **Заменяет** ручной метод [[traefik-acme-json-to-iis-cert-import]]
(экспорт PFX из домашнего traefik `acme.json` + ручной `Import-PfxCertificate`) — тот делал
RUVDS зависимым от домашней машины по сертификатам и требовал ручного продления.
Поднято 2026-06-05, после full DNS cutover на RUVDS (HTTP-01 challenge до cutover'а отскакивал
на windows-source — LE не мог достучаться).
## Что развёрнуто
- **win-acme v2.2.9** в `C:\win-acme\` (скачан с GitHub releases, `x64.pluggable`).
- Один **SAN-cert на 25 hostname** в store `WebHosting`, установлен во все 25 `*:443:<host>` SNI-биндинга.
- **Scheduled task `win-acme-renew-snolla`** — daily 09:00 + random delay 4h, runs as **SYSTEM**,
`wacs.exe --renew --baseuri https://acme-v02.api.letsencrypt.org/`. Renewal due 55 дней до expiry.
- Idempotent setup-скрипт: `scripts/iis-migration-to-ruvds/03-ruvds-winacme.ps1` (фазы `download`/`app`/`probe`/`task`/`verify`).
## Главный gotcha: OWIN-catch-all жрёт `/.well-known/acme-challenge/`
Сайт = **MoreThenCms** на OWIN. В корневом `web.config`: `owin:HandleAllRequests=true` +
handler `Owin path="*"` + `runAllManagedModulesForAllRequests="true"`**весь** трафик уходит в
managed OWIN-pipeline. HTTP-01 токен (статический файл) отдаётся не статикой, а CMS → `404`/`500`/redirect.
LE-валидация падает (`Invalid response ...: 500`).
Снятие Owin-**handler**'а в дочернем `web.config` НЕ помогает — OWIN перехватывает на уровне
**модуля** (HandleAllRequests). Что НЕ сработало:
- web.config с `<rewrite><rules><clear/></rules>` — рерайта-то и нет, маршрутит managed-pipeline.
- web.config с `<handlers><remove name="Owin"/></handlers>` в обычном (managed) пуле — модуль всё равно жив.
### Рабочее решение: отдельное IIS-приложение в пуле «No Managed Code»
`.well-known/acme-challenge` вынесен в **отдельное IIS Application** под сайтом `snolla` с
app-pool'ом `acme-challenge`, у которого `managedRuntimeVersion=''` (**No Managed Code**). В таком
пуле .NET/OWIN не стартует вообще — токен отдаётся нативным `StaticFileModule`.
Нюанс: в дочернем web.config всё равно нужно **`<handlers><remove name="Owin"/></handlers>`** —
иначе унаследованный managed Owin-handler даёт `500` (managed handler в unmanaged пуле). Плюс
extensionless-mime (токены без расширения). Проверенный web.config:
```xml
<configuration>
<system.webServer>
<handlers><remove name="Owin" /></handlers>
<staticContent>
<remove fileExtension="." /><mimeMap fileExtension="." mimeType="text/plain" />
</staticContent>
<modules runAllManagedModulesForAllRequests="false" />
</system.webServer>
</configuration>
```
Результат: токен отдаётся `200` для **любого** Host — даже для деградировавших CMS-тенантов
(`maljarka.tandemmebel.ru`/`rimiz.ru`, которые на app-уровне отдают `502`/`404`), т.к. статика
обходит CMS-роутинг.
### Патч шаблона win-acme
win-acme при filesystem-валидации **перезаписывает** дочерний web.config своим шаблоном
`C:\win-acme\Web_Config.xml` (а при `CleanupFolders=true` потом удаляет токен+web.config). Дефолтный
шаблон делает `<staticContent><clear/>` + mime, но **не снимает Owin-handler** → снова `500`.
Durable-фикс = **пропатчить сам шаблон** `C:\win-acme\Web_Config.xml`, добавив `<remove name="Owin"/>`
+ `runAllManagedModulesForAllRequests="false"`. Трекаемая копия: `scripts/iis-migration-to-ruvds/winacme-Web_Config.xml`.
Тогда на каждом renewal win-acme сам кладёт рабочий web.config в (постоянное) unmanaged-приложение,
валидирует, чистит. Между renewal'ами папка пустая — трафика на неё нет.
## Процедура де-риска (повторяемо)
1. `--validationmode http-01 --validation filesystem` локальный probe токена через `localhost` + `Host:`-заголовок (обходит home-DPI middlebox).
2. **Staging** через `--baseuri https://acme-staging-v02.api.letsencrypt.org/` (НЕ `--test` — он включает интерактивные промпты, которые вешают non-interactive SSH). pemfiles store + `--installation none` — без правок IIS.
3. Прод: `--store certificatestore --installation iis --installationsiteid 1`.
## Грабли
- **`--test` ≠ staging-only.** `--test` тащит интерактив («Try in default browser?», «Quit?») → виснет под SSH. Для staging без интерактива — `--baseuri <staging>`.
- **pemfiles path должен существовать** заранее, иначе abort до валидации.
- **Scheduled task руками, не через win-acme.** `--notaskscheduler` при выпуске; таск создаётся отдельно как SYSTEM (win-acme-овский setup может спросить креды интерактивно). win-acme потом пишет «Scheduled task not configured yet» — косметика, ищет *свой* таск.
- win-acme renewal'ы раздельны по `--baseuri` (разные ConfigurationPath): staging-renewal не трогается прод-таском. Staging-renewal всё равно убран для чистоты.
## Команда боевого выпуска (reference)
```
C:\win-acme\wacs.exe --source iis --siteid 1 --validationmode http-01 --validation filesystem ^
--webroot C:\sites\snolla --store certificatestore --installation iis --installationsiteid 1 ^
--accepttos --emailaddress <ops-email> --force --notaskscheduler
```