infra: Samihost fail2ban watchdog (auto-unban trusted IPs, drift guard, cap at 200)
This commit is contained in:
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
# Samihost fail2ban watchdog — auto-unban whitelisted IPs and keep ignoreip list in sync.
|
||||
# Deployed to /usr/local/bin/samihost-fail2ban-watchdog.sh on 194.163.161.162
|
||||
# Cron: every 30 min (0,30 * * * *)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
JAIL_LOCAL=/etc/fail2ban/jail.local
|
||||
BACKUP=/etc/fail2ban/jail.local.watchdog.bak
|
||||
EXPECTED_IGNOREIP="127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 fc00::/7 fe80::/10 100.64.0.0/10 100.121.150.22 100.85.236.10 100.71.97.12 100.81.59.99 100.98.123.59 194.233.88.206 173.212.201.200 194.163.161.162"
|
||||
LOG=/var/log/samihost-fail2ban-watchdog.log
|
||||
TELEGRAM_LOG=/tmp/fail2ban-watchdog-last-action
|
||||
|
||||
ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
||||
log() { echo "$(ts) $*" | tee -a "$LOG"; }
|
||||
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
touch "$LOG"
|
||||
|
||||
# --- 1. Verify ignoreip line is intact and matches expected ---
|
||||
CURRENT=$(grep '^ignoreip' "$JAIL_LOCAL" | sed 's/^ignoreip[[:space:]]*=[[:space:]]*//' || true)
|
||||
EXPECTED_NORMALIZED=$(echo "$EXPECTED_IGNOREIP" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//')
|
||||
CURRENT_NORMALIZED=$(echo "$CURRENT" | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//')
|
||||
|
||||
if [ "$CURRENT_NORMALIZED" != "$EXPECTED_NORMALIZED" ]; then
|
||||
log "ALERT: ignoreip line drifted. Restoring."
|
||||
cp "$JAIL_LOCAL" "$BACKUP"
|
||||
sed -i "s|^ignoreip = .*|ignoreip = $EXPECTED_IGNOREIP|" "$JAIL_LOCAL"
|
||||
fail2ban-client reload
|
||||
echo "ignoreip restored at $(ts)" > "$TELEGRAM_LOG"
|
||||
log "ignoreip restored, fail2ban reloaded"
|
||||
fi
|
||||
|
||||
# --- 2. Unban any currently-banned IPs that match our trusted set ---
|
||||
BANNED=$(fail2ban-client status sshd 2>/dev/null | awk -F: '/Banned IP list/{print $2}' | tr ' ' '\n' | grep -v '^$' || true)
|
||||
UNBANNED=0
|
||||
for ip in $BANNED; do
|
||||
# Match against any trusted network
|
||||
is_trusted=0
|
||||
for net in 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 ::1 fc00::/7 fe80::/10 100.121.150.22 100.85.236.10 100.71.97.12 100.81.59.99 100.98.123.59 194.233.88.206 173.212.201.200 194.163.161.162; do
|
||||
if [[ "$net" == *"/"* ]]; then
|
||||
# CIDR match (simple IPv4 only — IPv6 needs python or ipcalc, skip for now)
|
||||
base="${net%/*}"
|
||||
mask="${net#*/}"
|
||||
if [[ "$ip" == "$base"* ]] || python3 -c "import ipaddress,sys; sys.exit(0 if ipaddress.ip_address('$ip') in ipaddress.ip_network('$net', strict=False) else 1)" 2>/dev/null; then
|
||||
is_trusted=1
|
||||
break
|
||||
fi
|
||||
else
|
||||
if [ "$ip" = "$net" ]; then
|
||||
is_trusted=1
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ "$is_trusted" = "1" ]; then
|
||||
if fail2ban-client set sshd unbanip "$ip" >/dev/null 2>&1; then
|
||||
log "auto-unbanned trusted IP: $ip"
|
||||
UNBANNED=$((UNBANNED+1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
[ "$UNBANNED" -gt 0 ] && echo "auto-unbanned $UNBANNED trusted IPs at $(ts)" > "$TELEGRAM_LOG"
|
||||
|
||||
# --- 3. Cap the ban count — if more than 200 are banned, mass-unban stale ones ---
|
||||
TOTAL_BANNED=$(fail2ban-client status sshd 2>/dev/null | awk '/Currently banned/{print $NF}' || echo 0)
|
||||
if [ "$TOTAL_BANNED" -gt 200 ]; then
|
||||
log "ALERT: $TOTAL_BANNED IPs banned. Mass-unbanning all."
|
||||
for ip in $BANNED; do
|
||||
fail2ban-client set sshd unbanip "$ip" >/dev/null 2>&1 || true
|
||||
done
|
||||
echo "mass-unbanned $TOTAL_BANNED stale bans at $(ts)" > "$TELEGRAM_LOG"
|
||||
fi
|
||||
|
||||
log "watchdog run complete (unbanned=$UNBANNED, total_banned=$TOTAL_BANNED)"
|
||||
Reference in New Issue
Block a user