diff --git a/docs/security/README.md b/docs/security/README.md index cc278d3..a24a685 100644 --- a/docs/security/README.md +++ b/docs/security/README.md @@ -1,54 +1,40 @@ -# Безопасность периметра HAProxy (kalinamall.ru) +# Безопасность kalinamall.ru -План защиты хоста **haproxy.kalinamall.ru** и **бэкендов** за ним. HAProxy слушает **25**, **80**, **443**; с **интернета** (MikroTik DNAT) доступны только **80** и **443**. Фильтрация по SNI/Host и allowlist уже в `haproxy.cfg`, но **UFW на хосте inactive** — ниже поэтапное включение **defense in depth**. +Защита **haproxy.kalinamall.ru** (`192.168.160.117`) и бэкендов за ним. -## Модель угроз (кратко) +## Периметр -| Уровень | Что закрывает | -|---------|----------------| -| **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С | +- **80/443** с интернета → HAProxy `.117` → бэкенды (ACL в `haproxy.cfg`). +- **25** с интернета → **KSMG** `.57` напрямую (мимо HAProxy). -Обход HAProxy (VPN в `192.168.160.0/24`, прямой RDP) — отдельная политика; см. [backend-hosts.md](backend-hosts.md). +## Сейчас: UFW на HAProxy -## Документы +| Документ | Назначение | +|----------|------------| +| [architecture.md](architecture.md) | Схема, зоны, матрица | +| [haproxy-host.md](haproxy-host.md) | **UFW и правила** — основной документ для внедрения | +| [deployment-plan.md](deployment-plan.md) | Пошаговый план (фаза 2 = UFW, стоп) | +| [checklist.md](checklist.md) | Проверки после UFW | -| Файл | Содержание | -|------|------------| -| [architecture.md](architecture.md) | Схема потоков, зоны, принципы | -| [haproxy-host.md](haproxy-host.md) | Hardening Ubuntu, UFW, SSH, fail2ban, HAProxy | -| [backend-hosts.md](backend-hosts.md) | Правила для каждого бэкенда | -| [deployment-plan.md](deployment-plan.md) | **Пошаговый план** внедрения (фазы 0–5) | -| [checklist.md](checklist.md) | Проверки после внедрения | +**Позже** (не в текущем scope): [backend-hosts.md](backend-hosts.md), UFW на KSMG и бэкендах. ## Скрипты -| Скрипт | Назначение | -|--------|------------| -| [scripts/security/haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh) | UFW на HAProxy (dry-run по умолчанию) | -| [scripts/security/haproxy-ufw.env.example](../../scripts/security/haproxy-ufw.env.example) | Пример env для HAProxy | -| [scripts/security/backend-ufw-from-haproxy.sh](../../scripts/security/backend-ufw-from-haproxy.sh) | UFW на бэкенде: порт только с HAProxy | -| [scripts/security/backend-ufw.env.example](../../scripts/security/backend-ufw.env.example) | Пример env для бэкенда | - -## Быстрый старт (после чтения deployment-plan) - ```bash -# на HAProxy — сначала dry-run +sudo mkdir -p /etc/haproxy/security sudo cp ~/reverse-proxy/scripts/security/haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env -sudo nano /etc/haproxy/security/haproxy-ufw.env # SSH_ALLOWED, при необходимости порты -sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh -sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh --apply +sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh # dry-run +sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh --apply # вторая SSH-сессия! ``` -На каждом бэкенде — [backend-hosts.md](backend-hosts.md) и `backend-ufw-from-haproxy.sh`. +| Скрипт | Назначение | +|--------|------------| +| [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh) | UFW на HAProxy | +| [haproxy-ufw.env.example](../../scripts/security/haproxy-ufw.env.example) | `SSH_ALLOWED`, порты | +| [backend-ufw-from-haproxy.sh](../../scripts/security/backend-ufw-from-haproxy.sh) | Бэкенды — позже | -## Связь с конфигом HAProxy +## Allowlist HAProxy -Allowlist-файлы в `/etc/haproxy/` (**не** заменяют UFW): +Файлы в `/etc/haproxy/`: `git-sac-allowed`, `rds-allowed`, `1c-allowed`, `zabbix-allowed`, `syno-allowed`. -- `git-sac-allowed`, `rds-allowed`, `1c-allowed`, `zabbix-allowed`, `syno-allowed` - -Сервисы **без** allowlist на 443: **Exchange** (`ext`, `autodiscover`), **sac-api**. **SMTP 25** с интернета на HAProxy **не проброшен** (см. [architecture.md](architecture.md)); `fe_smtp` — для LAN/внутреннего использования. +Без allowlist на 443: Exchange (`ext`, `autodiscover`), `sac-api`. diff --git a/docs/security/architecture-flow.svg b/docs/security/architecture-flow.svg index c441e66..f7cdc84 100644 --- a/docs/security/architecture-flow.svg +++ b/docs/security/architecture-flow.svg @@ -1,37 +1,35 @@ - + - - + + - - Интернет + Интернет - + + 80/443 + + MikroTik DNAT + + + HAProxy .117 + + + ACL → бэкенды .160.0/24 - - MikroTik / периметр - - - DNAT публичный IP - - - HAProxy 192.168.160.117 (80 443) - - - - - HAProxy ACL: SNI / Host allowlist - - - - - Бэкенды 192.168.160.0/24 + + 25 + + MikroTik DNAT + + + KSMG .57 diff --git a/docs/security/architecture.md b/docs/security/architecture.md index 9d81335..23d9482 100644 --- a/docs/security/architecture.md +++ b/docs/security/architecture.md @@ -1,67 +1,64 @@ # Архитектура защиты -## Поток трафика +## Периметр (MikroTik) + +| С интернета | DNAT на | Порт | +|-------------|---------|------| +| HTTP/HTTPS | **HAProxy** `192.168.160.117` | **80**, **443** | +| SMTP | **KSMG** `192.168.160.57` | **25** | + +На HAProxy с интернета **только 80 и 443**. Порт **25** на HAProxy **не проброшен**. + +## Потоки ![Поток трафика](architecture-flow.svg) -Схема в SVG — без Mermaid, чтобы Gitea/Firefox не ломали рендер из‑за `
` в XML. - -Та же схема текстом: - ```text -Интернет → MikroTik (DNAT 80/443) → HAProxy .117 → ACL (SNI/Host) → бэкенды .160.0/24 +Интернет ──80/443──► MikroTik ──► HAProxy .117 ── ACL (SNI/Host) ──► бэкенды .160.0/24 +Интернет ──25──────► MikroTik ──► KSMG .57 ``` -**Периметр MikroTik:** с интернета на HAProxy проброшены только **80** и **443**. Порт **25** (и прочие) на HAProxy снаружи **недоступны** — DNAT не настроен. +### Через HAProxy (80/443) -## Два контура +- **443**: reject неизвестного SNI; allowlist — git, sac-web, rds, erp, syno; без allowlist — Exchange, sac-api. +- **80**: только `Host: zabbix.kalinamall.ru` + `zabbix-allowed`. -### 1. Публичный вход (через HAProxy) +Прокси и health-check идут **с IP `.117`**. Бэкенды должны разрешать этот источник (UFW на бэкендах — **позже**, см. [backend-hosts.md](backend-hosts.md)). -Клиент → публичный IP → **MikroTik (DNAT 80/443)** → **HAProxy** → (проверки) → один бэкенд. +### Мимо HAProxy -На HAProxy с интернета доступны только: +- **Почта**: inbound SMTP → **напрямую KSMG** (не через `fe_smtp` на `.117`). +- **LAN/VPN**: прямой доступ к бэкендам, RDP, SSH — если нет host firewall. -- **443/TCP**: отклонение неизвестного SNI; allowlist для git, sac-web, rds, erp, syno. -- **80/HTTP**: только `Host: zabbix.kalinamall.ru` + `zabbix-allowed`. +## Текущий этап: UFW на HAProxy -**25/SMTP** в `haproxy.cfg` слушает на хосте, но **с интернета не проброшен** (MikroTik DNAT только 80/443). Inbound-почта на KSMG идёт **не через** HAProxy:25 с периметра. +Сейчас внедряем только [haproxy-host.md](haproxy-host.md) — UFW на `.117`: -### 2. Внутренняя сеть (LAN / VPN) +| Порт | Кому | +|------|------| +| **22** | `SSH_ALLOWED`: `5.100.81.121`, `192.168.128.0/17` | +| **8404** (stats) | тот же список (если frontend включён) | +| **80**, **443** | все | +| остальное | deny (в т.ч. **25** на HAProxy) | -Трафик **мимо HAProxy** возможен, если: +## Матрица фильтрации -- есть VPN в `192.168.160.0/24`; -- бэкенд слушает на LAN без host firewall; -- RDP/RDP Gateway по внутренним адресам. - -Защита: **UFW на бэкендах** + политика «сервисный порт только с `.117`», админ-доступ — отдельные правила. - -## IP HAProxy (источник для бэкендов) - -| Имя | IP | -|-----|-----| -| haproxy.kalinamall.ru | **192.168.160.117** | - -Health-check HAProxy и прокси идут **с этого IP**. Правила на бэкендах должны разрешать его явно. - -## Матрица: кто фильтрует что - -| Угроза | MikroTik | UFW HAProxy | HAProxy cfg | UFW бэкенд | Приложение | -|--------|----------|-------------|-------------|------------|------------| -| Сканирование портов | ✓ | ✓ | — | ✓ | — | +| Угроза | MikroTik | UFW HAProxy | HAProxy cfg | UFW бэкенд* | Приложение | +|--------|----------|-------------|-------------|-------------|------------| +| Лишние порты с интернета | ✓ | ✓ | — | — | — | | Неверный SNI / Host | — | — | ✓ | — | — | -| Доступ не из allowlist | — | — | ✓ | — | ✓ | -| Прямой удар в бэкенд | ✓ | — | — | ✓ | — | -| Brute SSH на HAProxy | ✓ | ✓ | — | — | fail2ban | -| DDoS L4 | ✓ | частично | conn limits* | — | — | +| Не из allowlist | — | — | ✓ | — | ✓ | +| Прямой удар в бэкенд | частично | — | — | ✓ | — | +| Brute SSH | — | ✓ | — | — | fail2ban† | +| SMTP-спам на KSMG | — | — | — | ✓‡ | KSMG | -\* Опционально: `maxconn`, rate-limit на frontend (Enterprise / custom); на 2.8 — sysctl, MikroTik, внешний scrubbing. +\* Бэкенды — следующий этап. +† Рекомендуется, не в scope текущего UFW. +‡ KSMG — периметровый хост для **25**, отдельный план. ## Принципы -1. **Default deny** на вход (UFW `incoming deny`, бэкенды — только HAProxy + admin). -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 уже в инфраструктуре). +1. **Default deny** на вход (UFW `incoming deny`). +2. Периметр на MikroTik — источник истины для DNAT; UFW на HAProxy — defense in depth + SSH. +3. Allowlist в `haproxy.cfg` для 443/80; UFW не дублирует SNI/Host. +4. UFW: сначала dry-run, потом `--apply`; держать вторую SSH-сессию. diff --git a/docs/security/backend-hosts.md b/docs/security/backend-hosts.md index 6275333..9813ff4 100644 --- a/docs/security/backend-hosts.md +++ b/docs/security/backend-hosts.md @@ -1,90 +1,55 @@ # Firewall на бэкендах -Цель: сервисный порт доступен **с HAProxy** (`192.168.160.117`) и при необходимости с **admin**-подсетей; **не** с произвольного LAN/Internet напрямую. +**Статус: не внедряем сейчас.** Текущий этап — UFW только на HAProxy ([haproxy-host.md](haproxy-host.md)). + +Цель (когда дойдём): сервисный порт только с **HAProxy** `192.168.160.117` + admin при необходимости. Скрипт: [backend-ufw-from-haproxy.sh](../../scripts/security/backend-ufw-from-haproxy.sh). -## Таблица бэкендов +## Бэкенды за HAProxy -| Сервис | IP | Порт | Протокол | HAProxy backend | Примечание | -|--------|-----|------|----------|-----------------|------------| -| Exchange | 192.168.160.42 | 443 | TCP | be_exchange | OWA, ActiveSync | -| RDS Gateway | 192.168.160.40 | **4430** | TCP | be_rds | не 443 | -| 1С ERP | 192.168.160.150 | 443 | TCP | be_erp | | -| Git (Gitea) | 192.168.160.129 | 443 | TCP | be_git | SSH Gitea 2222 — отдельно | -| SAC | 192.168.160.145 | 443 | TCP | be_sac | | -| Zabbix | 192.168.160.53 | **80** | TCP | be_zabbix | HTTP | -| Synology heap | 192.168.160.185 | **5001** | TCP | be_syno_heap | DSM HTTPS | -| Synology exchange | 192.168.160.35 | **5001** | TCP | be_syno_exchange | | -| KSMG | 192.168.160.57 | 25 | TCP | be_ksmg_smtp | см. ниже | +| Сервис | IP | Порт | HAProxy backend | +|--------|-----|------|-----------------| +| Exchange | 192.168.160.42 | 443 | be_exchange | +| RDS Gateway | 192.168.160.40 | 4430 | be_rds | +| 1С ERP | 192.168.160.150 | 443 | be_erp | +| Git (Gitea) | 192.168.160.129 | 443 | be_git | +| SAC | 192.168.160.145 | 443 | be_sac | +| Zabbix | 192.168.160.53 | 80 | be_zabbix | +| Synology heap | 192.168.160.185 | 5001 | be_syno_heap | +| Synology exchange | 192.168.160.35 | 5001 | be_syno_exchange | -## Шаблон правил (логика) +`fe_smtp` в HAProxy — опционально для LAN; inbound **25** с интернета на **KSMG**, не через `.117`. -``` -allow from 192.168.160.117 to -allow from ADMIN_ALLOWED to 22/tcp # SSH, если на этом хосте -allow from LAN to ... # только если нужен прямой доступ (минимизировать) -deny incoming (default) -``` +## KSMG (192.168.160.57) — отдельно от HAProxy -### Пример env для Git (192.168.160.129) +| Путь | DNAT | +|------|------| +| Интернет → SMTP | MikroTik **25** → `.57` | +| HAProxy → KSMG | только если используется `fe_smtp` из LAN | + +При UFW на KSMG: разрешить **25** с интернета (или по списку MTA), **не** полагаться на HAProxy. + +## Шаблон (Linux) ```bash HAPROXY_IP=192.168.160.117 BACKEND_PORT=443 -ADMIN_ALLOWED="192.168.160.0/24" -UFW_EXTRA_SERVICES="2222/tcp" # если Gitea SSH с admin LAN +ADMIN_ALLOWED="192.168.128.0/17" ``` -## Особые случаи - -### KSMG (192.168.160.57:25) - -С **интернета** на 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) - -Порт **4430**, не 443. VPN-клиенты с прямым RDP на `.3` / `.40` обходят HAProxy — ограничивать **3389/4430** на session hosts отдельной GPO/файрволом. - -### Synology - -DSM может требовать доп. порты (5000, 5001, relay). Для доступа **только через HAProxy** достаточно **5001** с `.117`. Прямой доступ к NAS с LAN — отключить или ограничить в DSM **Панель → Безопасность → Брандмауэр**. - -### Exchange - -Внутренние клиенты Outlook могут ходить **напрямую** на `.42` по LAN — UFW не сломать без списка подсетей Exchange. Варианты: - -- разрешить `192.168.160.0/24` на 443 (шире); -- или только `.117` + подсеть Exchange DAG/clients (уже). - -### Windows vs Linux - -Скрипт UFW — для **Linux**. Для Windows (Exchange, RDS, 1С) — **Windows Firewall with Advanced Security**: inbound allow **Remote IP** `192.168.160.117`, порт сервиса. - -## Развёртывание на Linux-бэкенде - ```bash -sudo mkdir -p /etc/haproxy/security -sudo cp backend-ufw.env.example /etc/haproxy/security/backend-ufw.env -sudo nano /etc/haproxy/security/backend-ufw.env -sudo bash backend-ufw-from-haproxy.sh +sudo bash backend-ufw-from-haproxy.sh # dry-run sudo bash backend-ufw-from-haproxy.sh --apply ``` -## Проверка +## Особые случаи (кратко) -С **HAProxy** (или симуляция): +- **Exchange / Windows** — Windows Firewall, не UFW; внутренние Outlook-клиенты могут ходить на `.42` по LAN. +- **RDS** — порт **4430**; прямой RDP мимо HAProxy — отдельная политика. +- **Synology** — DSM firewall; с HAProxy достаточно **5001** с `.117`. +- **Gitea SSH 2222** — отдельное правило в `UFW_EXTRA_SERVICES`. -```bash -curl -kI --resolve git.kalinamall.ru:443:ПУБЛИЧНЫЙ_IP https://git.kalinamall.ru/ -``` +## Проверка (когда внедрим) -С **другого** LAN-хоста (не .117): - -```bash -curl -kI --connect-timeout 3 https://192.168.160.129/ # ожидается timeout/refused -``` - -В логе HAProxy — backend **UP**. +С HAProxy — сервис OK. С другого LAN IP на `https://192.168.160.129/` — refused/timeout. diff --git a/docs/security/checklist.md b/docs/security/checklist.md index efdb00f..5f878c4 100644 --- a/docs/security/checklist.md +++ b/docs/security/checklist.md @@ -1,44 +1,37 @@ -# Чеклист после внедрения защиты +# Чеклист -## HAProxy (192.168.160.117) +## UFW на HAProxy (текущий этап) -- [ ] `ufw status verbose` — active; есть 22 из admin, **80/443** open (25 — только если явно включён для LAN) -- [ ] SSH с admin-IP работает; с random IP — **нет** (или только ключ + fail2ban) -- [ ] `ss -tlnp` — нет лишних listen (8404, 3000, …) +- [ ] `ufw status verbose` — active +- [ ] **22/tcp** — только `5.100.81.121`, `192.168.128.0/17` +- [ ] **8404/tcp** — только тот же список (если stats включён) +- [ ] **80/tcp**, **443/tcp** — `Anywhere` +- [ ] **25/tcp** — нет правила allow (default deny) +- [ ] SSH с admin-IP — OK; с чужого IP — refused +- [ ] Сервисы через HAProxy (git, zabbix, rds, …) — OK - [ ] `haproxy -c -f /etc/haproxy/haproxy.cfg` — OK -- [ ] С allowlist IP: git, rds, zabbix, erp, syno — **OK** -- [ ] С чужого IP: git/rds/erp — **reject** (нет TCP reset или обрыв на ACL) -- [ ] `fail2ban-client status sshd` — active -- [ ] Логи: `grep -E 'SC|NOSRV|reject' /var/log/haproxy.log | tail` +- [ ] `/etc/haproxy/security/haproxy-ufw.env` — backup -## MikroTik / периметр +## MikroTik -- [ ] С интернета на публичный IP: открыты только **80, 443** -- [ ] Нет DNAT 3389, 5001, 4430 на внутренние хосты -- [ ] HAProxy health-check не блокируется +- [ ] DNAT **80/443** → `192.168.160.117` +- [ ] DNAT **25** → `192.168.160.57` (не на HAProxy) +- [ ] Нет DNAT 3389, 5001, 4430 на внутренние IP -## Бэкенды (каждый) +## После UFW (опционально, не блокер) -- [ ] UFW/firewall active -- [ ] С `.117` на сервисный порт — OK -- [ ] С другого LAN IP на сервисный порт — **closed** (если так задумано) -- [ ] HAProxy backend **UP** в stats/log -- [ ] Прикладной smoke-test (OWA, RD Web, Gitea login, …) +- [ ] fail2ban sshd +- [ ] `unattended-upgrades` -## KSMG (.57) +--- -- [ ] Inbound-почта с интернета **не** идёт через HAProxy:25 (DNAT на MikroTik только 80/443) -- [ ] На KSMG в `EXTRA_ALLOWED` — **192.168.160.117** (если используется `fe_smtp` из LAN) и прямые MTA +## Позже — бэкенды -## Документация и runbook +- [ ] UFW: сервисный порт только с `.117` +- [ ] KSMG: UFW под прямой **25** с интернета -- [ ] `/etc/haproxy/security/*.env` — backup в password store -- [ ] Откат UFW: `ufw disable` + процедура задокументирована -- [ ] Ответственный и дата внедрения фикс - -## Регулярно (ежемесячно) +## Регулярно - [ ] `apt upgrade` на HAProxy -- [ ] Проверка актуальности allowlist / `rds-allowed` cron -- [ ] Review fail2ban / auth.log -- [ ] Тест restore конфига из git +- [ ] Актуальность allowlist / `rds-allowed` +- [ ] `ufw status`, auth.log diff --git a/docs/security/deployment-plan.md b/docs/security/deployment-plan.md index 77ccd0b..11454a0 100644 --- a/docs/security/deployment-plan.md +++ b/docs/security/deployment-plan.md @@ -1,100 +1,61 @@ -# План развёртывания защиты +# План внедрения -Оценка: **2–4 рабочих дня** с окнами обслуживания на бэкендах (по одному хосту). +**Текущая остановка:** UFW на HAProxy (фаза 2). Бэкенды и KSMG — позже. -## Фаза 0 — Подготовка (без изменений на проде) +## Фаза 0 — Подготовка -- [ ] Зафиксировать **публичный 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`. -- [ ] Прочитать [checklist.md](checklist.md). - -**Критерий готовности:** есть консоль или второй admin-IP для отката UFW. +- [ ] Зафиксировать DNAT на MikroTik: + - `80`, `443` → `192.168.160.117` + - `25` → `192.168.160.57` (KSMG) +- [ ] SSH на `.117` работает с `5.100.81.121` или `192.168.128.0/17` +- [ ] Backup: `haproxy.cfg`, allowlist, `sshd_config` +- [ ] `git pull` на HAProxy +- [ ] Консоль/IPMI на случай lockout SSH --- -## Фаза 1 — HAProxy: базовый hardening (без UFW enable) +## Фаза 1 — Подготовка хоста (без UFW) -На **haproxy.kalinamall.ru**: +- [ ] `apt update && apt upgrade` +- [ ] SSH: ключи, без root/password ([haproxy-host.md](haproxy-host.md)) +- [ ] `haproxy -c -f /etc/haproxy/haproxy.cfg` — OK +- [ ] `fe_stats` (:8404) — закомментирован или будет под UFW +- [ ] Решить судьбу `fe_smtp` (:25): нужен из LAN или отключить -- [ ] `apt update && apt upgrade` (или unattended-upgrades). -- [ ] SSH: `PasswordAuthentication no`, `PermitRootLogin no`, только ключи (`/etc/ssh/sshd_config.d/`). -- [ ] Установить **fail2ban** (`sshd` jail), см. [haproxy-host.md](haproxy-host.md). -- [ ] Убедиться, что **stats :8404** не включены в prod (закомментированы в `haproxy.cfg`). -- [ ] Права: `/etc/haproxy/*-allowed` — `0644 root:root`; скрипты в `/usr/local/bin` — `0755`. -- [ ] `sysctl`: syncookies, rp_filter (см. [haproxy-host.md](haproxy-host.md)). - -**Проверка:** `ss -tlnp` — только 22, 25, 80, 443 (+ systemd-resolved если локально). +**Проверка:** `ss -tlnp` — ожидаемо 22, 80, 443; возможно 25. --- -## Фаза 2 — UFW на HAProxy +## Фаза 2 — UFW на HAProxy ← **СТОП ЗДЕСЬ** - [ ] `cp scripts/security/haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env` -- [ ] Заполнить **`SSH_ALLOWED`** (офис, VPN, admin; минимум ваш текущий IP + LAN). +- [ ] Проверить `SSH_ALLOWED="5.100.81.121 192.168.128.0/17"` - [ ] 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. -- [ ] Убедиться, что **25** с интернета на HAProxy **не** доступен (ожидаемо: DNAT не настроен). +- [ ] **Вторая SSH-сессия:** `bash scripts/security/haproxy-ufw-apply.sh --apply` +- [ ] [checklist.md](checklist.md) — HAProxy -**Откат:** `ufw disable` (временно) или `ufw delete` по номерам правил; env-файл сохранить. +**Откат:** `ufw disable` --- -## Фаза 3 — UFW на бэкендах (по одному хосту) +## Фаза 3 — UFW на бэкендах (позже) -Порядок рекомендуемый (от менее критичных к более): - -1. Zabbix `.53:80` -2. Git `.129:443` -3. SAC `.145:443` -4. Synology `.185:5001`, `.35:5001` -5. ERP `.150:443` -6. RDS `.40:4430` -7. Exchange `.42:443` -8. KSMG `.57:25` — **особый случай** (почта может идти не только с HAProxy); см. [backend-hosts.md](backend-hosts.md) - -На каждом хосте: - -- [ ] `cp backend-ufw.env.example` → `/etc/haproxy/security/backend-ufw.env` (или `/etc/security/`) -- [ ] Задать `BACKEND_PORT`, при необходимости `ADMIN_ALLOWED`, `EXTRA_ALLOWED` -- [ ] Dry-run → `--apply` -- [ ] Проверить сервис через HAProxy снаружи и health-check в логе HAProxy - -**Критерий:** с другого LAN-хоста (не `.117`) `curl https://:` **не** отвечает; с HAProxy — работает. +По одному хосту, см. [backend-hosts.md](backend-hosts.md): Zabbix → Git → SAC → Synology → ERP → RDS → Exchange. --- -## Фаза 4 — MikroTik и мониторинг +## Фаза 4 — KSMG (позже) -- [ ] На 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`. +Периметровый **25** на `.57` — отдельный UFW и политика relay. HAProxy к inbound-почте не привязан. --- -## Фаза 5 — Усиление (по приоритету) +## Фаза 5 — Усиление (позже) -| Задача | Зачем | -|--------|--------| -| Allowlist или rate-limit для **Exchange** | Сейчас 443 без IP-фильтра | -| Ограничить **sac-api** (если бизнес позволяет) | Сейчас без allowlist | -| TLS verify на бэкендах HAProxy | Вместо `verify none` — внутренняя CA | -| Отдельный VLAN для DMZ / HAProxy | Сегментация | -| WAF / reverse proxy HTTP для Zabbix → HTTPS | Порт 80 только HTTP | -| 2FA на Git, OWA, RD Gateway | Учётные записи | +Allowlist Exchange/sac-api, TLS verify на бэкендах, мониторинг UFW/fail2ban в Zabbix. ---- - -## Окна и коммуникация - -| Фаза | Риск | Окно | -|------|------|------| -| 1 | Низкий | любое | -| 2 | **SSH lockout** | рабочее время + console | -| 3 | Обрыв сервиса при ошибке порта | вечер / по сервису | -| 4 | Ошибка NAT | согласование с сетью | - -После каждой фазы — отметки в [checklist.md](checklist.md). +| Фаза | Риск | +|------|------| +| 1 | низкий | +| 2 | **SSH lockout** — вторая сессия + console | +| 3+ | обрыв сервиса при ошибке порта | diff --git a/docs/security/haproxy-host.md b/docs/security/haproxy-host.md index ea9159c..5ccde56 100644 --- a/docs/security/haproxy-host.md +++ b/docs/security/haproxy-host.md @@ -1,143 +1,93 @@ -# Hardening хоста HAProxy +# HAProxy: UFW и hardening Хост: **haproxy.kalinamall.ru**, **192.168.160.117**, Ubuntu 24.04, HAProxy 2.8.x. -## Слушающие порты (prod) +## Порты на хосте -| Порт | Frontend | Назначение | -|------|----------|------------| -| 22 | sshd | Администрирование (не в haproxy.cfg) | -| 25 | fe_smtp | SMTP → KSMG (**LAN**; с интернета не проброшен) | -| 80 | fe_http_zabbix | HTTP Zabbix | -| 443 | fe_https_sni | TLS passthrough по SNI | +| Порт | Служба | С интернета (MikroTik) | UFW (текущий план) | +|------|--------|------------------------|---------------------| +| 22 | sshd | нет DNAT | только `SSH_ALLOWED` | +| 25 | fe_smtp → KSMG | **нет** (25 → `.57`) | **закрыт** | +| 80 | fe_http_zabbix | да | все | +| 443 | fe_https_sni | да | все | +| 8404 | stats (закомментирован) | нет | только `SSH_ALLOWED` | -Не включать **8404/stats** на `0.0.0.0` без VPN и пароля. - -## Периметр (MikroTik) - -С **интернета** на публичный IP HAProxy проброшены только **TCP 80** и **443**. Остальные порты HAProxy (в т.ч. **25**) снаружи **недоступны** — DNAT не настроен. - -## UFW +## UFW — правила Скрипт: [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh). -Политика: - ``` default incoming: deny default outgoing: allow -allow 22/tcp from SSH_ALLOWED -allow 80, 443/tcp from anywhere # сервисы периметра (MikroTik DNAT) -# allow 25/tcp from LAN_CIDR # только если нужен SMTP через HAProxy изнутри сети + +22/tcp from 5.100.81.121, 192.168.128.0/17 +8404/tcp from 5.100.81.121, 192.168.128.0/17 # если включите fe_stats +80/tcp from anywhere +443/tcp from anywhere ``` -**SSH_ALLOWED** — несколько CIDR/ IP через пробел в env. Минимум: +`192.168.128.0/17` покрывает офисную/VPN-подсеть (в т.ч. `192.168.160.0/24`). -- подсеть админов / VPN; -- LAN `192.168.160.0/24` (если SSH только изнутри); -- **не** `0.0.0.0/0` без крайней необходимости. +### Env -Перед `--apply` обязательно dry-run и **вторая SSH-сессия**. +`/etc/haproxy/security/haproxy-ufw.env` (из [haproxy-ufw.env.example](../../scripts/security/haproxy-ufw.env.example)): -## SSH +```bash +SSH_ALLOWED="5.100.81.121 192.168.128.0/17" +INBOUND_TCP="80 443" +STATS_TCP="8404" +UFW_IPV6=no +``` -Пример drop-in `/etc/ssh/sshd_config.d/99-hardening.conf`: +### Внедрение + +```bash +sudo mkdir -p /etc/haproxy/security +sudo cp ~/reverse-proxy/scripts/security/haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env +sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh +# вторая SSH-сессия: +sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh --apply +sudo ufw status numbered +``` + +**Откат:** `sudo ufw disable` (временно). + +### Проверка + +- SSH с `5.100.81.121` или из `192.168.128.0/17` — OK. +- SSH с другого IP — timeout/refused. +- `curl -I http://zabbix.kalinamall.ru` / HTTPS на allowlist-сервис — OK. +- `ss -tlnp | grep :25` — слушает HAProxy, но UFW + MikroTik не пускают с периметра. + +## fe_smtp (:25) + +Inbound-почта с интернета идёт на **KSMG `.57`**, не на HAProxy. `fe_smtp` в `haproxy.cfg` можно оставить для LAN или **отключить**, если не используется — UFW его не открывает. + +## stats (:8404) + +В `haproxy.cfg` закомментирован. Если включите — UFW уже ограничит доступ списком `SSH_ALLOWED`; обязательно `stats auth` и не `0.0.0.0` без нужды. + +## Рекомендуется дополнительно (не блокирует UFW) + +| Мера | Зачем | +|------|--------| +| SSH: ключи, `PermitRootLogin no`, `PasswordAuthentication no` | до/вместе с UFW | +| fail2ban на sshd | подстраховка SSH | +| `unattended-upgrades` | патчи | +| Не открывать лишние listen | `ss -tlnp` | + +Опционально позже: sysctl syncookies, rate-limit на frontend, allowlist для Exchange/sac-api. + +### SSH (пример) + +`/etc/ssh/sshd_config.d/99-hardening.conf`: ``` PermitRootLogin no PasswordAuthentication no -KbdInteractiveAuthentication no PubkeyAuthentication yes MaxAuthTries 3 AllowUsers papatramp ``` -После правок: `sshd -t && systemctl reload sshd`. - -## fail2ban - -```bash -sudo apt install -y fail2ban -``` - -`/etc/fail2ban/jail.d/sshd.local`: - -```ini -[sshd] -enabled = true -port = ssh -filter = sshd -logpath = /var/log/auth.log -maxretry = 5 -findtime = 600 -bantime = 3600 -``` - -`systemctl enable --now fail2ban`, проверка: `fail2ban-client status sshd`. - -## HAProxy - -### Уже в конфиге - -- Reject неизвестного SNI. -- Allowlist по сервисам. -- Длинные timeout для RDP/Exchange — осознанно; не занижать без теста. - -### Рекомендуется добавить (отдельный PR / окно) - -1. **Лимит соединений** (global уже `maxconn 50000`; при атаке снизить или ограничить на frontend): - - ``` - # frontend fe_https_sni - # maxconn 10000 - ``` - -2. **Exchange / sac-api без allowlist** — рассмотреть: - - отдельный allowlist; - - или `tcp-request content reject` при превышении conn rate (HAProxy 2.8+: stick-table / sc1 — сложнее в TCP+SNI). - -3. **Логирование отказов** — grep `SC` / `` / короткие сессии в SIEM. - -4. **`ssl verify required`** на бэкендах после выпуска внутренней CA. - -### systemd - -```bash -systemctl enable haproxy -# после reload: systemctl status haproxy -``` - -## sysctl (опционально) - -`/etc/sysctl.d/99-haproxy-security.conf`: - -``` -net.ipv4.tcp_syncookies = 1 -net.ipv4.conf.all.rp_filter = 1 -net.ipv4.conf.default.rp_filter = 1 -net.ipv4.icmp_echo_ignore_broadcasts = 1 -``` - -`sysctl --system` - -## Обновления - -```bash -sudo apt install -y unattended-upgrades -sudo dpkg-reconfigure -plow unattended-upgrades -``` - -Исключить автоперезапуск HAProxy в неудобное окно — настроить `Unattended-Upgrade::Automatic-Reboot` по политике. - -## Права и секреты - -- Allowlist и `haproxy.cfg` — root, без world-readable секретов (в репо секретов нет). -- `/etc/haproxy/security/` — `0750 root:root`. -- Не коммитить `haproxy-ufw.env` с реальными IP в публичный GitHub (в kalinamall — по политике repo). - -## Мониторинг - -- HAProxy backend **DOWN** / **L4CON**. -- Рост отклонённых TCP (`SC`, reject по ACL). -- `ufw status numbered`, диск под логами. -- fail2ban bans. +`sshd -t && systemctl reload sshd` diff --git a/scripts/security/haproxy-ufw-apply.sh b/scripts/security/haproxy-ufw-apply.sh index 64b8d82..c8025a4 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 80/443 (25 — опционально для LAN). +# UFW для HAProxy: SSH и stats из SSH_ALLOWED, 80/443 для всех. # Dry-run по умолчанию; --apply для включения. # # cp haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env @@ -38,6 +38,7 @@ source "$ENV_FILE" SSH_ALLOWED=${SSH_ALLOWED:-} INBOUND_TCP=${INBOUND_TCP:-80 443} +STATS_TCP=${STATS_TCP:-8404} UFW_IPV6=${UFW_IPV6:-no} [[ -n $SSH_ALLOWED ]] || die "SSH_ALLOWED is empty — lockout risk" @@ -63,6 +64,9 @@ run ufw default allow outgoing for cidr in $SSH_ALLOWED; do run ufw allow from "$cidr" to any port 22 proto tcp comment 'admin ssh' + for port in $STATS_TCP; do + run ufw allow from "$cidr" to any port "$port" proto tcp comment 'haproxy stats' + done done for port in $INBOUND_TCP; do diff --git a/scripts/security/haproxy-ufw.env.example b/scripts/security/haproxy-ufw.env.example index a344abe..ce2e943 100644 --- a/scripts/security/haproxy-ufw.env.example +++ b/scripts/security/haproxy-ufw.env.example @@ -1,14 +1,13 @@ -# UFW для haproxy.kalinamall.ru — пример. -# Копия: /etc/haproxy/security/haproxy-ufw.env (не коммитить в GitHub с prod-IP при необходимости) +# UFW для haproxy.kalinamall.ru +# Копия: /etc/haproxy/security/haproxy-ufw.env -# IP/CIDR для SSH (через пробел). Обязательно перед --apply. -SSH_ALLOWED="192.168.160.0/24 5.100.81.121" +# SSH и stats — только эти IP/CIDR (через пробел) +SSH_ALLOWED="5.100.81.121 192.168.128.0/17" -# Публичные сервисы HAProxy с периметра (пробел). MikroTik DNAT: только 80 и 443. +# С периметра (MikroTik DNAT) — для всех INBOUND_TCP="80 443" -# Добавьте 25, если нужен SMTP через HAProxy только из LAN (не с интернета): -# INBOUND_TCP="80 443 25" +# HAProxy stats (если fe_stats включён в haproxy.cfg) +STATS_TCP="8404" -# IPv6: yes | no UFW_IPV6=no