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>
95 lines
6.9 KiB
Markdown
95 lines
6.9 KiB
Markdown
---
|
||
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
|
||
```
|