feat(security): fail2ban для HAProxy JSON reject и Zabbix 403.
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
## После UFW (опционально, не блокер)
|
||||
|
||||
- [ ] fail2ban sshd
|
||||
- [ ] fail2ban haproxy-reject + haproxy-zabbix-403 (`scripts/security/fail2ban-install.sh`)
|
||||
- [ ] `unattended-upgrades`
|
||||
|
||||
---
|
||||
|
||||
@@ -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":"<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 (пример)
|
||||
|
||||
`/etc/ssh/sshd_config.d/99-hardening.conf`:
|
||||
|
||||
@@ -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 =
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user