feat: unify daily SSH report format with SAC; DAILY_REPORT_ENABLED toggle
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -36,7 +36,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
|
|||||||
- `BACKUP_WEBHOOK_URL` — резервная доставка JSON `{"text":"..."}` (например Slack Incoming Webhook), если **все** каналы из **`NOTIFY_CHAIN`** не смогли доставить сообщение; **не заменяет** обязательность хотя бы одного основного канала (см. шаг 3)
|
- `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`
|
- `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» ниже
|
- `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`
|
- `BRUTE_WINDOW_SEC`, `BRUTE_MIN_FAILS`, `BRUTE_NOTIFY_COOLDOWN_SEC`
|
||||||
- `PROMETHEUS_TEXTFILE_DIR` — каталог для `ssh_monitor.prom` (совместимость с node_exporter textfile collector)
|
- `PROMETHEUS_TEXTFILE_DIR` — каталог для `ssh_monitor.prom` (совместимость с node_exporter textfile collector)
|
||||||
- `HEALTHCHECK_STATUS_FILE` — путь к JSON-файлу с меткой последней итерации цикла
|
- `HEALTHCHECK_STATUS_FILE` — путь к JSON-файлу с меткой последней итерации цикла
|
||||||
|
|||||||
+38
-18
@@ -52,6 +52,7 @@ MAIL_SMTP_SSL="0"
|
|||||||
LOG_FILE="/var/log/ssh_monitor.log"
|
LOG_FILE="/var/log/ssh_monitor.log"
|
||||||
LAST_HEARTBEAT_FILE="/var/log/last_heartbeat.txt"
|
LAST_HEARTBEAT_FILE="/var/log/last_heartbeat.txt"
|
||||||
LAST_REPORT_FILE="/var/log/last_daily_report.txt"
|
LAST_REPORT_FILE="/var/log/last_daily_report.txt"
|
||||||
|
DAILY_REPORT_ENABLED=1
|
||||||
DAILY_REPORT_HOUR=9
|
DAILY_REPORT_HOUR=9
|
||||||
DAILY_REPORT_TOP_IPS=5
|
DAILY_REPORT_TOP_IPS=5
|
||||||
# Пусто = календарь и час отчёта в зоне процесса (как `date`, обычно совпадает с /etc/localtime).
|
# Пусто = календарь и час отчёта в зоне процесса (как `date`, обычно совпадает с /etc/localtime).
|
||||||
@@ -183,6 +184,7 @@ load_config() {
|
|||||||
local cfg_log_file="${LOG_FILE}"
|
local cfg_log_file="${LOG_FILE}"
|
||||||
local cfg_last_heartbeat_file="${LAST_HEARTBEAT_FILE}"
|
local cfg_last_heartbeat_file="${LAST_HEARTBEAT_FILE}"
|
||||||
local cfg_last_report_file="${LAST_REPORT_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_hour="${DAILY_REPORT_HOUR}"
|
||||||
local cfg_daily_report_top_ips="${DAILY_REPORT_TOP_IPS}"
|
local cfg_daily_report_top_ips="${DAILY_REPORT_TOP_IPS}"
|
||||||
local cfg_daily_report_tz="${DAILY_REPORT_TZ}"
|
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_NAME_V6="${IPSET_NAME_V6:-$cfg_ipset_name_v6}"
|
||||||
IPSET_MAXELEM="${IPSET_MAXELEM:-$cfg_ipset_maxelem}"
|
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_HOUR="${DAILY_REPORT_HOUR:-$cfg_daily_report_hour}"
|
||||||
DAILY_REPORT_TOP_IPS="${DAILY_REPORT_TOP_IPS:-$cfg_daily_report_top_ips}"
|
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}")"
|
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)"
|
ban_line="🚫 Автобан IP: отключён (ENABLE_IP_BAN=0)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local message=" 📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА "$'\n'
|
local message=""
|
||||||
message="${message}🕐 Время отчета: ${current_time}"$'\n'
|
message+="📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА"$'\n'
|
||||||
message="${message}"$'\n'
|
message+="🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
||||||
message="${message} 📈 СТАТИСТИКА ЗА ПОСЛЕДНИЕ 24 ЧАСА: "$'\n'
|
message+="🕐 Время отчета: ${current_time}"$'\n'
|
||||||
message="${message} ✅ Успешных SSH подключений: ${successful_logins:-0}"$'\n'
|
message+=""$'\n'
|
||||||
message="${message} ❌ Неудачных попыток SSH: ${failed_logins:-0}"$'\n'
|
message+="📈 СТАТИСТИКА ЗА ПОСЛЕДНИЕ 24 ЧАСА:"$'\n'
|
||||||
message="${message} ⚠️ Команд через sudo: ${sudo_commands:-0}"$'\n'
|
message+=" ✅ Успешных SSH подключений: ${successful_logins:-0}"$'\n'
|
||||||
message="${message} ${ban_line}"$'\n'
|
message+=" ❌ Неудачных попыток SSH: ${failed_logins:-0}"$'\n'
|
||||||
message="${message}"$'\n'
|
message+=" ⚠️ Команд через sudo: ${sudo_commands:-0}"$'\n'
|
||||||
message="${message} 🧾 ТОП-${DAILY_REPORT_TOP_IPS} IP ПО НЕУДАЧНЫМ ПОПЫТКАМ:"$'\n'
|
message+=" ${ban_line}"$'\n'
|
||||||
|
message+=""$'\n'
|
||||||
|
message+="🧾 ТОП-${DAILY_REPORT_TOP_IPS:-5} IP ПО НЕУДАЧНЫМ ПОПЫТКАМ:"$'\n'
|
||||||
if [ -n "$top_lines" ]; then
|
if [ -n "$top_lines" ]; then
|
||||||
message="${message}${top_lines}"$'\n'
|
message+="${top_lines}"$'\n'
|
||||||
else
|
else
|
||||||
message="${message} (нет данных)"$'\n'
|
message+=" (нет данных)"$'\n'
|
||||||
fi
|
fi
|
||||||
message="${message}"$'\n'
|
message+=""$'\n'
|
||||||
message="${message} 👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (${active_count}):"$'\n'
|
message+="👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (${active_count}):"$'\n'
|
||||||
|
|
||||||
if [ -n "$active_sessions" ]; then
|
if [ -n "$active_sessions" ]; then
|
||||||
message="${message}${active_sessions}"$'\n'
|
message+="${active_sessions}"$'\n'
|
||||||
else
|
else
|
||||||
message="${message} Нет активных пользователей"$'\n'
|
message+=" (нет данных)"$'\n'
|
||||||
fi
|
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
|
local sac_summary sac_details_json
|
||||||
sac_summary="SSH 24ч: успех ${successful_logins:-0}, неудач ${failed_logins:-0}, sudo ${sudo_commands:-0}"
|
sac_summary="SSH 24ч: успех ${successful_logins:-0}, неудач ${failed_logins:-0}, sudo ${sudo_commands:-0}"
|
||||||
sac_details_json="$(
|
sac_details_json="$(
|
||||||
@@ -1765,18 +1773,27 @@ send_daily_report() {
|
|||||||
SAC_SUDO="${sudo_commands:-0}" \
|
SAC_SUDO="${sudo_commands:-0}" \
|
||||||
SAC_BANS="${active_bans:-0}" \
|
SAC_BANS="${active_bans:-0}" \
|
||||||
SAC_ACTIVE="${active_count:-0}" \
|
SAC_ACTIVE="${active_count:-0}" \
|
||||||
|
SAC_ACTIVE_USERS="$active_users_json" \
|
||||||
python3 <<'PY'
|
python3 <<'PY'
|
||||||
import html
|
import html
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
body = os.environ.get("SAC_REPORT_BODY", "")
|
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 = {
|
stats = {
|
||||||
"successful_ssh": int(os.environ.get("SAC_OK", "0") or 0),
|
"platform": "ssh",
|
||||||
"failed_ssh": int(os.environ.get("SAC_FAIL", "0") or 0),
|
"successful_logins": ok,
|
||||||
|
"failed_logins": fail,
|
||||||
|
"successful_ssh": ok,
|
||||||
|
"failed_ssh": fail,
|
||||||
"sudo_commands": int(os.environ.get("SAC_SUDO", "0") or 0),
|
"sudo_commands": int(os.environ.get("SAC_SUDO", "0") or 0),
|
||||||
"active_bans": int(os.environ.get("SAC_BANS", "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_sessions": int(os.environ.get("SAC_ACTIVE", "0") or 0),
|
||||||
|
"active_users": active_users,
|
||||||
|
"generated_by": "agent",
|
||||||
}
|
}
|
||||||
escaped = html.escape(body)
|
escaped = html.escape(body)
|
||||||
report_html = '<div class="agent-report">' + escaped.replace("\n", "<br>\n") + "</div>"
|
report_html = '<div class="agent-report">' + escaped.replace("\n", "<br>\n") + "</div>"
|
||||||
@@ -1819,6 +1836,9 @@ read_last_report_date() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_daily_report() {
|
check_daily_report() {
|
||||||
|
if [ "${DAILY_REPORT_ENABLED:-1}" != "1" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
local today
|
local today
|
||||||
today=$(report_date '+%F')
|
today=$(report_date '+%F')
|
||||||
local last_date
|
local last_date
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ LOGIND_NOTIFY_FAILED="1"
|
|||||||
LOGIND_SKIP_REMOTE="1"
|
LOGIND_SKIP_REMOTE="1"
|
||||||
|
|
||||||
DAILY_REPORT_HOUR="9"
|
DAILY_REPORT_HOUR="9"
|
||||||
# IANA-зона для «9:00» и календарной даты отчёта; пусто = как у команды date (обычно локаль сервера, см. timedatectl)
|
# 0 = не слать report.daily.ssh с агента (суточный отчёт только из SAC)
|
||||||
|
DAILY_REPORT_ENABLED="1"
|
||||||
DAILY_REPORT_TZ=""
|
DAILY_REPORT_TZ=""
|
||||||
DAILY_REPORT_TOP_IPS="5"
|
DAILY_REPORT_TOP_IPS="5"
|
||||||
# IANA-зона для «🕐 Время» в Telegram/email. Пусто = как DAILY_REPORT_TZ, иначе зона процесса (часто UTC в systemd — см. README).
|
# IANA-зона для «🕐 Время» в Telegram/email. Пусто = как DAILY_REPORT_TZ, иначе зона процесса (часто UTC в systemd — см. README).
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.2.7-SAC
|
1.2.8-SAC
|
||||||
|
|||||||
Reference in New Issue
Block a user