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>
This commit is contained in:
@@ -10,6 +10,8 @@ updated: 2026-05-24
|
||||
|
||||
Recipe для one-shot переноса Let's Encrypt certs из traefik `acme.json` (single-file ACME store) в IIS на Windows host с SNI multi-binding'ами. Использован при миграции [[../entities/ruvds-iis-host]] 2026-05-23 для 14 LE certs → 25 HTTPS hostnames bound via SNI. **Это не renewal pipeline**, а bootstrap-перенос — для long-term renewal recommend `win-acme` standalone на IIS host'е (см. footnote).
|
||||
|
||||
> **Update 2026-06-05 — SUPERSEDED для renewal'а.** Этот ручной PFX-перенос больше не на критическом пути: на RUVDS поднят постоянный self-renewing win-acme HTTP-01 pipeline — см. [[winacme-iis-owin-catchall-http01]]. RUVDS больше не зависит от домашнего traefik по сертификатам. Этот recipe оставлен как reference для bootstrap-сценария / если HTTP-01 недоступен.
|
||||
|
||||
## Когда применять
|
||||
|
||||
- Migration: traefik-on-A → IIS-on-B, certs нужны до того как ACME-validation с нового host'а станет возможной (DNS ещё указывает на старый host, HTTP-01 challenge bounce'нется).
|
||||
|
||||
94
.wiki/concepts/winacme-iis-owin-catchall-http01.md
Normal file
94
.wiki/concepts/winacme-iis-owin-catchall-http01.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
Reference in New Issue
Block a user