diff --git a/README.md b/README.md index 7a50149..2d49969 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **` - `BACKUP_WEBHOOK_URL` — резервная доставка JSON `{"text":"..."}` (например Slack Incoming Webhook), если **все** каналы из **`NOTIFY_CHAIN`** не смогли доставить сообщение; **не заменяет** обязательность хотя бы одного основного канала (см. шаг 3) - `LOG_FILE`, `LAST_HEARTBEAT_FILE`, `LAST_REPORT_FILE`, `LAST_SSH_CHECK_FILE`, `LAST_SUDO_CHECK_FILE`, `LAST_SECURITY_EVENTS_FILE`, `LAST_LOGIND_CHECK_FILE`, `BAN_LIST_FILE` - `ENABLE_LOGIND_MONITOR`, `LOGIND_NOTIFY_NEW`, `LOGIND_NOTIFY_REMOVED`, `LOGIND_NOTIFY_FAILED`, `LOGIND_SKIP_REMOTE` — см. раздел «Мониторинг systemd-logind» ниже -- `DAILY_REPORT_HOUR` (0..23), `DAILY_REPORT_TZ` (опционально), `NOTIFY_TZ` (опционально), `SERVER_DISPLAY_NAME` (опционально), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS` +- `DAILY_REPORT_HOUR` (0..23), `DAILY_REPORT_ENABLED` (1/0 — отчёт с агента; 0 = только SAC), `DAILY_REPORT_TZ` (опционально), `NOTIFY_TZ` (опционально), `SERVER_DISPLAY_NAME` (опционально), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS` - `BRUTE_WINDOW_SEC`, `BRUTE_MIN_FAILS`, `BRUTE_NOTIFY_COOLDOWN_SEC` - `PROMETHEUS_TEXTFILE_DIR` — каталог для `ssh_monitor.prom` (совместимость с node_exporter textfile collector) - `HEALTHCHECK_STATUS_FILE` — путь к JSON-файлу с меткой последней итерации цикла diff --git a/ssh-monitor b/ssh-monitor index 5a9d6c2..95d268c 100644 --- a/ssh-monitor +++ b/ssh-monitor @@ -52,6 +52,7 @@ MAIL_SMTP_SSL="0" LOG_FILE="/var/log/ssh_monitor.log" LAST_HEARTBEAT_FILE="/var/log/last_heartbeat.txt" LAST_REPORT_FILE="/var/log/last_daily_report.txt" +DAILY_REPORT_ENABLED=1 DAILY_REPORT_HOUR=9 DAILY_REPORT_TOP_IPS=5 # Пусто = календарь и час отчёта в зоне процесса (как `date`, обычно совпадает с /etc/localtime). @@ -183,6 +184,7 @@ load_config() { local cfg_log_file="${LOG_FILE}" local cfg_last_heartbeat_file="${LAST_HEARTBEAT_FILE}" local cfg_last_report_file="${LAST_REPORT_FILE}" + local cfg_daily_report_enabled="${DAILY_REPORT_ENABLED:-1}" local cfg_daily_report_hour="${DAILY_REPORT_HOUR}" local cfg_daily_report_top_ips="${DAILY_REPORT_TOP_IPS}" local cfg_daily_report_tz="${DAILY_REPORT_TZ}" @@ -258,6 +260,7 @@ load_config() { IPSET_NAME_V6="${IPSET_NAME_V6:-$cfg_ipset_name_v6}" IPSET_MAXELEM="${IPSET_MAXELEM:-$cfg_ipset_maxelem}" + DAILY_REPORT_ENABLED="$(trim_value "${DAILY_REPORT_ENABLED:-$cfg_daily_report_enabled}")" DAILY_REPORT_HOUR="${DAILY_REPORT_HOUR:-$cfg_daily_report_hour}" DAILY_REPORT_TOP_IPS="${DAILY_REPORT_TOP_IPS:-$cfg_daily_report_top_ips}" DAILY_REPORT_TZ="$(trim_value "${DAILY_REPORT_TZ:-$cfg_daily_report_tz}")" @@ -1732,30 +1735,35 @@ send_daily_report() { ban_line="🚫 Автобан IP: отключён (ENABLE_IP_BAN=0)" fi - local message=" 📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА "$'\n' - message="${message}🕐 Время отчета: ${current_time}"$'\n' - message="${message}"$'\n' - message="${message} 📈 СТАТИСТИКА ЗА ПОСЛЕДНИЕ 24 ЧАСА: "$'\n' - message="${message} ✅ Успешных SSH подключений: ${successful_logins:-0}"$'\n' - message="${message} ❌ Неудачных попыток SSH: ${failed_logins:-0}"$'\n' - message="${message} ⚠️ Команд через sudo: ${sudo_commands:-0}"$'\n' - message="${message} ${ban_line}"$'\n' - message="${message}"$'\n' - message="${message} 🧾 ТОП-${DAILY_REPORT_TOP_IPS} IP ПО НЕУДАЧНЫМ ПОПЫТКАМ:"$'\n' + local message="" + message+="📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА"$'\n' + message+="🖥️ Сервер: $(server_display_name_with_ip)"$'\n' + message+="🕐 Время отчета: ${current_time}"$'\n' + message+=""$'\n' + message+="📈 СТАТИСТИКА ЗА ПОСЛЕДНИЕ 24 ЧАСА:"$'\n' + message+=" ✅ Успешных SSH подключений: ${successful_logins:-0}"$'\n' + message+=" ❌ Неудачных попыток SSH: ${failed_logins:-0}"$'\n' + message+=" ⚠️ Команд через sudo: ${sudo_commands:-0}"$'\n' + message+=" ${ban_line}"$'\n' + message+=""$'\n' + message+="🧾 ТОП-${DAILY_REPORT_TOP_IPS:-5} IP ПО НЕУДАЧНЫМ ПОПЫТКАМ:"$'\n' if [ -n "$top_lines" ]; then - message="${message}${top_lines}"$'\n' + message+="${top_lines}"$'\n' else - message="${message} (нет данных)"$'\n' + message+=" (нет данных)"$'\n' fi - message="${message}"$'\n' - message="${message} 👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (${active_count}):"$'\n' + message+=""$'\n' + message+="👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (${active_count}):"$'\n' if [ -n "$active_sessions" ]; then - message="${message}${active_sessions}"$'\n' + message+="${active_sessions}"$'\n' else - message="${message} Нет активных пользователей"$'\n' + message+=" (нет данных)"$'\n' fi + local active_users_json + active_users_json="$(printf '%s' "$active_sessions" | python3 -c 'import json,sys; print(json.dumps([l.strip() for l in sys.stdin.read().splitlines() if l.strip()]))' 2>/dev/null || echo '[]')" + local sac_summary sac_details_json sac_summary="SSH 24ч: успех ${successful_logins:-0}, неудач ${failed_logins:-0}, sudo ${sudo_commands:-0}" sac_details_json="$( @@ -1765,18 +1773,27 @@ send_daily_report() { SAC_SUDO="${sudo_commands:-0}" \ SAC_BANS="${active_bans:-0}" \ SAC_ACTIVE="${active_count:-0}" \ + SAC_ACTIVE_USERS="$active_users_json" \ python3 <<'PY' import html import json import os body = os.environ.get("SAC_REPORT_BODY", "") +active_users = json.loads(os.environ.get("SAC_ACTIVE_USERS", "[]") or "[]") +ok = int(os.environ.get("SAC_OK", "0") or 0) +fail = int(os.environ.get("SAC_FAIL", "0") or 0) stats = { - "successful_ssh": int(os.environ.get("SAC_OK", "0") or 0), - "failed_ssh": int(os.environ.get("SAC_FAIL", "0") or 0), + "platform": "ssh", + "successful_logins": ok, + "failed_logins": fail, + "successful_ssh": ok, + "failed_ssh": fail, "sudo_commands": int(os.environ.get("SAC_SUDO", "0") or 0), "active_bans": int(os.environ.get("SAC_BANS", "0") or 0), "active_sessions": int(os.environ.get("SAC_ACTIVE", "0") or 0), + "active_users": active_users, + "generated_by": "agent", } escaped = html.escape(body) report_html = '
' + escaped.replace("\n", "
\n") + "
" @@ -1819,6 +1836,9 @@ read_last_report_date() { } check_daily_report() { + if [ "${DAILY_REPORT_ENABLED:-1}" != "1" ]; then + return 0 + fi local today today=$(report_date '+%F') local last_date diff --git a/ssh-monitor.conf.example b/ssh-monitor.conf.example index 3fe10ef..5fd9a80 100644 --- a/ssh-monitor.conf.example +++ b/ssh-monitor.conf.example @@ -68,7 +68,8 @@ LOGIND_NOTIFY_FAILED="1" LOGIND_SKIP_REMOTE="1" DAILY_REPORT_HOUR="9" -# IANA-зона для «9:00» и календарной даты отчёта; пусто = как у команды date (обычно локаль сервера, см. timedatectl) +# 0 = не слать report.daily.ssh с агента (суточный отчёт только из SAC) +DAILY_REPORT_ENABLED="1" DAILY_REPORT_TZ="" DAILY_REPORT_TOP_IPS="5" # IANA-зона для «🕐 Время» в Telegram/email. Пусто = как DAILY_REPORT_TZ, иначе зона процесса (часто UTC в systemd — см. README). diff --git a/version.txt b/version.txt index 5d52c6a..bd5f74f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.7-SAC +1.2.8-SAC