docs(security): периметр 80/443→HAProxy, 25→KSMG; UFW на .117

Переписаны docs/security под текущий этап: SSH/stats из SSH_ALLOWED,
80/443 для всех. Скрипт UFW дополнен правилом 8404.
This commit is contained in:
Andrey Lutsenko
2026-06-13 22:34:57 +10:00
parent e3201ae41b
commit 8e2a4ac1b9
9 changed files with 259 additions and 406 deletions
+24 -38
View File
@@ -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`) и бэкендов за ним.
## Модель угроз (кратко) ## Периметр
| Уровень | Что закрывает | - **80/443** с интернета → HAProxy `.117` → бэкенды (ACL в `haproxy.cfg`).
|---------|----------------| - **25** с интернета → **KSMG** `.57` напрямую (мимо HAProxy).
| **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С |
Обход 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 |
| Файл | Содержание | **Позже** (не в текущем scope): [backend-hosts.md](backend-hosts.md), UFW на KSMG и бэкендах.
|------|------------|
| [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) | Проверки после внедрения |
## Скрипты ## Скрипты
| Скрипт | Назначение |
|--------|------------|
| [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 ```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 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 # dry-run
sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh --apply # вторая SSH-сессия!
sudo bash ~/reverse-proxy/scripts/security/haproxy-ufw-apply.sh --apply
``` ```
На каждом бэкенде — [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`.
Сервисы **без** allowlist на 443: **Exchange** (`ext`, `autodiscover`), **sac-api**. **SMTP 25** с интернета на HAProxy **не проброшен** (см. [architecture.md](architecture.md)); `fe_smtp` — для LAN/внутреннего использования.
+24 -26
View File
@@ -1,37 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 500" role="img" aria-label=оток трафика: Интернет → MikroTik → HAProxy → ACL → бэкенды"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 340" role="img" aria-label=ериметр: 80/443 на HAProxy, 25 на KSMG">
<defs> <defs>
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto"> <marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0,0 10,3.5 0,7" fill="#555"/> <polygon points="0,0 8,3 0,6" fill="#555"/>
</marker> </marker>
</defs> </defs>
<style> <style>
.box { fill: #f6f8fa; stroke: #24292f; stroke-width: 1.5; } .box { fill: #f6f8fa; stroke: #24292f; stroke-width: 1.5; }
.text { fill: #24292f; font: 14px system-ui, -apple-system, sans-serif; text-anchor: middle; } .text { fill: #24292f; font: 13px system-ui, sans-serif; text-anchor: middle; }
.edge { stroke: #57606a; stroke-width: 1.5; fill: none; marker-end: url(#arrow); } .edge { stroke: #57606a; stroke-width: 1.5; fill: none; marker-end: url(#arrow); }
.edge-label { fill: #57606a; font: 12px system-ui, -apple-system, sans-serif; text-anchor: middle; } .label { fill: #57606a; font: 11px system-ui, sans-serif; text-anchor: middle; }
.title { fill: #24292f; font: bold 12px system-ui, sans-serif; text-anchor: middle; }
</style> </style>
<rect class="box" x="95" y="10" width="110" height="40" rx="6"/> <text class="title" x="210" y="18">Интернет</text>
<text class="text" x="150" y="35">Интернет</text>
<line class="edge" x1="150" y1="50" x2="150" y2="78"/> <line class="edge" x1="130" y1="28" x2="130" y2="52"/>
<text class="label" x="130" y="46">80/443</text>
<rect class="box" x="55" y="52" width="150" height="32" rx="5"/>
<text class="text" x="130" y="73">MikroTik DNAT</text>
<line class="edge" x1="130" y1="84" x2="130" y2="108"/>
<rect class="box" x="40" y="108" width="180" height="32" rx="5"/>
<text class="text" x="130" y="129">HAProxy .117</text>
<line class="edge" x1="130" y1="140" x2="130" y2="164"/>
<rect class="box" x="25" y="164" width="210" height="32" rx="5"/>
<text class="text" x="130" y="185">ACL → бэкенды .160.0/24</text>
<rect class="box" x="45" y="78" width="210" height="40" rx="6"/> <line class="edge" x1="290" y1="28" x2="290" y2="52"/>
<text class="text" x="150" y="103">MikroTik / периметр</text> <text class="label" x="290" y="46">25</text>
<rect class="box" x="215" y="52" width="150" height="32" rx="5"/>
<line class="edge" x1="150" y1="118" x2="150" y2="146"/> <text class="text" x="290" y="73">MikroTik DNAT</text>
<text class="edge-label" x="150" y="136">DNAT публичный IP</text> <line class="edge" x1="290" y1="84" x2="290" y2="108"/>
<rect class="box" x="230" y="108" width="120" height="32" rx="5"/>
<rect class="box" x="20" y="146" width="260" height="40" rx="6"/> <text class="text" x="290" y="129">KSMG .57</text>
<text class="text" x="150" y="171">HAProxy 192.168.160.117 (80 443)</text>
<line class="edge" x1="150" y1="186" x2="150" y2="214"/>
<rect class="box" x="35" y="214" width="230" height="40" rx="6"/>
<text class="text" x="150" y="239">HAProxy ACL: SNI / Host allowlist</text>
<line class="edge" x1="150" y1="254" x2="150" y2="282"/>
<rect class="box" x="15" y="282" width="270" height="40" rx="6"/>
<text class="text" x="150" y="307">Бэкенды 192.168.160.0/24</text>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

+42 -45
View File
@@ -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) ![Поток трафика](architecture-flow.svg)
Схема в SVG — без Mermaid, чтобы Gitea/Firefox не ломали рендер из‑за `<br>` в XML.
Та же схема текстом:
```text ```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. ## Текущий этап: UFW на HAProxy
- **80/HTTP**: только `Host: zabbix.kalinamall.ru` + `zabbix-allowed`.
**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`; | Угроза | MikroTik | UFW HAProxy | HAProxy cfg | UFW бэкенд* | Приложение |
- бэкенд слушает на 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 бэкенд | Приложение |
|--------|----------|-------------|-------------|------------|------------|
| Сканирование портов | ✓ | ✓ | — | ✓ | — |
| Неверный SNI / Host | — | — | ✓ | — | — | | Неверный SNI / Host | — | — | ✓ | — | — |
| Доступ не из allowlist | — | — | ✓ | — | ✓ | | Не из allowlist | — | — | ✓ | — | ✓ |
| Прямой удар в бэкенд | | — | — | ✓ | — | | Прямой удар в бэкенд | частично | — | — | ✓ | — |
| Brute SSH на HAProxy | | ✓ | — | — | fail2ban | | Brute SSH | | ✓ | — | — | fail2ban |
| DDoS L4 | | частично | conn limits* | | | | 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). 1. **Default deny** на вход (UFW `incoming deny`).
2. **Минимум открытых портов** с интернета на HAProxy: **80** и **443** (DNAT на MikroTik). 2. Периметр на MikroTik — источник истины для DNAT; UFW на HAProxy — defense in depth + SSH.
3. **Не дублировать** смысл правил без нужды: allowlist в HAProxy для HTTP/SNI; UFW — транспорт и админ. 3. Allowlist в `haproxy.cfg` для 443/80; UFW не дублирует SNI/Host.
4. **Сначала dry-run**, потом `--apply`; для SSH — **console/IPMI** или сессия, которую UFW не оборвёт. 4. UFW: сначала dry-run, потом `--apply`; держать вторую SSH-сессию.
5. **Логи**: HAProxy + UFW + fail2ban; алерты на аномалии (Zabbix уже в инфраструктуре).
+33 -68
View File
@@ -1,90 +1,55 @@
# Firewall на бэкендах # 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). Скрипт: [backend-ufw-from-haproxy.sh](../../scripts/security/backend-ufw-from-haproxy.sh).
## Таблица бэкендов ## Бэкенды за HAProxy
| Сервис | IP | Порт | Протокол | HAProxy backend | Примечание | | Сервис | IP | Порт | HAProxy backend |
|--------|-----|------|----------|-----------------|------------| |--------|-----|------|-----------------|
| Exchange | 192.168.160.42 | 443 | TCP | be_exchange | OWA, ActiveSync | | Exchange | 192.168.160.42 | 443 | be_exchange |
| RDS Gateway | 192.168.160.40 | **4430** | TCP | be_rds | не 443 | | RDS Gateway | 192.168.160.40 | 4430 | be_rds |
| 1С ERP | 192.168.160.150 | 443 | TCP | be_erp | | | 1С ERP | 192.168.160.150 | 443 | be_erp |
| Git (Gitea) | 192.168.160.129 | 443 | TCP | be_git | SSH Gitea 2222 — отдельно | | Git (Gitea) | 192.168.160.129 | 443 | be_git |
| SAC | 192.168.160.145 | 443 | TCP | be_sac | | | SAC | 192.168.160.145 | 443 | be_sac |
| Zabbix | 192.168.160.53 | **80** | TCP | be_zabbix | HTTP | | Zabbix | 192.168.160.53 | 80 | be_zabbix |
| Synology heap | 192.168.160.185 | **5001** | TCP | be_syno_heap | DSM HTTPS | | Synology heap | 192.168.160.185 | 5001 | be_syno_heap |
| Synology exchange | 192.168.160.35 | **5001** | TCP | be_syno_exchange | | | Synology exchange | 192.168.160.35 | 5001 | be_syno_exchange |
| KSMG | 192.168.160.57 | 25 | TCP | be_ksmg_smtp | см. ниже |
## Шаблон правил (логика) `fe_smtp` в HAProxy — опционально для LAN; inbound **25** с интернета на **KSMG**, не через `.117`.
``` ## KSMG (192.168.160.57) — отдельно от HAProxy
allow from 192.168.160.117 to <PORT>
allow from ADMIN_ALLOWED to 22/tcp # SSH, если на этом хосте
allow from LAN to ... # только если нужен прямой доступ (минимизировать)
deny incoming (default)
```
### Пример env для Git (192.168.160.129) | Путь | DNAT |
|------|------|
| Интернет → SMTP | MikroTik **25**`.57` |
| HAProxy → KSMG | только если используется `fe_smtp` из LAN |
При UFW на KSMG: разрешить **25** с интернета (или по списку MTA), **не** полагаться на HAProxy.
## Шаблон (Linux)
```bash ```bash
HAPROXY_IP=192.168.160.117 HAPROXY_IP=192.168.160.117
BACKEND_PORT=443 BACKEND_PORT=443
ADMIN_ALLOWED="192.168.160.0/24" ADMIN_ALLOWED="192.168.128.0/17"
UFW_EXTRA_SERVICES="2222/tcp" # если Gitea SSH с admin LAN
``` ```
## Особые случаи
### 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 ```bash
sudo mkdir -p /etc/haproxy/security sudo bash backend-ufw-from-haproxy.sh # dry-run
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 --apply 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): С HAProxy — сервис OK. С другого LAN IP на `https://192.168.160.129/` — refused/timeout.
```bash
curl -kI --connect-timeout 3 https://192.168.160.129/ # ожидается timeout/refused
```
В логе HAProxy — backend **UP**.
+24 -31
View File
@@ -1,44 +1,37 @@
# Чеклист после внедрения защиты # Чеклист
## HAProxy (192.168.160.117) ## UFW на HAProxy (текущий этап)
- [ ] `ufw status verbose` — active; есть 22 из admin, **80/443** open (25 — только если явно включён для LAN) - [ ] `ufw status verbose` — active
- [ ] SSH с admin-IP работает; с random IP — **нет** (или только ключ + fail2ban) - [ ] **22/tcp** только `5.100.81.121`, `192.168.128.0/17`
- [ ] `ss -tlnp` — нет лишних listen (8404, 3000, …) - [ ] **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 - [ ] `haproxy -c -f /etc/haproxy/haproxy.cfg` — OK
- [ ] С allowlist IP: git, rds, zabbix, erp, syno — **OK** - [ ] `/etc/haproxy/security/haproxy-ufw.env` — backup
- [ ] С чужого IP: git/rds/erp — **reject** (нет TCP reset или обрыв на ACL)
- [ ] `fail2ban-client status sshd` — active
- [ ] Логи: `grep -E 'SC|NOSRV|reject' /var/log/haproxy.log | tail`
## MikroTik / периметр ## MikroTik
- [ ] С интернета на публичный IP: открыты только **80, 443** - [ ] DNAT **80/443**`192.168.160.117`
- [ ] Нет DNAT 3389, 5001, 4430 на внутренние хосты - [ ] DNAT **25**`192.168.160.57` (не на HAProxy)
- [ ] HAProxy health-check не блокируется - [ ] Нет DNAT 3389, 5001, 4430 на внутренние IP
## Бэкенды (каждый) ## После UFW (опционально, не блокер)
- [ ] UFW/firewall active - [ ] fail2ban sshd
- [ ] С `.117` на сервисный порт — OK - [ ] `unattended-upgrades`
- [ ] С другого LAN IP на сервисный порт — **closed** (если так задумано)
- [ ] HAProxy backend **UP** в stats/log
- [ ] Прикладной smoke-test (OWA, RD Web, Gitea login, …)
## 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 - [ ] `apt upgrade` на HAProxy
- [ ] Проверка актуальности allowlist / `rds-allowed` cron - [ ] Актуальность allowlist / `rds-allowed`
- [ ] Review fail2ban / auth.log - [ ] `ufw status`, auth.log
- [ ] Тест restore конфига из git
+33 -72
View File
@@ -1,100 +1,61 @@
# План развёртывания защиты # План внедрения
Оценка: **24 рабочих дня** с окнами обслуживания на бэкендах (по одному хосту). **Текущая остановка:** UFW на HAProxy (фаза 2). Бэкенды и KSMG — позже.
## Фаза 0 — Подготовка (без изменений на проде) ## Фаза 0 — Подготовка
- [ ] Зафиксировать **публичный IP** HAProxy и правила DNAT на MikroTik (**только 80/443**). - [ ] Зафиксировать DNAT на MikroTik:
- [ ] Убедиться, что **SSH** на `192.168.160.117` доступен с admin-IP (не только с одного ноутбука). - `80`, `443``192.168.160.117`
- [ ] Снимок/backup: `haproxy.cfg`, allowlist, `/etc/ssh/sshd_config`. - `25``192.168.160.57` (KSMG)
- [ ] Клонировать репозиторий на HAProxy: `git pull kalinamall main`. - [ ] SSH на `.117` работает с `5.100.81.121` или `192.168.128.0/17`
- [ ] Прочитать [checklist.md](checklist.md). - [ ] Backup: `haproxy.cfg`, allowlist, `sshd_config`
- [ ] `git pull` на HAProxy
**Критерий готовности:** есть консоль или второй admin-IP для отката UFW. - [ ] Консоль/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). **Проверка:** `ss -tlnp` — ожидаемо 22, 80, 443; возможно 25.
- [ ] 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 если локально).
--- ---
## Фаза 2 — UFW на HAProxy ## Фаза 2 — UFW на HAProxy ← **СТОП ЗДЕСЬ**
- [ ] `cp scripts/security/haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env` - [ ] `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` - [ ] Dry-run: `bash scripts/security/haproxy-ufw-apply.sh`
- [ ] **Не закрывая SSH-сессию**, в second tab: `bash scripts/security/haproxy-ufw-apply.sh --apply` - [ ] **Вторая SSH-сессия:** `bash scripts/security/haproxy-ufw-apply.sh --apply`
- [ ] Проверить с admin-IP: `ssh haproxy.kalinamall.ru`, `curl` на 80/443 с allowlist. - [ ] [checklist.md](checklist.md) — HAProxy
- [ ] Убедиться, что **25** с интернета на HAProxy **не** доступен (ожидаемо: DNAT не настроен).
**Откат:** `ufw disable` (временно) или `ufw delete` по номерам правил; env-файл сохранить. **Откат:** `ufw disable`
--- ---
## Фаза 3 — UFW на бэкендах (по одному хосту) ## Фаза 3 — UFW на бэкендах (позже)
Порядок рекомендуемый (от менее критичных к более): По одному хосту, см. [backend-hosts.md](backend-hosts.md): Zabbix → Git → SAC → Synology → ERP → RDS → Exchange.
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://<backend-ip>:<port>` **не** отвечает; с HAProxy — работает.
--- ---
## Фаза 4 — MikroTik и мониторинг ## Фаза 4 — KSMG (позже)
- [ ] На MikroTik: с интернета на HAProxy только **80, 443** (не пробрасывать 25, 3389, 5001, 4430 на внутренние IP). Периметровый **25** на `.57` — отдельный UFW и политика relay. HAProxy к inbound-почте не привязан.
- [ ] Raw/firewall: опционально drop obvious scanners (согласовать с `rds-allowed` / RU geo).
- [ ] Zabbix: триггеры на `ufw status`, `fail2ban`, рост `fe_https_sni` reject, backend DOWN.
- [ ] Ротация логов `/var/log/haproxy.log`.
--- ---
## Фаза 5 — Усиление (по приоритету) ## Фаза 5 — Усиление (позже)
| Задача | Зачем | Allowlist Exchange/sac-api, TLS verify на бэкендах, мониторинг UFW/fail2ban в Zabbix.
|--------|--------|
| 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 | Учётные записи |
--- | Фаза | Риск |
|------|------|
## Окна и коммуникация | 1 | низкий |
| 2 | **SSH lockout** — вторая сессия + console |
| Фаза | Риск | Окно | | 3+ | обрыв сервиса при ошибке порта |
|------|------|------|
| 1 | Низкий | любое |
| 2 | **SSH lockout** | рабочее время + console |
| 3 | Обрыв сервиса при ошибке порта | вечер / по сервису |
| 4 | Ошибка NAT | согласование с сетью |
После каждой фазы — отметки в [checklist.md](checklist.md).
+67 -117
View File
@@ -1,143 +1,93 @@
# Hardening хоста HAProxy # HAProxy: UFW и hardening
Хост: **haproxy.kalinamall.ru**, **192.168.160.117**, Ubuntu 24.04, HAProxy 2.8.x. Хост: **haproxy.kalinamall.ru**, **192.168.160.117**, Ubuntu 24.04, HAProxy 2.8.x.
## Слушающие порты (prod) ## Порты на хосте
| Порт | Frontend | Назначение | | Порт | Служба | С интернета (MikroTik) | UFW (текущий план) |
|------|----------|------------| |------|--------|------------------------|---------------------|
| 22 | sshd | Администрирование (не в haproxy.cfg) | | 22 | sshd | нет DNAT | только `SSH_ALLOWED` |
| 25 | fe_smtp | SMTP → KSMG (**LAN**; с интернета не проброшен) | | 25 | fe_smtp → KSMG | **нет** (25 → `.57`) | **закрыт** |
| 80 | fe_http_zabbix | HTTP Zabbix | | 80 | fe_http_zabbix | да | все |
| 443 | fe_https_sni | TLS passthrough по SNI | | 443 | fe_https_sni | да | все |
| 8404 | stats (закомментирован) | нет | только `SSH_ALLOWED` |
Не включать **8404/stats** на `0.0.0.0` без VPN и пароля. ## UFW — правила
## Периметр (MikroTik)
С **интернета** на публичный IP HAProxy проброшены только **TCP 80** и **443**. Остальные порты HAProxy (в т.ч. **25**) снаружи **недоступны** — DNAT не настроен.
## UFW
Скрипт: [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh). Скрипт: [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh).
Политика:
``` ```
default incoming: deny default incoming: deny
default outgoing: allow default outgoing: allow
allow 22/tcp from SSH_ALLOWED
allow 80, 443/tcp from anywhere # сервисы периметра (MikroTik DNAT) 22/tcp from 5.100.81.121, 192.168.128.0/17
# allow 25/tcp from LAN_CIDR # только если нужен SMTP через HAProxy изнутри сети 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; ### Env
- LAN `192.168.160.0/24` (если SSH только изнутри);
- **не** `0.0.0.0/0` без крайней необходимости.
Перед `--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 PermitRootLogin no
PasswordAuthentication no PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes PubkeyAuthentication yes
MaxAuthTries 3 MaxAuthTries 3
AllowUsers papatramp AllowUsers papatramp
``` ```
После правок: `sshd -t && systemctl reload sshd`. `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` / `<NOSRV>` / короткие сессии в 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.
+5 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/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 для включения. # Dry-run по умолчанию; --apply для включения.
# #
# cp haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env # cp haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env
@@ -38,6 +38,7 @@ source "$ENV_FILE"
SSH_ALLOWED=${SSH_ALLOWED:-} SSH_ALLOWED=${SSH_ALLOWED:-}
INBOUND_TCP=${INBOUND_TCP:-80 443} INBOUND_TCP=${INBOUND_TCP:-80 443}
STATS_TCP=${STATS_TCP:-8404}
UFW_IPV6=${UFW_IPV6:-no} UFW_IPV6=${UFW_IPV6:-no}
[[ -n $SSH_ALLOWED ]] || die "SSH_ALLOWED is empty — lockout risk" [[ -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 for cidr in $SSH_ALLOWED; do
run ufw allow from "$cidr" to any port 22 proto tcp comment 'admin ssh' 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 done
for port in $INBOUND_TCP; do for port in $INBOUND_TCP; do
+7 -8
View File
@@ -1,14 +1,13 @@
# UFW для haproxy.kalinamall.ru — пример. # UFW для haproxy.kalinamall.ru
# Копия: /etc/haproxy/security/haproxy-ufw.env (не коммитить в GitHub с prod-IP при необходимости) # Копия: /etc/haproxy/security/haproxy-ufw.env
# IP/CIDR для SSH (через пробел). Обязательно перед --apply. # SSH и stats — только эти IP/CIDR (через пробел)
SSH_ALLOWED="192.168.160.0/24 5.100.81.121" SSH_ALLOWED="5.100.81.121 192.168.128.0/17"
# Публичные сервисы HAProxy с периметра (пробел). MikroTik DNAT: только 80 и 443. # С периметра (MikroTik DNAT) — для всех
INBOUND_TCP="80 443" INBOUND_TCP="80 443"
# Добавьте 25, если нужен SMTP через HAProxy только из LAN (не с интернета): # HAProxy stats (если fe_stats включён в haproxy.cfg)
# INBOUND_TCP="80 443 25" STATS_TCP="8404"
# IPv6: yes | no
UFW_IPV6=no UFW_IPV6=no