diff --git a/README.md b/README.md index 70c611a..70addb2 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ TLS: HAProxy на **443** в режиме **TCP + SNI**, **не терминир | Zabbix (веб) | **`zabbix.kalinamall.ru`** | **192.168.160.53** | **80** (HTTP) | **`zabbix-allowed`** | | Synology DSM | **`heap.kalinamall.ru`** | **192.168.160.185** | 443 → **5001** | **`syno-allowed`** | | Synology DSM | **`exchange.kalinamall.ru`** | **192.168.160.35** | 443 → **5001** | **`syno-allowed`** | -| Почтовый шлюз (KSMG) | `ksmg.kalinamall.ru` | **192.168.160.57** | 25 | нет | +| Почтовый шлюз (KSMG) | `ksmg.kalinamall.ru` | **192.168.160.57** | 25 | нет (с интернета на HAProxy не проброшен; см. [security/architecture.md](docs/security/architecture.md)) | -DNS: **A** для `ext`, `autodiscover`, `rds`, `erp`, `git`, `sac`, **`sac-api`**, **`zabbix`**, **`heap`**, **`exchange`**, `ksmg` → публичный IP HAProxy. +DNS: **A** для `ext`, `autodiscover`, `rds`, `erp`, `git`, `sac`, **`sac-api`**, **`zabbix`**, **`heap`**, **`exchange`** → публичный IP HAProxy. Inbound SMTP (**25**) с интернета на HAProxy **не** пробрасывается; `ksmg` / MX — отдельная схема (см. [security/architecture.md](docs/security/architecture.md)). Снаружи Zabbix — **`http://zabbix.kalinamall.ru`**. Synology DSM — **`https://heap.kalinamall.ru`**, **`https://exchange.kalinamall.ru`** (TLS на NAS, wildcard `*.kalinamall.ru`). Имя **`exchange.kalinamall.ru`** — NAS, не OWA (**`ext.kalinamall.ru`**). diff --git a/docs/haproxy-install.md b/docs/haproxy-install.md index 0518140..373fcca 100644 --- a/docs/haproxy-install.md +++ b/docs/haproxy-install.md @@ -63,7 +63,7 @@ sudo haproxy -c -f /etc/haproxy/haproxy.cfg ## Порты на фаерволе -На хосте с HAProxy с интернета обычно открывают только то, что слушает конфиг: **25**, **80** (Zabbix HTTP), **443** (и при использовании — **8404** для stats). Остальной трафик — по политике безопасности. +На хосте с HAProxy с **интернета** (MikroTik DNAT) доступны только **80** (Zabbix HTTP) и **443**. Порт **25** в конфиге есть, но с периметра **не проброшен**. Остальной трафик — по политике безопасности. **Полный план:** [docs/security/README.md](security/README.md) — UFW, бэкенды, чеклист, скрипты `scripts/security/`. diff --git a/docs/security/README.md b/docs/security/README.md index ebf99e3..cc278d3 100644 --- a/docs/security/README.md +++ b/docs/security/README.md @@ -1,13 +1,13 @@ -# Безопасность периметра HAProxy (kalinamall.ru) +# Безопасность периметра HAProxy (kalinamall.ru) -План защиты хоста **haproxy.kalinamall.ru** и **бэкендов** за ним. HAProxy слушает **25**, **80**, **443**; фильтрация по SNI/Host и allowlist уже в `haproxy.cfg`, но **UFW на хосте inactive** — ниже поэтапное включение **defense in depth**. +План защиты хоста **haproxy.kalinamall.ru** и **бэкендов** за ним. HAProxy слушает **25**, **80**, **443**; с **интернета** (MikroTik DNAT) доступны только **80** и **443**. Фильтрация по SNI/Host и allowlist уже в `haproxy.cfg`, но **UFW на хосте inactive** — ниже поэтапное включение **defense in depth**. ## Модель угроз (кратко) | Уровень | Что закрывает | |---------|----------------| -| **MikroTik / провайдер** | DDoS, geo, лишние порты до DMZ | -| **UFW на HAProxy** | Вход только 25/80/443 + SSH с доверенных IP | +| **MikroTik / провайдер** | DDoS, geo; DNAT на HAProxy только **80/443** | +| **UFW на HAProxy** | SSH с доверенных IP; **80/443** (и **25** только если нужен с LAN) | | **HAProxy ACL** | Неизвестный SNI, allowlist по сервисам | | **UFW на бэкендах** | Сервисы доступны **только с IP HAProxy** (`192.168.160.117`) | | **Приложение** | Auth OWA, RD Gateway, Gitea, SAC, 1С | @@ -51,4 +51,4 @@ Allowlist-файлы в `/etc/haproxy/` (**не** заменяют UFW): - `git-sac-allowed`, `rds-allowed`, `1c-allowed`, `zabbix-allowed`, `syno-allowed` -Сервисы **без** allowlist на 443: **Exchange** (`ext`, `autodiscover`), **sac-api**, **SMTP 25** — усилить на уровне приложения и мониторинга; при необходимости добавить allowlist или rate-limit (см. [haproxy-host.md](haproxy-host.md)). +Сервисы **без** allowlist на 443: **Exchange** (`ext`, `autodiscover`), **sac-api**. **SMTP 25** с интернета на HAProxy **не проброшен** (см. [architecture.md](architecture.md)); `fe_smtp` — для LAN/внутреннего использования. diff --git a/docs/security/architecture-flow.svg b/docs/security/architecture-flow.svg index a8c8f5e..c441e66 100644 --- a/docs/security/architecture-flow.svg +++ b/docs/security/architecture-flow.svg @@ -23,7 +23,7 @@ DNAT публичный IP - HAProxy 192.168.160.117 (25 80 443) + HAProxy 192.168.160.117 (80 443) diff --git a/docs/security/architecture.md b/docs/security/architecture.md index 466c938..9d81335 100644 --- a/docs/security/architecture.md +++ b/docs/security/architecture.md @@ -9,20 +9,23 @@ Та же схема текстом: ```text -Интернет → MikroTik (DNAT) → HAProxy .117 (25/80/443) → ACL (SNI/Host) → бэкенды .160.0/24 +Интернет → MikroTik (DNAT 80/443) → HAProxy .117 → ACL (SNI/Host) → бэкенды .160.0/24 ``` +**Периметр MikroTik:** с интернета на HAProxy проброшены только **80** и **443**. Порт **25** (и прочие) на HAProxy снаружи **недоступны** — DNAT не настроен. + ## Два контура ### 1. Публичный вход (через HAProxy) -Клиент → публичный IP → **HAProxy** → (проверки) → один бэкенд. +Клиент → публичный IP → **MikroTik (DNAT 80/443)** → **HAProxy** → (проверки) → один бэкенд. -На HAProxy уже есть: +На HAProxy с интернета доступны только: - **443/TCP**: отклонение неизвестного SNI; allowlist для git, sac-web, rds, erp, syno. - **80/HTTP**: только `Host: zabbix.kalinamall.ru` + `zabbix-allowed`. -- **25/SMTP**: passthrough на KSMG без IP-фильтра на frontend. + +**25/SMTP** в `haproxy.cfg` слушает на хосте, но **с интернета не проброшен** (MikroTik DNAT только 80/443). Inbound-почта на KSMG идёт **не через** HAProxy:25 с периметра. ### 2. Внутренняя сеть (LAN / VPN) @@ -58,7 +61,7 @@ Health-check HAProxy и прокси идут **с этого IP**. Правил ## Принципы 1. **Default deny** на вход (UFW `incoming deny`, бэкенды — только HAProxy + admin). -2. **Минимум открытых портов** с интернета: 25, 80, 443 на одном хосте. +2. **Минимум открытых портов** с интернета на HAProxy: **80** и **443** (DNAT на MikroTik). 3. **Не дублировать** смысл правил без нужды: allowlist в HAProxy для HTTP/SNI; UFW — транспорт и админ. 4. **Сначала dry-run**, потом `--apply`; для SSH — **console/IPMI** или сессия, которую UFW не оборвёт. 5. **Логи**: HAProxy + UFW + fail2ban; алерты на аномалии (Zabbix уже в инфраструктуре). diff --git a/docs/security/backend-hosts.md b/docs/security/backend-hosts.md index a2388e8..6275333 100644 --- a/docs/security/backend-hosts.md +++ b/docs/security/backend-hosts.md @@ -1,4 +1,4 @@ -# Firewall на бэкендах +# Firewall на бэкендах Цель: сервисный порт доступен **с HAProxy** (`192.168.160.117`) и при необходимости с **admin**-подсетей; **не** с произвольного LAN/Internet напрямую. @@ -40,7 +40,9 @@ UFW_EXTRA_SERVICES="2222/tcp" # если Gitea SSH с admin LAN ### KSMG (192.168.160.57:25) -Почта с интернета приходит на **HAProxy:25** → KSMG. На KSMG в `EXTRA_ALLOWED` укажите **192.168.160.117**. Если другие MTA шлют **напрямую** на `.57` (минуя HAProxy), их IP тоже нужно явно разрешить — иначе после UFW почта встанет. +С **интернета** на HAProxy проброшены только **80/443** — inbound SMTP **не** приходит на **HAProxy:25**. Почта с периметра идёт **напрямую на KSMG** (или иным маршрутом), минуя HAProxy. + +`fe_smtp` в `haproxy.cfg` — для **LAN** (прокси на KSMG с `.117`). На KSMG в `EXTRA_ALLOWED` укажите **192.168.160.117**, если этот путь используется. IP внешних MTA, шлющих **напрямую** на `.57`, тоже нужно явно разрешить — иначе после UFW почта встанет. ### RDS (192.168.160.40) diff --git a/docs/security/checklist.md b/docs/security/checklist.md index 57a9170..efdb00f 100644 --- a/docs/security/checklist.md +++ b/docs/security/checklist.md @@ -1,8 +1,8 @@ -# Чеклист после внедрения защиты +# Чеклист после внедрения защиты ## HAProxy (192.168.160.117) -- [ ] `ufw status verbose` — active; есть 22 из admin, 25/80/443 open +- [ ] `ufw status verbose` — active; есть 22 из admin, **80/443** open (25 — только если явно включён для LAN) - [ ] SSH с admin-IP работает; с random IP — **нет** (или только ключ + fail2ban) - [ ] `ss -tlnp` — нет лишних listen (8404, 3000, …) - [ ] `haproxy -c -f /etc/haproxy/haproxy.cfg` — OK @@ -13,7 +13,7 @@ ## MikroTik / периметр -- [ ] С интернета на публичный IP: открыты только **25, 80, 443** +- [ ] С интернета на публичный IP: открыты только **80, 443** - [ ] Нет DNAT 3389, 5001, 4430 на внутренние хосты - [ ] HAProxy health-check не блокируется @@ -27,8 +27,8 @@ ## KSMG (.57) -- [ ] Почта inbound через HAProxy:25 — OK -- [ ] Прямые отправители (если есть) — в `EXTRA_ALLOWED` +- [ ] Inbound-почта с интернета **не** идёт через HAProxy:25 (DNAT на MikroTik только 80/443) +- [ ] На KSMG в `EXTRA_ALLOWED` — **192.168.160.117** (если используется `fe_smtp` из LAN) и прямые MTA ## Документация и runbook diff --git a/docs/security/deployment-plan.md b/docs/security/deployment-plan.md index 579f213..77ccd0b 100644 --- a/docs/security/deployment-plan.md +++ b/docs/security/deployment-plan.md @@ -1,10 +1,10 @@ -# План развёртывания защиты +# План развёртывания защиты Оценка: **2–4 рабочих дня** с окнами обслуживания на бэкендах (по одному хосту). ## Фаза 0 — Подготовка (без изменений на проде) -- [ ] Зафиксировать **публичный IP** HAProxy и правила DNAT на MikroTik. +- [ ] Зафиксировать **публичный IP** HAProxy и правила DNAT на MikroTik (**только 80/443**). - [ ] Убедиться, что **SSH** на `192.168.160.117` доступен с admin-IP (не только с одного ноутбука). - [ ] Снимок/backup: `haproxy.cfg`, allowlist, `/etc/ssh/sshd_config`. - [ ] Клонировать репозиторий на HAProxy: `git pull kalinamall main`. @@ -36,7 +36,7 @@ - [ ] Dry-run: `bash scripts/security/haproxy-ufw-apply.sh` - [ ] **Не закрывая SSH-сессию**, в second tab: `bash scripts/security/haproxy-ufw-apply.sh --apply` - [ ] Проверить с admin-IP: `ssh haproxy.kalinamall.ru`, `curl` на 80/443 с allowlist. -- [ ] Проверить SMTP 25 с внешнего MTA (если используется). +- [ ] Убедиться, что **25** с интернета на HAProxy **не** доступен (ожидаемо: DNAT не настроен). **Откат:** `ufw disable` (временно) или `ufw delete` по номерам правил; env-файл сохранить. @@ -68,7 +68,7 @@ ## Фаза 4 — MikroTik и мониторинг -- [ ] На MikroTik: с интернета на HAProxy только **25, 80, 443** (не пробрасывать 3389, 5001, 4430 на внутренние IP). +- [ ] На MikroTik: с интернета на HAProxy только **80, 443** (не пробрасывать 25, 3389, 5001, 4430 на внутренние IP). - [ ] Raw/firewall: опционально drop obvious scanners (согласовать с `rds-allowed` / RU geo). - [ ] Zabbix: триггеры на `ufw status`, `fail2ban`, рост `fe_https_sni` reject, backend DOWN. - [ ] Ротация логов `/var/log/haproxy.log`. diff --git a/docs/security/haproxy-host.md b/docs/security/haproxy-host.md index 243c393..ea9159c 100644 --- a/docs/security/haproxy-host.md +++ b/docs/security/haproxy-host.md @@ -1,4 +1,4 @@ -# Hardening хоста HAProxy +# Hardening хоста HAProxy Хост: **haproxy.kalinamall.ru**, **192.168.160.117**, Ubuntu 24.04, HAProxy 2.8.x. @@ -7,12 +7,16 @@ | Порт | Frontend | Назначение | |------|----------|------------| | 22 | sshd | Администрирование (не в haproxy.cfg) | -| 25 | fe_smtp | SMTP → KSMG | +| 25 | fe_smtp | SMTP → KSMG (**LAN**; с интернета не проброшен) | | 80 | fe_http_zabbix | HTTP Zabbix | | 443 | fe_https_sni | TLS passthrough по SNI | Не включать **8404/stats** на `0.0.0.0` без VPN и пароля. +## Периметр (MikroTik) + +С **интернета** на публичный IP HAProxy проброшены только **TCP 80** и **443**. Остальные порты HAProxy (в т.ч. **25**) снаружи **недоступны** — DNAT не настроен. + ## UFW Скрипт: [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh). @@ -23,7 +27,8 @@ default incoming: deny default outgoing: allow allow 22/tcp from SSH_ALLOWED -allow 25, 80, 443/tcp from anywhere # сервисы периметра +allow 80, 443/tcp from anywhere # сервисы периметра (MikroTik DNAT) +# allow 25/tcp from LAN_CIDR # только если нужен SMTP через HAProxy изнутри сети ``` **SSH_ALLOWED** — несколько CIDR/ IP через пробел в env. Минимум: diff --git a/scripts/security/haproxy-ufw-apply.sh b/scripts/security/haproxy-ufw-apply.sh index ffd614e..64b8d82 100755 --- a/scripts/security/haproxy-ufw-apply.sh +++ b/scripts/security/haproxy-ufw-apply.sh @@ -1,5 +1,5 @@ -#!/usr/bin/env bash -# UFW для HAProxy: default deny, SSH из SSH_ALLOWED, inbound 25/80/443. +#!/usr/bin/env bash +# UFW для HAProxy: default deny, SSH из SSH_ALLOWED, inbound 80/443 (25 — опционально для LAN). # Dry-run по умолчанию; --apply для включения. # # cp haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env @@ -37,7 +37,7 @@ done source "$ENV_FILE" SSH_ALLOWED=${SSH_ALLOWED:-} -INBOUND_TCP=${INBOUND_TCP:-25 80 443} +INBOUND_TCP=${INBOUND_TCP:-80 443} UFW_IPV6=${UFW_IPV6:-no} [[ -n $SSH_ALLOWED ]] || die "SSH_ALLOWED is empty — lockout risk" diff --git a/scripts/security/haproxy-ufw.env.example b/scripts/security/haproxy-ufw.env.example index e8c78e7..a344abe 100644 --- a/scripts/security/haproxy-ufw.env.example +++ b/scripts/security/haproxy-ufw.env.example @@ -1,11 +1,14 @@ -# UFW для haproxy.kalinamall.ru — пример. +# UFW для haproxy.kalinamall.ru — пример. # Копия: /etc/haproxy/security/haproxy-ufw.env (не коммитить в GitHub с prod-IP при необходимости) # IP/CIDR для SSH (через пробел). Обязательно перед --apply. SSH_ALLOWED="192.168.160.0/24 5.100.81.121" -# Публичные сервисы HAProxy (пробел) -INBOUND_TCP="25 80 443" +# Публичные сервисы HAProxy с периметра (пробел). MikroTik DNAT: только 80 и 443. +INBOUND_TCP="80 443" + +# Добавьте 25, если нужен SMTP через HAProxy только из LAN (не с интернета): +# INBOUND_TCP="80 443 25" # IPv6: yes | no UFW_IPV6=no