docs(security): план UFW, hardening HAProxy и бэкендов, скрипты deploy.
This commit is contained in:
@@ -7,6 +7,8 @@ zabbix-allowed text eol=lf
|
|||||||
syno-allowed text eol=lf
|
syno-allowed text eol=lf
|
||||||
rds-allowed-static text eol=lf
|
rds-allowed-static text eol=lf
|
||||||
scripts/*.sh text eol=lf
|
scripts/*.sh text eol=lf
|
||||||
|
scripts/security/*.sh text eol=lf
|
||||||
|
scripts/security/*.example text eol=lf
|
||||||
scripts/*.cron text eol=lf
|
scripts/*.cron text eol=lf
|
||||||
scripts/haproxy_ru_ipdeny_lib.sh text eol=lf
|
scripts/haproxy_ru_ipdeny_lib.sh text eol=lf
|
||||||
.gitignore text eol=lf
|
.gitignore text eol=lf
|
||||||
|
|||||||
@@ -38,3 +38,4 @@ DNS: **A** для `ext`, `autodiscover`, `rds`, `erp`, `git`, `sac`, **`sac-api`
|
|||||||
- [Работа с Git: clone, pull, push, ключи и PAT](docs/git.md)
|
- [Работа с Git: clone, pull, push, ключи и PAT](docs/git.md)
|
||||||
- [Политика: Gitea — основной, GitHub — резерв без секретов](docs/repo-policy.md)
|
- [Политика: Gitea — основной, GitHub — резерв без секретов](docs/repo-policy.md)
|
||||||
- [Let's Encrypt / ACME через HAProxy (порт 80)](docs/letsencrypt-acme.md)
|
- [Let's Encrypt / ACME через HAProxy (порт 80)](docs/letsencrypt-acme.md)
|
||||||
|
- [Безопасность: UFW, бэкенды, план внедрения](docs/security/README.md)
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ sudo haproxy -c -f /etc/haproxy/haproxy.cfg
|
|||||||
|
|
||||||
На хосте с HAProxy с интернета обычно открывают только то, что слушает конфиг: **25**, **80** (Zabbix HTTP), **443** (и при использовании — **8404** для stats). Остальной трафик — по политике безопасности.
|
На хосте с HAProxy с интернета обычно открывают только то, что слушает конфиг: **25**, **80** (Zabbix HTTP), **443** (и при использовании — **8404** для stats). Остальной трафик — по политике безопасности.
|
||||||
|
|
||||||
|
**Полный план:** [docs/security/README.md](security/README.md) — UFW, бэкенды, чеклист, скрипты `scripts/security/`.
|
||||||
|
|
||||||
Порт **80** используется для **`http://zabbix.kalinamall.ru`** (allowlist `zabbix-allowed`). Для ACME на других именах см. [letsencrypt-acme.md](letsencrypt-acme.md) — потребуется расширить `fe_http_zabbix` маршрутизацией по Host/пути.
|
Порт **80** используется для **`http://zabbix.kalinamall.ru`** (allowlist `zabbix-allowed`). Для ACME на других именах см. [letsencrypt-acme.md](letsencrypt-acme.md) — потребуется расширить `fe_http_zabbix` маршрутизацией по Host/пути.
|
||||||
|
|
||||||
## SAC и Seaca (sac-api)
|
## SAC и Seaca (sac-api)
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Безопасность периметра HAProxy (kalinamall.ru)
|
||||||
|
|
||||||
|
План защиты хоста **haproxy.kalinamall.ru** и **бэкендов** за ним. HAProxy слушает **25**, **80**, **443**; фильтрация по SNI/Host и allowlist уже в `haproxy.cfg`, но **UFW на хосте inactive** — ниже поэтапное включение **defense in depth**.
|
||||||
|
|
||||||
|
## Модель угроз (кратко)
|
||||||
|
|
||||||
|
| Уровень | Что закрывает |
|
||||||
|
|---------|----------------|
|
||||||
|
| **MikroTik / провайдер** | DDoS, geo, лишние порты до DMZ |
|
||||||
|
| **UFW на HAProxy** | Вход только 25/80/443 + SSH с доверенных IP |
|
||||||
|
| **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).
|
||||||
|
|
||||||
|
## Документы
|
||||||
|
|
||||||
|
| Файл | Содержание |
|
||||||
|
|------|------------|
|
||||||
|
| [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
|
||||||
|
# на HAProxy — сначала dry-run
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
На каждом бэкенде — [backend-hosts.md](backend-hosts.md) и `backend-ufw-from-haproxy.sh`.
|
||||||
|
|
||||||
|
## Связь с конфигом HAProxy
|
||||||
|
|
||||||
|
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)).
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Архитектура защиты
|
||||||
|
|
||||||
|
## Поток трафика
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
Internet[Интернет]
|
||||||
|
MT[MikroTik / периметр]
|
||||||
|
HAP[HAProxy 192.168.160.117<br/>25 80 443]
|
||||||
|
ACL[HAProxy ACL<br/>SNI Host allowlist]
|
||||||
|
BE[Бэкенды 192.168.160.0/24]
|
||||||
|
|
||||||
|
Internet --> MT
|
||||||
|
MT -->|DNAT публичный IP| HAP
|
||||||
|
HAP --> ACL
|
||||||
|
ACL --> BE
|
||||||
|
```
|
||||||
|
|
||||||
|
## Два контура
|
||||||
|
|
||||||
|
### 1. Публичный вход (через HAProxy)
|
||||||
|
|
||||||
|
Клиент → публичный IP → **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.
|
||||||
|
|
||||||
|
### 2. Внутренняя сеть (LAN / VPN)
|
||||||
|
|
||||||
|
Трафик **мимо 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 бэкенд | Приложение |
|
||||||
|
|--------|----------|-------------|-------------|------------|------------|
|
||||||
|
| Сканирование портов | ✓ | ✓ | — | ✓ | — |
|
||||||
|
| Неверный SNI / Host | — | — | ✓ | — | — |
|
||||||
|
| Доступ не из allowlist | — | — | ✓ | — | ✓ |
|
||||||
|
| Прямой удар в бэкенд | ✓ | — | — | ✓ | — |
|
||||||
|
| Brute SSH на HAProxy | ✓ | ✓ | — | — | fail2ban |
|
||||||
|
| DDoS L4 | ✓ | частично | conn limits* | — | — |
|
||||||
|
|
||||||
|
\* Опционально: `maxconn`, rate-limit на frontend (Enterprise / custom); на 2.8 — sysctl, MikroTik, внешний scrubbing.
|
||||||
|
|
||||||
|
## Принципы
|
||||||
|
|
||||||
|
1. **Default deny** на вход (UFW `incoming deny`, бэкенды — только HAProxy + admin).
|
||||||
|
2. **Минимум открытых портов** с интернета: 25, 80, 443 на одном хосте.
|
||||||
|
3. **Не дублировать** смысл правил без нужды: allowlist в HAProxy для HTTP/SNI; UFW — транспорт и админ.
|
||||||
|
4. **Сначала dry-run**, потом `--apply`; для SSH — **console/IPMI** или сессия, которую UFW не оборвёт.
|
||||||
|
5. **Логи**: HAProxy + UFW + fail2ban; алерты на аномалии (Zabbix уже в инфраструктуре).
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Firewall на бэкендах
|
||||||
|
|
||||||
|
Цель: сервисный порт доступен **с HAProxy** (`192.168.160.117`) и при необходимости с **admin**-подсетей; **не** с произвольного LAN/Internet напрямую.
|
||||||
|
|
||||||
|
Скрипт: [backend-ufw-from-haproxy.sh](../../scripts/security/backend-ufw-from-haproxy.sh).
|
||||||
|
|
||||||
|
## Таблица бэкендов
|
||||||
|
|
||||||
|
| Сервис | 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 | см. ниже |
|
||||||
|
|
||||||
|
## Шаблон правил (логика)
|
||||||
|
|
||||||
|
```
|
||||||
|
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)
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Особые случаи
|
||||||
|
|
||||||
|
### KSMG (192.168.160.57:25)
|
||||||
|
|
||||||
|
Почта с интернета приходит на **HAProxy:25** → KSMG. На KSMG в `EXTRA_ALLOWED` укажите **192.168.160.117**. Если другие MTA шлют **напрямую** на `.57` (минуя HAProxy), их IP тоже нужно явно разрешить — иначе после 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 --apply
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
С **HAProxy** (или симуляция):
|
||||||
|
|
||||||
|
```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**.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Чеклист после внедрения защиты
|
||||||
|
|
||||||
|
## HAProxy (192.168.160.117)
|
||||||
|
|
||||||
|
- [ ] `ufw status verbose` — active; есть 22 из admin, 25/80/443 open
|
||||||
|
- [ ] SSH с admin-IP работает; с random IP — **нет** (или только ключ + fail2ban)
|
||||||
|
- [ ] `ss -tlnp` — нет лишних listen (8404, 3000, …)
|
||||||
|
- [ ] `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`
|
||||||
|
|
||||||
|
## MikroTik / периметр
|
||||||
|
|
||||||
|
- [ ] С интернета на публичный IP: открыты только **25, 80, 443**
|
||||||
|
- [ ] Нет DNAT 3389, 5001, 4430 на внутренние хосты
|
||||||
|
- [ ] HAProxy health-check не блокируется
|
||||||
|
|
||||||
|
## Бэкенды (каждый)
|
||||||
|
|
||||||
|
- [ ] UFW/firewall active
|
||||||
|
- [ ] С `.117` на сервисный порт — OK
|
||||||
|
- [ ] С другого LAN IP на сервисный порт — **closed** (если так задумано)
|
||||||
|
- [ ] HAProxy backend **UP** в stats/log
|
||||||
|
- [ ] Прикладной smoke-test (OWA, RD Web, Gitea login, …)
|
||||||
|
|
||||||
|
## KSMG (.57)
|
||||||
|
|
||||||
|
- [ ] Почта inbound через HAProxy:25 — OK
|
||||||
|
- [ ] Прямые отправители (если есть) — в `EXTRA_ALLOWED`
|
||||||
|
|
||||||
|
## Документация и runbook
|
||||||
|
|
||||||
|
- [ ] `/etc/haproxy/security/*.env` — backup в password store
|
||||||
|
- [ ] Откат UFW: `ufw disable` + процедура задокументирована
|
||||||
|
- [ ] Ответственный и дата внедрения фикс
|
||||||
|
|
||||||
|
## Регулярно (ежемесячно)
|
||||||
|
|
||||||
|
- [ ] `apt upgrade` на HAProxy
|
||||||
|
- [ ] Проверка актуальности allowlist / `rds-allowed` cron
|
||||||
|
- [ ] Review fail2ban / auth.log
|
||||||
|
- [ ] Тест restore конфига из git
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# План развёртывания защиты
|
||||||
|
|
||||||
|
Оценка: **2–4 рабочих дня** с окнами обслуживания на бэкендах (по одному хосту).
|
||||||
|
|
||||||
|
## Фаза 0 — Подготовка (без изменений на проде)
|
||||||
|
|
||||||
|
- [ ] Зафиксировать **публичный IP** HAProxy и правила DNAT на MikroTik.
|
||||||
|
- [ ] Убедиться, что **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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фаза 1 — HAProxy: базовый hardening (без UFW enable)
|
||||||
|
|
||||||
|
На **haproxy.kalinamall.ru**:
|
||||||
|
|
||||||
|
- [ ] `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 если локально).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фаза 2 — UFW на HAProxy
|
||||||
|
|
||||||
|
- [ ] `cp scripts/security/haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env`
|
||||||
|
- [ ] Заполнить **`SSH_ALLOWED`** (офис, VPN, admin; минимум ваш текущий IP + LAN).
|
||||||
|
- [ ] 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 (если используется).
|
||||||
|
|
||||||
|
**Откат:** `ufw disable` (временно) или `ufw delete` по номерам правил; env-файл сохранить.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фаза 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://<backend-ip>:<port>` **не** отвечает; с HAProxy — работает.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фаза 4 — MikroTik и мониторинг
|
||||||
|
|
||||||
|
- [ ] На MikroTik: с интернета на HAProxy только **25, 80, 443** (не пробрасывать 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`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фаза 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 | Учётные записи |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Окна и коммуникация
|
||||||
|
|
||||||
|
| Фаза | Риск | Окно |
|
||||||
|
|------|------|------|
|
||||||
|
| 1 | Низкий | любое |
|
||||||
|
| 2 | **SSH lockout** | рабочее время + console |
|
||||||
|
| 3 | Обрыв сервиса при ошибке порта | вечер / по сервису |
|
||||||
|
| 4 | Ошибка NAT | согласование с сетью |
|
||||||
|
|
||||||
|
После каждой фазы — отметки в [checklist.md](checklist.md).
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# Hardening хоста HAProxy
|
||||||
|
|
||||||
|
Хост: **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 |
|
||||||
|
| 80 | fe_http_zabbix | HTTP Zabbix |
|
||||||
|
| 443 | fe_https_sni | TLS passthrough по SNI |
|
||||||
|
|
||||||
|
Не включать **8404/stats** на `0.0.0.0` без VPN и пароля.
|
||||||
|
|
||||||
|
## UFW
|
||||||
|
|
||||||
|
Скрипт: [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh).
|
||||||
|
|
||||||
|
Политика:
|
||||||
|
|
||||||
|
```
|
||||||
|
default incoming: deny
|
||||||
|
default outgoing: allow
|
||||||
|
allow 22/tcp from SSH_ALLOWED
|
||||||
|
allow 25, 80, 443/tcp from anywhere # сервисы периметра
|
||||||
|
```
|
||||||
|
|
||||||
|
**SSH_ALLOWED** — несколько CIDR/ IP через пробел в env. Минимум:
|
||||||
|
|
||||||
|
- подсеть админов / VPN;
|
||||||
|
- LAN `192.168.160.0/24` (если SSH только изнутри);
|
||||||
|
- **не** `0.0.0.0/0` без крайней необходимости.
|
||||||
|
|
||||||
|
Перед `--apply` обязательно dry-run и **вторая SSH-сессия**.
|
||||||
|
|
||||||
|
## SSH
|
||||||
|
|
||||||
|
Пример drop-in `/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` / `<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.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# UFW на бэкенде: сервисный порт только с HAProxy (+ admin/extra).
|
||||||
|
#
|
||||||
|
# cp backend-ufw.env.example /etc/haproxy/security/backend-ufw.env
|
||||||
|
# ./backend-ufw-from-haproxy.sh --apply
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ENV_FILE="${BACKEND_UFW_ENV:-/etc/haproxy/security/backend-ufw.env}"
|
||||||
|
APPLY=0
|
||||||
|
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
log() { echo "[*] $*"; }
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--apply) APPLY=1; shift ;;
|
||||||
|
--env-file) ENV_FILE=$2; shift 2 ;;
|
||||||
|
-h | --help)
|
||||||
|
echo "Usage: $0 [--apply] [--env-file PATH]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -f $ENV_FILE ]] || die "missing env: $ENV_FILE"
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$ENV_FILE"
|
||||||
|
|
||||||
|
HAPROXY_IP=${HAPROXY_IP:-192.168.160.117}
|
||||||
|
BACKEND_PORT=${BACKEND_PORT:-}
|
||||||
|
ADMIN_ALLOWED=${ADMIN_ALLOWED:-}
|
||||||
|
EXTRA_ALLOWED=${EXTRA_ALLOWED:-}
|
||||||
|
UFW_EXTRA_SERVICES=${UFW_EXTRA_SERVICES:-}
|
||||||
|
UFW_IPV6=${UFW_IPV6:-no}
|
||||||
|
|
||||||
|
[[ -n $BACKEND_PORT ]] || die "BACKEND_PORT required"
|
||||||
|
|
||||||
|
command -v ufw >/dev/null 2>&1 || die "ufw not installed"
|
||||||
|
|
||||||
|
run() {
|
||||||
|
log "$*"
|
||||||
|
[[ $APPLY -eq 1 ]] && "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $APPLY -eq 0 ]] && echo "=== DRY-RUN (pass --apply) ==="
|
||||||
|
|
||||||
|
if [[ $APPLY -eq 1 ]]; then
|
||||||
|
sed -i "s/^IPV6=.*/IPV6=${UFW_IPV6}/" /etc/default/ufw 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
run ufw --force reset
|
||||||
|
run ufw default deny incoming
|
||||||
|
run ufw default allow outgoing
|
||||||
|
|
||||||
|
run ufw allow from "$HAPROXY_IP" to any port "$BACKEND_PORT" proto tcp comment 'haproxy'
|
||||||
|
|
||||||
|
for cidr in $EXTRA_ALLOWED; do
|
||||||
|
[[ -z $cidr ]] && continue
|
||||||
|
run ufw allow from "$cidr" to any port "$BACKEND_PORT" proto tcp comment 'extra'
|
||||||
|
done
|
||||||
|
|
||||||
|
for cidr in $ADMIN_ALLOWED; do
|
||||||
|
[[ -z $cidr ]] && continue
|
||||||
|
run ufw allow from "$cidr" to any port 22 proto tcp comment 'admin ssh'
|
||||||
|
for svc in $UFW_EXTRA_SERVICES; do
|
||||||
|
[[ -z $svc ]] && continue
|
||||||
|
run ufw allow from "$cidr" to any port "$svc" comment 'admin extra'
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $APPLY -eq 1 ]]; then
|
||||||
|
ufw --force enable
|
||||||
|
ufw status verbose
|
||||||
|
log "done port=$BACKEND_PORT haproxy=$HAPROXY_IP"
|
||||||
|
else
|
||||||
|
echo "=== end dry-run ==="
|
||||||
|
fi
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# UFW на бэкенде — пример для Git 192.168.160.129
|
||||||
|
HAPROXY_IP=192.168.160.117
|
||||||
|
BACKEND_PORT=443
|
||||||
|
|
||||||
|
# Admin SSH / мониторинг (пробел), опционально
|
||||||
|
ADMIN_ALLOWED="192.168.160.0/24"
|
||||||
|
|
||||||
|
# Доп. порты с ADMIN_ALLOWED, напр. 2222/tcp для Gitea SSH
|
||||||
|
UFW_EXTRA_SERVICES=""
|
||||||
|
|
||||||
|
# Доп. источники на BACKEND_PORT (напр. MTA для KSMG)
|
||||||
|
EXTRA_ALLOWED=""
|
||||||
|
|
||||||
|
UFW_IPV6=no
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# UFW для HAProxy: default deny, SSH из SSH_ALLOWED, inbound 25/80/443.
|
||||||
|
# Dry-run по умолчанию; --apply для включения.
|
||||||
|
#
|
||||||
|
# cp haproxy-ufw.env.example /etc/haproxy/security/haproxy-ufw.env
|
||||||
|
# ./haproxy-ufw-apply.sh
|
||||||
|
# ./haproxy-ufw-apply.sh --apply
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ENV_FILE="${HAPROXY_UFW_ENV:-/etc/haproxy/security/haproxy-ufw.env}"
|
||||||
|
APPLY=0
|
||||||
|
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
log() { echo "[*] $*"; }
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: haproxy-ufw-apply.sh [--apply] [--env-file PATH]
|
||||||
|
|
||||||
|
--apply выполнить ufw enable/reset (иначе только показ команд)
|
||||||
|
--env-file путь к env (default: /etc/haproxy/security/haproxy-ufw.env)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--apply) APPLY=1; shift ;;
|
||||||
|
--env-file) ENV_FILE=$2; shift 2 ;;
|
||||||
|
-h | --help) usage; exit 0 ;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -f $ENV_FILE ]] || die "missing env: $ENV_FILE (copy from haproxy-ufw.env.example)"
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$ENV_FILE"
|
||||||
|
|
||||||
|
SSH_ALLOWED=${SSH_ALLOWED:-}
|
||||||
|
INBOUND_TCP=${INBOUND_TCP:-25 80 443}
|
||||||
|
UFW_IPV6=${UFW_IPV6:-no}
|
||||||
|
|
||||||
|
[[ -n $SSH_ALLOWED ]] || die "SSH_ALLOWED is empty — lockout risk"
|
||||||
|
|
||||||
|
command -v ufw >/dev/null 2>&1 || die "ufw not installed (apt install ufw)"
|
||||||
|
|
||||||
|
run() {
|
||||||
|
log "$*"
|
||||||
|
[[ $APPLY -eq 1 ]] && "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $APPLY -eq 0 ]]; then
|
||||||
|
echo "=== DRY-RUN (pass --apply to execute) ==="
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $APPLY -eq 1 ]]; then
|
||||||
|
sed -i "s/^IPV6=.*/IPV6=${UFW_IPV6}/" /etc/default/ufw 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
run ufw --force reset
|
||||||
|
run ufw default deny incoming
|
||||||
|
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'
|
||||||
|
done
|
||||||
|
|
||||||
|
for port in $INBOUND_TCP; do
|
||||||
|
run ufw allow "$port/tcp" comment "haproxy service $port"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $APPLY -eq 1 ]]; then
|
||||||
|
ufw --force enable
|
||||||
|
ufw status verbose
|
||||||
|
log "done"
|
||||||
|
else
|
||||||
|
echo "=== end dry-run ==="
|
||||||
|
fi
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# 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"
|
||||||
|
|
||||||
|
# IPv6: yes | no
|
||||||
|
UFW_IPV6=no
|
||||||
Reference in New Issue
Block a user