From cd14f0a1c414439af6bf7e781086c8ae94cc1b69 Mon Sep 17 00:00:00 2001 From: PTah Date: Mon, 15 Jun 2026 15:03:30 +1000 Subject: [PATCH] =?UTF-8?q?feat(security):=20fail2ban=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20HAProxy=20JSON=20reject=20=D0=B8=20Zabbix=20403.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/security/checklist.md | 1 + docs/security/haproxy-host.md | 28 +++++++++++ scripts/security/fail2ban-install.sh | 47 +++++++++++++++++++ .../fail2ban/haproxy-json-reject.conf | 6 +++ .../security/fail2ban/haproxy-zabbix-403.conf | 5 ++ scripts/security/fail2ban/haproxy.local | 22 +++++++++ scripts/security/haproxy-ufw-apply.sh | 5 ++ scripts/security/haproxy-ufw.env.example | 1 + 8 files changed, 115 insertions(+) create mode 100644 scripts/security/fail2ban-install.sh create mode 100644 scripts/security/fail2ban/haproxy-json-reject.conf create mode 100644 scripts/security/fail2ban/haproxy-zabbix-403.conf create mode 100644 scripts/security/fail2ban/haproxy.local diff --git a/docs/security/checklist.md b/docs/security/checklist.md index 5f878c4..00e15eb 100644 --- a/docs/security/checklist.md +++ b/docs/security/checklist.md @@ -21,6 +21,7 @@ ## После UFW (опционально, не блокер) - [ ] fail2ban sshd +- [ ] fail2ban haproxy-reject + haproxy-zabbix-403 (`scripts/security/fail2ban-install.sh`) - [ ] `unattended-upgrades` --- diff --git a/docs/security/haproxy-host.md b/docs/security/haproxy-host.md index 5ccde56..ee39d0d 100644 --- a/docs/security/haproxy-host.md +++ b/docs/security/haproxy-host.md @@ -28,6 +28,14 @@ default outgoing: allow `192.168.128.0/17` покрывает офисную/VPN-подсеть (в т.ч. `192.168.160.0/24`). +Ответный трафик с бэкендов (`.42`, `.129` → высокие порты на `.117`): + +``` +ufw allow from 192.168.160.0/24 comment 'LAN backends return' +``` + +(в [haproxy-ufw-apply.sh](../../scripts/security/haproxy-ufw-apply.sh) — `LAN_BACKENDS_CIDR`). + ### Env `/etc/haproxy/security/haproxy-ufw.env` (из [haproxy-ufw.env.example](../../scripts/security/haproxy-ufw.env.example)): @@ -73,11 +81,31 @@ Inbound-почта с интернета идёт на **KSMG `.57`**, не на |------|--------| | SSH: ключи, `PermitRootLogin no`, `PasswordAuthentication no` | до/вместе с UFW | | fail2ban на sshd | подстраховка SSH | +| fail2ban на HAProxy JSON | бан сканеров / reject SNI / Zabbix 403 | | `unattended-upgrades` | патчи | | Не открывать лишние listen | `ss -tlnp` | Опционально позже: sysctl syncookies, rate-limit на frontend, allowlist для Exchange/sac-api. +## fail2ban (HAProxy JSON) + +Скрипт: [fail2ban-install.sh](../../scripts/security/fail2ban-install.sh), конфиги в `scripts/security/fail2ban/`. + +| Jail | Что ловит | +|------|-----------| +| `haproxy-reject` | JSON: `"be":"fe_https_sni","srv":""` — сканеры, чужой SNI, allowlist reject | +| `haproxy-zabbix-403` | httplog: `fe_http_zabbix` + `403` | + +```bash +sudo apt install -y fail2ban +sudo bash ~/reverse-proxy/scripts/security/fail2ban-install.sh +sudo fail2ban-client status haproxy-reject +``` + +Порог: **15** reject за **10 мин** → бан **24 ч** через UFW. `ignoreip`: LAN, VPN, admin. + +После `systemctl restart fail2ban` подождите 1–2 с перед `fail2ban-client status` (сокет). + ### SSH (пример) `/etc/ssh/sshd_config.d/99-hardening.conf`: diff --git a/scripts/security/fail2ban-install.sh b/scripts/security/fail2ban-install.sh new file mode 100644 index 0000000..09b4cc3 --- /dev/null +++ b/scripts/security/fail2ban-install.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Установка fail2ban filter/jail для HAProxy JSON-лога. +# sudo bash fail2ban-install.sh +# sudo bash fail2ban-install.sh --test # только fail2ban-regex +set -Eeuo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +F2B_DIR="$SCRIPT_DIR/fail2ban" +TEST_ONLY=0 + +die() { echo "error: $*" >&2; exit 1; } +log() { echo "[*] $*"; } + +while [[ $# -gt 0 ]]; do + case "$1" in + --test) TEST_ONLY=1; shift ;; + -h | --help) + echo "Usage: fail2ban-install.sh [--test]" + exit 0 + ;; + *) die "unknown option: $1" ;; + esac +done + +[[ -d $F2B_DIR ]] || die "missing $F2B_DIR" + +command -v fail2ban-client >/dev/null 2>&1 || die "install fail2ban first (apt install fail2ban)" + +install -m 0644 "$F2B_DIR/haproxy-json-reject.conf" /etc/fail2ban/filter.d/haproxy-json-reject.conf +install -m 0644 "$F2B_DIR/haproxy-zabbix-403.conf" /etc/fail2ban/filter.d/haproxy-zabbix-403.conf +install -m 0644 "$F2B_DIR/haproxy.local" /etc/fail2ban/jail.d/haproxy.local + +log "fail2ban-regex haproxy-json-reject" +fail2ban-regex /var/log/haproxy.log /etc/fail2ban/filter.d/haproxy-json-reject.conf | tail -8 + +if [[ $TEST_ONLY -eq 1 ]]; then + log "test only — jail not restarted" + exit 0 +fi + +fail2ban-client -t +systemctl enable fail2ban +systemctl restart fail2ban +sleep 1 +fail2ban-client status +fail2ban-client status haproxy-reject +log "done" diff --git a/scripts/security/fail2ban/haproxy-json-reject.conf b/scripts/security/fail2ban/haproxy-json-reject.conf new file mode 100644 index 0000000..a657e06 --- /dev/null +++ b/scripts/security/fail2ban/haproxy-json-reject.conf @@ -0,0 +1,6 @@ +[Definition] + +# Сканеры и reject на fe_https_sni (неизвестный SNI, allowlist, мусор) +failregex = ^.*haproxy\[(?:\d+|master)\]: \{"time":"[^"]*","client":"".*"fe":"fe_https_sni","be":"fe_https_sni","srv":"" + +ignoreregex = diff --git a/scripts/security/fail2ban/haproxy-zabbix-403.conf b/scripts/security/fail2ban/haproxy-zabbix-403.conf new file mode 100644 index 0000000..a33faf1 --- /dev/null +++ b/scripts/security/fail2ban/haproxy-zabbix-403.conf @@ -0,0 +1,5 @@ +[Definition] + +failregex = ^.*haproxy\[(?:\d+|master)\]: :\d+ \[[^\]]+\] fe_http_zabbix .* 403 + +ignoreregex = diff --git a/scripts/security/fail2ban/haproxy.local b/scripts/security/fail2ban/haproxy.local new file mode 100644 index 0000000..8caa303 --- /dev/null +++ b/scripts/security/fail2ban/haproxy.local @@ -0,0 +1,22 @@ +[DEFAULT] +banaction = ufw +banaction_allports = ufw +allowipv6 = no + +[haproxy-reject] +enabled = true +filter = haproxy-json-reject +logpath = /var/log/haproxy.log +maxretry = 15 +findtime = 600 +bantime = 86400 +ignoreip = 127.0.0.1/8 192.168.160.0/24 192.168.128.0/17 5.100.81.121 + +[haproxy-zabbix-403] +enabled = true +filter = haproxy-zabbix-403 +logpath = /var/log/haproxy.log +maxretry = 10 +findtime = 600 +bantime = 86400 +ignoreip = 127.0.0.1/8 192.168.160.0/24 192.168.128.0/17 5.100.81.121 diff --git a/scripts/security/haproxy-ufw-apply.sh b/scripts/security/haproxy-ufw-apply.sh index c8025a4..52353fa 100755 --- a/scripts/security/haproxy-ufw-apply.sh +++ b/scripts/security/haproxy-ufw-apply.sh @@ -38,6 +38,7 @@ source "$ENV_FILE" SSH_ALLOWED=${SSH_ALLOWED:-} INBOUND_TCP=${INBOUND_TCP:-80 443} +LAN_BACKENDS_CIDR=${LAN_BACKENDS_CIDR:-192.168.160.0/24} STATS_TCP=${STATS_TCP:-8404} UFW_IPV6=${UFW_IPV6:-no} @@ -73,6 +74,10 @@ for port in $INBOUND_TCP; do run ufw allow "$port/tcp" comment "haproxy service $port" done +if [[ -n ${LAN_BACKENDS_CIDR:-} ]]; then + run ufw allow from "$LAN_BACKENDS_CIDR" comment 'LAN backends return' +fi + if [[ $APPLY -eq 1 ]]; then ufw --force enable ufw status verbose diff --git a/scripts/security/haproxy-ufw.env.example b/scripts/security/haproxy-ufw.env.example index ce2e943..e8771a0 100644 --- a/scripts/security/haproxy-ufw.env.example +++ b/scripts/security/haproxy-ufw.env.example @@ -6,6 +6,7 @@ SSH_ALLOWED="5.100.81.121 192.168.128.0/17" # С периметра (MikroTik DNAT) — для всех INBOUND_TCP="80 443" +LAN_BACKENDS_CIDR="192.168.160.0/24" # HAProxy stats (если fe_stats включён в haproxy.cfg) STATS_TCP="8404"