From 56d4b0002e6f186fdb16ffc0f61ec0428ea87306 Mon Sep 17 00:00:00 2001 From: ssh-monitor Date: Thu, 16 Apr 2026 11:57:59 +1000 Subject: [PATCH] Add ENABLE_IP_BAN mode switch Introduce ENABLE_IP_BAN in config and script. When set to 0, ssh-monitor works in monitor-only mode (alerts without ipset/iptables bans). Made-with: Cursor --- README.md | 4 ++- ssh-monitor | 67 +++++++++++++++++++++++++++++++++++++--- ssh-monitor.conf.example | 3 ++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dab12c0..ec84fdd 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **` - `BRUTE_WINDOW_SEC`, `BRUTE_MIN_FAILS`, `BRUTE_NOTIFY_COOLDOWN_SEC` - `PROMETHEUS_TEXTFILE_DIR` — каталог для `ssh_monitor.prom` (совместимость с node_exporter textfile collector) - `HEALTHCHECK_STATUS_FILE` — путь к JSON-файлу с меткой последней итерации цикла +- `ENABLE_IP_BAN` — `1` (по умолчанию): мониторинг и автобан через ipset; `0`: только мониторинг и уведомления (без ipset/бана) - `BAN_TIME`, `MAX_ATTEMPTS`, `BAN_CHECK_INTERVAL`, `MONITOR_INTERVAL` - `WHITELIST_IPS`, `WHITELIST_SUBNETS` (CSV, например `ip1,ip2`) - `WATCHDOG_MAX_HEARTBEAT_AGE`, `WATCHDOG_LOG_FILE`, `WATCHDOG_SERVICE_NAME`, `WATCHDOG_NOTIFY_ON_RECOVERY` @@ -68,7 +69,8 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **` - `BRUTE_NOTIFY_COOLDOWN_SEC` — пауза между повторными уведомлениями по одному и тому же IP. - `PROMETHEUS_TEXTFILE_DIR` — если задан существующий каталог, на каждой итерации пишется `ssh_monitor.prom` с метрикой `ssh_monitor_last_loop_unixtime`. - `HEALTHCHECK_STATUS_FILE` — если задан, на каждой итерации обновляется JSON `{ "ts", "hostname", "dry_run" }`. -- `BAN_TIME` — длительность бана IP в секундах. +- `ENABLE_IP_BAN` — включить автобан IP (`1`) или работать только как монитор с уведомлениями (`0`; **ipset** не требуется). +- `BAN_TIME` — длительность бана IP в секундах (используется при `ENABLE_IP_BAN=1`). - `MAX_ATTEMPTS` — число неудачных SSH-попыток до автоматического бана. - `BAN_CHECK_INTERVAL` — интервал проверки просроченных банов и аудита согласованности бан-листа с firewall (секунды). - `MONITOR_INTERVAL` — пауза между итерациями основного цикла мониторинга (секунды). diff --git a/ssh-monitor b/ssh-monitor index 9a093bf..8372d3e 100644 --- a/ssh-monitor +++ b/ssh-monitor @@ -7,7 +7,7 @@ IFS=$'\n\t' # ============================================ CONFIG_FILE="/etc/ssh-monitor.conf" -SSH_MONITOR_VERSION="1.1.1-ipset" +SSH_MONITOR_VERSION="1.1.2-ipset" TELEGRAM_BOT_TOKEN="" TELEGRAM_CHAT_ID="" @@ -68,6 +68,9 @@ BRUTE_NOTIFY_COOLDOWN_SEC=3600 # НАСТРОЙКИ БЛОКИРОВКИ IP # ============================================ +# 1 = мониторинг + автобан IP (ipset); 0 = только мониторинг и уведомления (ipset/iptables не трогаем). +ENABLE_IP_BAN=1 + BAN_TIME=3600 MAX_ATTEMPTS=3 BAN_CHECK_INTERVAL=60 @@ -174,6 +177,7 @@ load_config() { local cfg_brute_window_sec="${BRUTE_WINDOW_SEC}" local cfg_brute_min_fails="${BRUTE_MIN_FAILS}" local cfg_brute_notify_cooldown_sec="${BRUTE_NOTIFY_COOLDOWN_SEC}" + local cfg_enable_ip_ban="${ENABLE_IP_BAN:-1}" local cfg_ban_time="${BAN_TIME}" local cfg_max_attempts="${MAX_ATTEMPTS}" local cfg_ban_check_interval="${BAN_CHECK_INTERVAL}" @@ -234,6 +238,7 @@ load_config() { BRUTE_WINDOW_SEC="${BRUTE_WINDOW_SEC:-$cfg_brute_window_sec}" BRUTE_MIN_FAILS="${BRUTE_MIN_FAILS:-$cfg_brute_min_fails}" BRUTE_NOTIFY_COOLDOWN_SEC="${BRUTE_NOTIFY_COOLDOWN_SEC:-$cfg_brute_notify_cooldown_sec}" + ENABLE_IP_BAN="${ENABLE_IP_BAN:-$cfg_enable_ip_ban}" BAN_TIME="${BAN_TIME:-$cfg_ban_time}" MAX_ATTEMPTS="${MAX_ATTEMPTS:-$cfg_max_attempts}" BAN_CHECK_INTERVAL="${BAN_CHECK_INTERVAL:-$cfg_ban_check_interval}" @@ -253,6 +258,13 @@ load_config() { MAIL_TO="$(trim_value "${MAIL_TO:-$cfg_mail_to}")" MAIL_SMTP_STARTTLS="${MAIL_SMTP_STARTTLS:-$cfg_mail_smtp_starttls}" MAIL_SMTP_SSL="${MAIL_SMTP_SSL:-$cfg_mail_smtp_ssl}" + + local _eib + _eib="$(trim_value "${ENABLE_IP_BAN:-1}")" + case "${_eib,,}" in + 0 | no | off | false) ENABLE_IP_BAN=0 ;; + *) ENABLE_IP_BAN=1 ;; + esac } # Дата/время для ежедневного отчёта (час DAILY_REPORT_HOUR и «сегодня» в LAST_REPORT_FILE). @@ -335,9 +347,9 @@ validate_config() { validate_numeric_or_default "BRUTE_NOTIFY_COOLDOWN_SEC" "3600" validate_numeric_or_default "MAIL_SMTP_PORT" "587" validate_numeric_or_default "IPSET_MAXELEM" "1048576" - if [ "${CHECK_CONFIG_ONLY:-0}" != "1" ] && [ "${DRY_RUN:-0}" != "1" ]; then + if [ "${ENABLE_IP_BAN:-1}" = "1" ] && [ "${CHECK_CONFIG_ONLY:-0}" != "1" ] && [ "${DRY_RUN:-0}" != "1" ]; then if ! command -v ipset >/dev/null 2>&1; then - printf '%s\n' 'Требуется утилита ipset (например: apt install ipset)' >&2 + printf '%s\n' 'Включён автобан IP (ENABLE_IP_BAN=1): требуется утилита ipset (например: apt install ipset)' >&2 exit 1 fi fi @@ -721,6 +733,9 @@ ipset_timeout_clamp() { } ensure_ipset_and_iptables() { + if [ "${ENABLE_IP_BAN:-1}" != "1" ]; then + return 0 + fi if [ "${DRY_RUN:-0}" = "1" ]; then return 0 fi @@ -804,6 +819,9 @@ do_ipset_del_ban() { is_ip_blocked() { local ip="$1" + if [ "${ENABLE_IP_BAN:-1}" != "1" ]; then + return 1 + fi if [ "${DRY_RUN:-0}" = "1" ]; then return 1 fi @@ -828,6 +846,18 @@ block_ip() { return 0 fi + if [ "${ENABLE_IP_BAN:-1}" != "1" ]; then + local ban_time_hours=$((BAN_TIME / 3600)) + local message=" ⚠️ ЛИМИТ НЕУДАЧНЫХ SSH (автобан отключён в конфиге) "$'\n' + message="${message}🌐 IP: ${ip}"$'\n' + message="${message}👤 Попыток: ${attempts} (порог ${MAX_ATTEMPTS}; ENABLE_IP_BAN=0)"$'\n' + message="${message}ℹ️ ipset/firewall не меняются. При ENABLE_IP_BAN=1 бан был бы ~${ban_time_hours} ч."$'\n' + message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" + notify_send "$message" + write_log "Порог неудачных SSH для $ip ($attempts попыток), автобан отключён (ENABLE_IP_BAN=0)" + return 0 + fi + local ban_until=$(($(date '+%s') + BAN_TIME)) local t_use t_use=$(ipset_timeout_clamp "$BAN_TIME") @@ -926,10 +956,12 @@ ban_list_rewrite_deduped() { # Ядро само удаляет элемент ipset по timeout; здесь только подчистка файла учёта # (без Telegram на каждый IP — иначе спам). unblock_expired_ips() { + [ "${ENABLE_IP_BAN:-1}" = "1" ] || return 0 ban_list_rewrite_deduped 0 } load_existing_bans() { + [ "${ENABLE_IP_BAN:-1}" = "1" ] || return 0 if [ ! -f "$BAN_LIST_FILE" ]; then return fi @@ -971,6 +1003,7 @@ load_existing_bans() { } verify_ban_list_vs_ipset() { + [ "${ENABLE_IP_BAN:-1}" = "1" ] || return 0 [ ! -f "$BAN_LIST_FILE" ] && return 0 local current_epoch current_epoch=$(date '+%s') @@ -1552,6 +1585,8 @@ send_daily_report() { active_count=$(who 2>/dev/null | grep -c "." || echo "0") local active_bans=0 + local ban_line + if [ "${ENABLE_IP_BAN:-1}" = "1" ]; then if command -v ipset >/dev/null 2>&1; then active_bans=$(ipset_active_ban_count) else @@ -1565,6 +1600,10 @@ send_daily_report() { done <"$BAN_LIST_FILE" fi fi + ban_line="🚫 Активных банов (ipset): ${active_bans}" + else + ban_line="🚫 Автобан IP: отключён (ENABLE_IP_BAN=0)" + fi local message=" 📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА "$'\n' message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n' @@ -1574,7 +1613,7 @@ send_daily_report() { message="${message} ✅ Успешных SSH подключений: ${successful_logins:-0}"$'\n' message="${message} ❌ Неудачных попыток SSH: ${failed_logins:-0}"$'\n' message="${message} ⚠️ Команд через sudo: ${sudo_commands:-0}"$'\n' - message="${message} 🚫 Заблокировано IP: ${active_bans}"$'\n' + message="${message} ${ban_line}"$'\n' message="${message}"$'\n' message="${message} 🧾 ТОП-${DAILY_REPORT_TOP_IPS} IP ПО НЕУДАЧНЫМ ПОПЫТКАМ:"$'\n' if [ -n "$top_lines" ]; then @@ -1709,7 +1748,11 @@ send_startup_notification() { message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n' message="${message}🕐 Время запуска: ${timestamp}"$'\n' message="${message}📊 Мониторинг SSH, SUDO и systemd-logind активирован"$'\n' + if [ "${ENABLE_IP_BAN:-1}" = "1" ]; then message="${message}🚫 Автоблокировка (ipset): после ${MAX_ATTEMPTS} попыток, timeout ${BAN_TIME} с ($IPSET_NAME_V4)"$'\n' + else + message="${message}🚫 Автоблокировка IP: выключена (ENABLE_IP_BAN=0), только уведомления"$'\n' + fi message="${message}⚪ Белый список: ${whitelist_count} IP/подсетей"$'\n' message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n' message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)" @@ -1739,8 +1782,12 @@ main() { fi load_whitelist_from_file + if [ "${ENABLE_IP_BAN:-1}" = "1" ]; then ensure_ipset_and_iptables || write_log "WARN: ensure_ipset_and_iptables завершился с ошибкой" load_existing_bans + else + write_log "ENABLE_IP_BAN=0: мониторинг и уведомления без ipset/автобана" + fi send_startup_notification local last_ban_check @@ -1757,7 +1804,7 @@ main() { local current_time current_time=$(date '+%s') - if [ $((current_time - last_ban_check)) -ge "$BAN_CHECK_INTERVAL" ]; then + if [ "${ENABLE_IP_BAN:-1}" = "1" ] && [ $((current_time - last_ban_check)) -ge "$BAN_CHECK_INTERVAL" ]; then unblock_expired_ips verify_ban_list_vs_ipset last_ban_check=$current_time @@ -1782,6 +1829,7 @@ run_check_config() { printf 'SSH_ACCEPT_NOTIFY_DEDUP_SEC=%s\n' "${SSH_ACCEPT_NOTIFY_DEDUP_SEC:-5}" printf 'DAILY_REPORT_TOP_IPS=%s\n' "$DAILY_REPORT_TOP_IPS" printf 'BRUTE_WINDOW_SEC=%s BRUTE_MIN_FAILS=%s\n' "$BRUTE_WINDOW_SEC" "$BRUTE_MIN_FAILS" + printf 'ENABLE_IP_BAN=%s (1=мониторинг+бан ipset, 0=только мониторинг и уведомления)\n' "${ENABLE_IP_BAN:-1}" printf 'IPSET_NAME_V4=%s IPSET_NAME_V6=%s IPSET_MAXELEM=%s\n' "$IPSET_NAME_V4" "$IPSET_NAME_V6" "$IPSET_MAXELEM" printf 'PROMETHEUS_TEXTFILE_DIR=%s\n' "${PROMETHEUS_TEXTFILE_DIR:-}" printf 'HEALTHCHECK_STATUS_FILE=%s\n' "${HEALTHCHECK_STATUS_FILE:-}" @@ -1831,9 +1879,18 @@ mkdir -p "$(dirname "$LOG_FILE")" touch "$LOG_FILE" write_log "Конфигурация загружена из $CONFIG_FILE (или дефолты), версия ${SSH_MONITOR_VERSION:-?}, очередь оповещений: $(notify_chain_human)" +if [ "${ENABLE_IP_BAN:-1}" = "1" ]; then + write_log "Режим: мониторинг + автобан IP (ENABLE_IP_BAN=1)" +else + write_log "Режим: только мониторинг и уведомления (ENABLE_IP_BAN=0)" +fi if [ "${DRY_RUN:-0}" = "1" ]; then + if [ "${ENABLE_IP_BAN:-1}" = "1" ]; then write_log "Режим DRY-RUN: ipset/iptables не меняются, уведомления печатаются в stderr" + else + write_log "Режим DRY-RUN: уведомления печатаются в stderr" + fi fi main diff --git a/ssh-monitor.conf.example b/ssh-monitor.conf.example index f0151e0..afabc77 100644 --- a/ssh-monitor.conf.example +++ b/ssh-monitor.conf.example @@ -44,6 +44,9 @@ LAST_SECURITY_EVENTS_FILE="/var/log/last_security_events_check" LAST_LOGIND_CHECK_FILE="/var/log/last_logind_check" BAN_LIST_FILE="/var/log/ssh_bans.txt" +# 1 = мониторинг + автобан IP (нужен ipset). 0 = только мониторинг и уведомления (логины, неудачи, sudo, logind и т.д. — без ipset/iptables). +ENABLE_IP_BAN="1" + # systemd-logind: 1 = включено, 0 = выключено ENABLE_LOGIND_MONITOR="1" LOGIND_NOTIFY_NEW="1"