feat(security): fail2ban для HAProxy JSON reject и Zabbix 403.

This commit is contained in:
PTah
2026-06-15 15:03:30 +10:00
parent 2ce71df1f8
commit cd14f0a1c4
8 changed files with 115 additions and 0 deletions
+1
View File
@@ -21,6 +21,7 @@
## После UFW (опционально, не блокер) ## После UFW (опционально, не блокер)
- [ ] fail2ban sshd - [ ] fail2ban sshd
- [ ] fail2ban haproxy-reject + haproxy-zabbix-403 (`scripts/security/fail2ban-install.sh`)
- [ ] `unattended-upgrades` - [ ] `unattended-upgrades`
--- ---
+28
View File
@@ -28,6 +28,14 @@ default outgoing: allow
`192.168.128.0/17` покрывает офисную/VPN-подсеть (в т.ч. `192.168.160.0/24`). `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 ### Env
`/etc/haproxy/security/haproxy-ufw.env` (из [haproxy-ufw.env.example](../../scripts/security/haproxy-ufw.env.example)): `/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 | | SSH: ключи, `PermitRootLogin no`, `PasswordAuthentication no` | до/вместе с UFW |
| fail2ban на sshd | подстраховка SSH | | fail2ban на sshd | подстраховка SSH |
| fail2ban на HAProxy JSON | бан сканеров / reject SNI / Zabbix 403 |
| `unattended-upgrades` | патчи | | `unattended-upgrades` | патчи |
| Не открывать лишние listen | `ss -tlnp` | | Не открывать лишние listen | `ss -tlnp` |
Опционально позже: sysctl syncookies, rate-limit на frontend, allowlist для Exchange/sac-api. Опционально позже: 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":"<NOSRV>"` — сканеры, чужой 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 (пример) ### SSH (пример)
`/etc/ssh/sshd_config.d/99-hardening.conf`: `/etc/ssh/sshd_config.d/99-hardening.conf`:
+47
View File
@@ -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"
@@ -0,0 +1,6 @@
[Definition]
# Сканеры и reject на fe_https_sni (неизвестный SNI, allowlist, мусор)
failregex = ^.*haproxy\[(?:\d+|master)\]: \{"time":"[^"]*","client":"<HOST>".*"fe":"fe_https_sni","be":"fe_https_sni","srv":"<NOSRV>"
ignoreregex =
@@ -0,0 +1,5 @@
[Definition]
failregex = ^.*haproxy\[(?:\d+|master)\]: <HOST>:\d+ \[[^\]]+\] fe_http_zabbix .* 403
ignoreregex =
+22
View File
@@ -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
+5
View File
@@ -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}
LAN_BACKENDS_CIDR=${LAN_BACKENDS_CIDR:-192.168.160.0/24}
STATS_TCP=${STATS_TCP:-8404} STATS_TCP=${STATS_TCP:-8404}
UFW_IPV6=${UFW_IPV6:-no} UFW_IPV6=${UFW_IPV6:-no}
@@ -73,6 +74,10 @@ for port in $INBOUND_TCP; do
run ufw allow "$port/tcp" comment "haproxy service $port" run ufw allow "$port/tcp" comment "haproxy service $port"
done done
if [[ -n ${LAN_BACKENDS_CIDR:-} ]]; then
run ufw allow from "$LAN_BACKENDS_CIDR" comment 'LAN backends return'
fi
if [[ $APPLY -eq 1 ]]; then if [[ $APPLY -eq 1 ]]; then
ufw --force enable ufw --force enable
ufw status verbose ufw status verbose
+1
View File
@@ -6,6 +6,7 @@ SSH_ALLOWED="5.100.81.121 192.168.128.0/17"
# С периметра (MikroTik DNAT) — для всех # С периметра (MikroTik DNAT) — для всех
INBOUND_TCP="80 443" INBOUND_TCP="80 443"
LAN_BACKENDS_CIDR="192.168.160.0/24"
# HAProxy stats (если fe_stats включён в haproxy.cfg) # HAProxy stats (если fe_stats включён в haproxy.cfg)
STATS_TCP="8404" STATS_TCP="8404"