wiki(ingest): RUVDS IIS migration + daily backup pipeline

- entities/ruvds-iis-host (NEW) — 80.64.31.36, Win Server 2025 Core,
  25 SNI HTTPS bindings, 2/24 hostnames DNS-flipped
- sources/iis-migration-to-ruvds-2026-05-23 (NEW) — chronology,
  SSH/scp pivot после home-ISP outbound 445 block
- sources/ruvds-backup-daily-kreknin-2026-05-24 (NEW) — rclone+SFTP
  SYSTEM task daily 04:30, ntfy общий канал
- concepts/traefik-acme-json-to-iis-cert-import (NEW) — PFX + SNI
  recipe
- concepts/windows-server-2025-core-bootstrap — SMB deprecate,
  HTTP middlebox warning, HTTP/2 note; backup/cert open-Qs закрыты
- entities/windows-recovery-host — partial-cutover state,
  imgproxy SPOF carve-out, tandemmebel indefinitely здесь
- overview / index / log — catalog refresh

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 23:31:14 +03:00
parent b7b8e27a27
commit 22786e1865
9 changed files with 492 additions and 8 deletions

View File

@@ -0,0 +1,124 @@
---
title: Экспорт LE certs из traefik acme.json в IIS (PFX + SNI bindings)
type: concept
tags: [traefik, iis, letsencrypt, certs, pfx, sni, windows]
sources: [../sources/iis-migration-to-ruvds-2026-05-23.md]
updated: 2026-05-24
---
# traefik `acme.json` → IIS cert import
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).
## Когда применять
- Migration: traefik-on-A → IIS-on-B, certs нужны до того как ACME-validation с нового host'а станет возможной (DNS ещё указывает на старый host, HTTP-01 challenge bounce'нется).
- Bootstrap пилотного IIS-сервера с готовыми certs для smoke testing **до** DNS swap'а.
**Не применять для regular renewal** — каждый renewal цикл traefik будет генерировать новый cert, и manual re-export не масштабируется. После full DNS swap → `win-acme` HTTP-01 на IIS-side directly.
## Входные данные
- `acme.json` — обычно в `<traefik-data>/letsencrypt/acme.json`. Содержит JSON с массивом `Certificates[].domain.main`, `.sans`, `.certificate` (base64 PEM bundle), `.key` (base64 PEM private key).
- Target IIS host с PowerShell + OpenSSL (для cert conversion) + access to `Cert:\LocalMachine\My` store.
## Recipe (per cert)
### 1. Распаковать пару PEM из acme.json
```powershell
$acme = Get-Content C:\path\to\acme.json | ConvertFrom-Json
# Resolver key — обычно "letsencrypt" или имя из traefik.yml
$resolver = $acme.letsencrypt
foreach ($cert in $resolver.Certificates) {
$domain = $cert.domain.main
$sans = $cert.domain.sans # может быть $null
$safeName = $domain -replace '\*','wildcard'
[System.Convert]::FromBase64String($cert.certificate) | Set-Content "C:\temp\$safeName.crt" -AsByteStream
[System.Convert]::FromBase64String($cert.key) | Set-Content "C:\temp\$safeName.key" -AsByteStream
}
```
### 2. Convert PEM pair → PFX (OpenSSL)
```powershell
$pfxPass = 'pfximport' # temp passphrase, only for Import step
& openssl pkcs12 -export `
-inkey "C:\temp\$safeName.key" `
-in "C:\temp\$safeName.crt" `
-out "C:\temp\$safeName.pfx" `
-name $domain `
-password "pass:$pfxPass"
```
### 3. Import PFX → `Cert:\LocalMachine\My`
```powershell
$securePass = ConvertTo-SecureString $pfxPass -AsPlainText -Force
$imported = Import-PfxCertificate `
-FilePath "C:\temp\$safeName.pfx" `
-CertStoreLocation 'Cert:\LocalMachine\My' `
-Password $securePass
# $imported.Thumbprint — нужен для следующего шага
```
### 4. SNI binding в IIS (per hostname)
Один cert может покрывать несколько SANs — для каждого hostname создать **separate binding** с **SslFlags=1 (SNI)**:
```powershell
$allHosts = @($domain) + @($sans | Where-Object { $_ })
foreach ($hostname in $allHosts) {
# 4a. Create binding without cert
New-WebBinding -Name 'snolla' `
-IPAddress '*' -Port 443 -Protocol 'https' `
-HostHeader $hostname -SslFlags 1 # 1 = SNI
# 4b. Attach cert via thumbprint (нужен netsh-стиль через IIS:\)
$binding = Get-WebBinding -Name 'snolla' -Port 443 -HostHeader $hostname
$binding.AddSslCertificate($imported.Thumbprint, 'My')
}
```
> **SslFlags=1 обязателен.** Без SNI один port 443 = один cert per IP — Windows откажется bind'ить второй cert на тот же `*:443`. С SNI можно навесить ~unlimited hostnames на один `*:443`.
## Verification
```powershell
# Список bindings:
Get-WebBinding -Port 443 | Format-Table protocol, bindingInformation, sslFlags
# Сертификат за binding:
$binding.attributes['certificateHash'].Value # thumbprint
$binding.attributes['certificateStoreName'].Value # "My"
# External smoke (с другого host'а — home middlebox может mangle Host header):
curl -k --resolve "$hostname:443:80.64.31.36" "https://$hostname/" -I
# Должен вернуть chain LE R13 + correct Subject CN
```
HTTP/2 negotiate'ится auto (IIS 10 + Windows Server 2019+ → ALPN supported из коробки).
## Gotchas
- **`acme.json` permission** на Linux: `chmod 600`, traefik не запустится с другим. На Windows copy-out для conversion'а — нужны Admin perms.
- **PEM bundle order** в acme.json: `certificate` поле содержит leaf + chain (Let's Encrypt R13 + ISRG Root X1). OpenSSL pkcs12 импортит весь bundle корректно — IIS отдаст полный chain.
- **PFX password** — только для transit между PEM и cert store, после import не используется. `pfximport` (или любой placeholder) — OK; не путать с pass для encrypted PFX-on-disk storage.
- **HostHeader case-sensitivity** в `New-WebBinding`: IIS lowercase'ит автоматически, но скрипт принимай canonical lowercase.
- **Wildcard SANs:** если cert `*.example.com` — нужно отдельный binding для каждого конкретного subdomain'а (Windows не делает wildcard matching на binding'ах автоматически). Либо использовать default-cert на listener'е (`netsh http add sslcert`) — но это break'нет multi-cert SNI scenario.
- **Cert duplicates:** повторный import того же cert'а создаёт duplicate в `Cert:\LocalMachine\My`. Idempotency — check thumbprint exists перед import'ом.
- **`netsh http show sslcert`** — для low-level audit что binding'и реально привязаны (PowerShell `Get-WebBinding` иногда показывает stale state после ручных правок IIS Manager'ом).
## Limits / когда recipe мал
- **>30 hostnames** — `Import-PfxCertificate` + `New-WebBinding` цикл становится медленным (~5 sec/cert). Batch'ить через `appcmd add binding /commit:apphost`.
- **TLS 1.3** — IIS 10 на Server 2022+ supports; на Server 2019 — Edge case. Не зависит от recipe — от Windows feature.
- **Renewal** — этот recipe **не покрывает**. Variant A (win-acme HTTP-01 standalone) предпочтительнее, см. [[../sources/iis-migration-to-ruvds-2026-05-23]] Outstanding (post-soak).
## Cross-refs
- Use case: [[../sources/iis-migration-to-ruvds-2026-05-23]] Phase 6
- Source acme.json расположение: traefik-on-windows-host setup, см. [[traefik-on-windows-docker-desktop]]
- Long-term replacement: `win-acme` (wacs.exe) standalone — пока не documented as concept; reference https://www.win-acme.com/
- Bootstrap-host где recipe был применён: [[../entities/ruvds-iis-host]]

View File

@@ -9,6 +9,8 @@ related: [[iis-migration-2026-05-19-postmortem]], [[recovery-architecture-snapsh
Из коробки на RUVDS (2GB RAM, 30GB HDD, Win Server 2025 Core, RDP-only) машина **не готова** ни принимать файлы по SMB, ни хостить IIS — стандартный сценарий «купил, RDP'нулся, robocopy'нул» не работает. Документ фиксирует дефолтное состояние, default-blockers, и матрицу transfer-методов для переноса content'а с windows-recovery-host.
> **Update 2026-05-24 (post-migration):** SMB transfer-метод **deprecated** — outbound TCP/445 блокирует residential RU ISP (стандартная анти-worm политика). Canonical transfer-метод = **SSH/scp** (см. §«Update 2026-05-24» ниже). HTTP smoke с home-network также unreliable — DPI/transparent proxy mangles Host header.
## Дефолтное состояние fresh Win Server 2025 Core
| Компонент | Дефолт | Нужно для IIS-host'инга |
@@ -48,7 +50,7 @@ Test-NetConnection -ComputerName <ruvds-ip> -Port 445
| **SFTP** (OpenSSH server feature) | `Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0` + start sshd + FW 22 | works с любого Linux/Mac source; ключи безопасно | extra service surface; sshd default config иногда permissive |
| **HTTP PUT через временный traefik+webdav** | overkill для bootstrap | reusable для последующих deploy'ев | сначала нужен IIS/nginx running — но IIS ещё не deployed → chicken-and-egg |
**Recommendation для IIS migration:** SMB inbound с source-IP whitelist. Самый быстрый для 11 sites × ~1 GB; native robocopy /Z поддерживает interrupt-resume; FW rule убирается после migration cutover.
**~~Recommendation для IIS migration: SMB inbound с source-IP whitelist~~** — **deprecated 2026-05-24**, см. §«Update 2026-05-24» ниже. Текущий canonical — **SSH/scp**.
```powershell
# На RUVDS (RDP-сессия, PowerShell):
@@ -96,9 +98,68 @@ net use \\<ruvds-ip>\sites /delete
- **LE certs:** если IIS будет terminate'ить TLS напрямую — `win-acme` (wacs.exe) standalone-friendly на Core. Альтернатива — traefik-on-RUVDS перед IIS, но это лишняя layer для 11 sites.
- **Backup strategy для RUVDS:** unsolved — RUVDS native snapshots = VDS-snapshot-level, не file-level. Daily rsync sites+IIS config → kreknin (расширить `vds-backup-rsync-kreknin` на источник RUVDS) — отдельная chore-task после успешного cutover.
## Update 2026-05-24 — SMB deprecate + HTTP middlebox + HTTP/2
### SMB deprecated — home-ISP блокирует outbound 445
После выполнения bootstrap-чеклиста выше (FW 445 + SMB share созданы на RUVDS) **TCP/445 всё равно не reachable с source**. Root cause — residential RU ISP блокируют outbound 445 (стандартная анти-worm политика). FW scoping на RUVDS-стороне корректен, проблема на source side и **не fixable** без VPN.
> **Canonical transfer-метод теперь — SSH/scp.** Pivot zaprotokolirovan в [[../sources/iis-migration-to-ruvds-2026-05-23]] Phase 2-3.
Setup (на RUVDS):
```powershell
# OpenSSH server feature (если ещё не установлен):
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
# FW 22 scoped к source IP (не глобально):
New-NetFirewallRule -DisplayName "ssh-from-source" -Direction Inbound `
-Protocol TCP -LocalPort 22 -RemoteAddress <source-public-ip> -Action Allow
# Deploy pubkey для administrators (правильный ACL обязателен):
$pub = "ssh-ed25519 AAAA... source-comment"
Set-Content C:\ProgramData\ssh\administrators_authorized_keys $pub
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r `
/grant 'SYSTEM:(F)' /grant 'BUILTIN\Administrators:(F)'
```
Source-side transfer:
```powershell
scp -i ~/.ssh/<key> -r C:\sites\snolla `
Administrator@<ruvds-ip>:C:/sites/
```
8.66 GB / 44725 files за ~25 мин на 5 MB/s home uplink. Не parallel, но resume через `scp -r` после прерывания не работает — нужен `rsync` (нет на Core) или restart с нуля. Для production cutover это OK; для multi-GB content с unreliable link — `rclone copy` (см. [[../sources/ruvds-backup-daily-kreknin-2026-05-24]]).
После cutover — `Remove-NetFirewallRule -DisplayName 'ssh-from-source'`, `Remove-Item C:\ProgramData\ssh\administrators_authorized_keys`.
### HTTP middlebox в home network mangles Host header
Local smoke testing с home network через external IP `curl -H "Host: real-hostname.example" http://<ruvds-ip>/` **возвращает default catch-all content**, не per-tenant. Inside RUVDS (loopback) — корректно. С другой сети (VDS, mobile uplink) — корректно. SSH tunnel `localhost:N → ruvds:80` — корректно.
> **Conclusion:** home-ISP / OpenWRT DPI / transparent proxy mangles Host header для **direct external HTTP**. Real end-users из других сетей не affected. Для smoke testing — обязательно SSH tunnel либо smoke с VDS/mobile.
### HTTP/2 auto-negotiates на IIS 10 + Server 2022/2025
ALPN supported из коробки; `curl --http2 https://...` → `HTTP/2 200`. Не требует config'а.
### Backup pipeline — закрыт
`Backup strategy для RUVDS` в исходном «Gotcha-fineprint» — **закрыт** 2026-05-24: rclone + SFTP → kreknin daily 04:30 MSK. См. [[../sources/ruvds-backup-daily-kreknin-2026-05-24]] для recipe + scripts.
### Cert import recipe — закрыт
Перенос LE certs из traefik `acme.json` → IIS PFX → SNI bindings вынесен в отдельный recipe-концепт: [[traefik-acme-json-to-iis-cert-import]].
## Cross-refs
- Driver для миграции: [[future-resilient-architecture-goals]] §Workshop pass 1 → IIS-track «вынести IIS с windows-recovery-host чтобы убрать SPOF домашней машины».
- Vendor selection: `.tasks/windows-hosting-vendor-research.md` (🟢 closed 2026-05-22 — RUVDS выбран).
- Mid-impl task: `.tasks/iis-migration-to-ruvds.md` (🟡 paused 2026-05-23 — этот концепт зафиксирован после failed-robocopy инцидента).
- Live RUVDS host: [[../entities/ruvds-iis-host]].
- Migration chronology: [[../sources/iis-migration-to-ruvds-2026-05-23]].
- Backup chronology: [[../sources/ruvds-backup-daily-kreknin-2026-05-24]].
- Cert import recipe: [[traefik-acme-json-to-iis-cert-import]].
- Source-host postmortem: [[iis-migration-2026-05-19-postmortem]] — что было при первой миграции снолла-recovery → нативный IIS.