77 lines
3.2 KiB
Bash
Executable File
77 lines
3.2 KiB
Bash
Executable File
#!/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)"
|