diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 9935a6a..0b758aa 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -46,6 +46,51 @@ --- +## Фаза 12 (v1.x) — Страница статуса сервиса + DNS-проверка доменов + +**Статус:** запланирована, не начата. + +**Цель:** дать оператору один взгляд на «сервис жив и почта не будет зарубаться из-за DNS» — два независимых экрана: здоровье процесса (`/status`) и корректность DNS для каждого отправляющего домена (встроено в существующую страницу домена). Выходит за рамки ТЗ v1.0 (не было в разделе 7.2), согласовано с пользователем как отдельная фаза. + +### A. `/status` — здоровье сервера (только сервер, без доменов) + +1. **Процессы supervisord** — `supervisorctl status` (fixed-argv, без shell, как остальные exec-вызовы проекта), статус opendkim/postfix/panel/cert-reload/logrotate. +2. **Очередь Postfix** — переиспользовать `internal/postfix.Queue()`. +3. **TLS-сертификат** — распарсить `x509.NotAfter` из `TLS_CERT_FILE`, предупреждение при приближении срока истечения. +4. **Milter-сокеты** (`os.Stat` на `opendkim.sock`/`journal.sock`): отсутствие `opendkim.sock` — **ошибка** (при `default_action=tempfail` почта не уходит, см. [progress.md:132](progress.md)); отсутствие `journal.sock` — **предупреждение** (fail-open, почта уходит, но Send Log не пишется). +5. **PTR / прямое-обратное соответствие (FCrDNS) — ключевая проверка для доставляемости:** + - резолвим A/AAAA `SELFPOST_HOSTNAME` → IP сервера; + - резолвим PTR этого IP → имя; + - сравниваем PTR-имя с `SELFPOST_HOSTNAME`. + - **OK** — совпадают; **ошибка** — PTR отсутствует или не совпадает (многие принимающие сервера отклоняют/спамят почту без корректного PTR). + - Резолвленный IP сервера переиспользуется в пункте B.2 (SPF) — отдельный env для IP не нужен. + +Дешёвые проверки (1–4) — HTMX-polling как на остальных экранах мониторинга (~5с). PTR/hostname-lookup чуть дороже сети — кэш с TTL (например 1 мин) или отдельная кнопка «Recheck», не завязывать на 5-секундный polling. + +### B. DNS-статус домена (на странице `domain_detail.html`, рядом с существующей DKIM TXT-записью) + +1. **DKIM** — резолвим `._domainkey.` TXT, сравниваем с реальным ключом OpenDKIM (переиспользовать логику генерации записи из [dkim.go](../internal/domain/dkim.go)). +2. **SPF (поверхностная проверка, по решению пользователя)** — резолвим TXT домена, ищем запись с `v=spf1`. Если найдена — проверяем **только присутствие** механизма, покрывающего IP сервера (`ip4:`/`ip6:` буквально, либо `a`/`mx` без аргумента, резолвящийся в IP сервера из A.5). **Без** рекурсии по `include`, без полной RFC 7208-оценки pass/fail/softfail — осознанное упрощение (без новых зависимостей). +3. **DMARC** — TXT `_dmarc.`, наличие + политика (`none`/`quarantine`/`reject`). +4. Статусы по каждому пункту: не найдено / найдено, но не покрывает наш сервис / корректно. Кэш на домен (TTL ~5–10 мин) + кнопка «Recheck» (без autopolling — DNS дороже, чем локальные проверки блока A). + +### Архитектура + +- Новый пакет `internal/dnscheck`: `ServerIP(hostname)` (A/AAAA lookup), `PTRCheck(hostname, ip)`, `DKIMCheck(domain, selector, expectedKey)`, `SPFCheck(domain, serverIP)`, `DMARCCheck(domain)` — все с таймаутом на DNS-запрос (~5с), через `net.DefaultResolver`/контекст. +- Кэш результатов в памяти (мьютекс, TTL), без изменений схемы БД/миграций. +- Веб: `GET /status` + `GET /status/fragment` (быстрый блок A, для polling); секция DNS-статуса на `domain_detail.html` + `POST /domains/{id}/dns-recheck` (форс, обход кэша). +- Безопасность: ничего нового по ТЗ 7.6 не добавляет — DNS/exec-вызовы только за авторизованным админом, без пользовательского ввода в exec (доменные имена уже провалидированы при добавлении домена), таймауты на все сетевые вызовы (защита от зависания страницы). + +**Готово, когда:** `/status` показывает живой статус всех пяти пунктов блока A и обновляется polling'ом; на странице домена отображается статус DKIM/SPF/DMARC с кнопкой Recheck; PTR-проверка корректно ловит и совпадение, и несовпадение (проверено на реальном домене `selfpost.example.com`, у которого PTR уже настроен, — см. [selfpost-prod-deployment.md]); `gofmt`/`vet`/`test`/`docker build` зелёные. + +**Риски:** DNS-резолверы могут быть медленными/недоступными — все проверки таймаутят и не блокируют остальной UI (страница рендерится с "неизвестно/timeout", а не висит). Ложные срабатывания SPF-эвристики (например, домен покрывает IP сервера через `include:` стороннего сервиса, который в свою очередь резолвится в IP сервера) — задокументировать как известное ограничение поверхностной проверки. + +**Модель:** Sonnet (UI + рутинные DNS-lookup, не риск-критичный тракт доставки/безопасности). + +**Зависимости:** не блокирует и не блокируется существующими фазами; использует уже реализованные `internal/postfix.Queue()`, DKIM-логику домена, паттерны HTMX-фрагментов из Фазы 7. + +--- + ## Опциональные фазы — целевой релиз 2.x.x (вне базового объёма v1.0) Эти фазы **не входят** в линейный базис 0→11 и не являются частью поставки v1.0 (v1.x — только исходящий релей). Они отнесены к **релизной линии 2.x.x** и добавлены в дорожную карту как согласуемые расширения. **Реализация — только после явного согласования (ТЗ 12.6):** ТЗ v1.0 раздел 3 явно исключает приём входящей почты из объёма, поэтому включение этой функциональности — сознательное расширение границ проекта (major-релиз 2.0), а не доработка по своей инициативе. Внесение в план фиксирует намерение и дизайн; кодирование начинается отдельным решением. diff --git a/docs/progress.md b/docs/progress.md index 14bacdd..8b5f5e9 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -35,7 +35,7 @@ ## Текущее состояние - **Базовый линейный план 0→11 (v1.0) полностью выполнен и принят** (аудит безопасности ТЗ 7.6 — полное соответствие, деплой в проде подтверждён). Подробности — в git-истории и `CHANGELOG.md`. -- **Дальше — только то, что перечислено в `implementation-plan.md`:** открытые вопросы (раздел A-D — hardening сверх обязательного 7.6, надёжность, CI/тесты) и опциональная **Фаза O1+** (входящий релей, линия 2.x.x) — оба блока требуют явного решения/согласования пользователя перед реализацией. +- **Дальше — то, что перечислено в `implementation-plan.md`:** открытые вопросы (раздел A-D — hardening сверх обязательного 7.6, надёжность, CI/тесты), опциональная **Фаза O1+** (входящий релей, линия 2.x.x, требует согласования) и **Фаза 12 (v1.x, запланирована, не начата)** — страница статуса `/status` (процессы/очередь/TLS/milter-сокеты/PTR) + DNS-статус домена (DKIM/SPF-эвристика/DMARC) на странице домена; согласована с пользователем, готова к реализации. - **Прод:** `selfpost.example.com`, реальный Let's Encrypt сертификат, живой e2e (DKIM/SPF pass). ## Рабочая петля (dev loop) — ВАЖНО