Zabbix: короткий plain для канала; seq в файле; alert через python3 -o
Made-with: Cursor
This commit is contained in:
+131
-47
@@ -7,7 +7,7 @@ IFS=$'\n\t'
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
CONFIG_FILE="/etc/ssh-monitor.conf"
|
CONFIG_FILE="/etc/ssh-monitor.conf"
|
||||||
SSH_MONITOR_VERSION="0.9.5.8"
|
SSH_MONITOR_VERSION="0.9.7.0"
|
||||||
|
|
||||||
TELEGRAM_BOT_TOKEN=""
|
TELEGRAM_BOT_TOKEN=""
|
||||||
TELEGRAM_CHAT_ID=""
|
TELEGRAM_CHAT_ID=""
|
||||||
@@ -22,9 +22,10 @@ ZABBIX_SERVER=""
|
|||||||
ZABBIX_HOST_NAME=""
|
ZABBIX_HOST_NAME=""
|
||||||
ZABBIX_ALERT_KEY="ssh.monitor.alert"
|
ZABBIX_ALERT_KEY="ssh.monitor.alert"
|
||||||
ZABBIX_SEQ_KEY="ssh.monitor.seq"
|
ZABBIX_SEQ_KEY="ssh.monitor.seq"
|
||||||
# Лимит UTF-8 байт для значения ssh.monitor.alert (Zabbix trapper «Character» ≈255 байт).
|
# Лимит UTF-8 байт для значения ssh.monitor.alert (0 = не урезать). Для короткого plain-текста в Zabbix обычно 0.
|
||||||
# 0 = не урезать (item типа Text / большой лимит). По умолчанию 250 — влезает в Character.
|
ZABBIX_ALERT_MAX_BYTES="0"
|
||||||
ZABBIX_ALERT_MAX_BYTES="250"
|
# Файл последнего ssh.monitor.seq (число), чтобы после перезапуска счётчик не сбрасывался в 1.
|
||||||
|
ZABBIX_SEQ_STATE_FILE="/var/log/ssh_monitor_zabbix_seq"
|
||||||
|
|
||||||
# Почта (SMTP через python3). Нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
# Почта (SMTP через python3). Нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
||||||
MAIL_SMTP_HOST=""
|
MAIL_SMTP_HOST=""
|
||||||
@@ -160,7 +161,8 @@ load_config() {
|
|||||||
local cfg_zabbix_host_name="${ZABBIX_HOST_NAME:-}"
|
local cfg_zabbix_host_name="${ZABBIX_HOST_NAME:-}"
|
||||||
local cfg_zabbix_alert_key="${ZABBIX_ALERT_KEY:-ssh.monitor.alert}"
|
local cfg_zabbix_alert_key="${ZABBIX_ALERT_KEY:-ssh.monitor.alert}"
|
||||||
local cfg_zabbix_seq_key="${ZABBIX_SEQ_KEY:-ssh.monitor.seq}"
|
local cfg_zabbix_seq_key="${ZABBIX_SEQ_KEY:-ssh.monitor.seq}"
|
||||||
local cfg_zabbix_alert_max_bytes="${ZABBIX_ALERT_MAX_BYTES:-250}"
|
local cfg_zabbix_alert_max_bytes="${ZABBIX_ALERT_MAX_BYTES:-0}"
|
||||||
|
local cfg_zabbix_seq_state_file="${ZABBIX_SEQ_STATE_FILE:-/var/log/ssh_monitor_zabbix_seq}"
|
||||||
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}"
|
||||||
@@ -215,10 +217,11 @@ load_config() {
|
|||||||
ZABBIX_ALERT_KEY="$(trim_value "${ZABBIX_ALERT_KEY:-$cfg_zabbix_alert_key}")"
|
ZABBIX_ALERT_KEY="$(trim_value "${ZABBIX_ALERT_KEY:-$cfg_zabbix_alert_key}")"
|
||||||
ZABBIX_SEQ_KEY="$(trim_value "${ZABBIX_SEQ_KEY:-$cfg_zabbix_seq_key}")"
|
ZABBIX_SEQ_KEY="$(trim_value "${ZABBIX_SEQ_KEY:-$cfg_zabbix_seq_key}")"
|
||||||
ZABBIX_ALERT_MAX_BYTES="$(trim_value "${ZABBIX_ALERT_MAX_BYTES:-$cfg_zabbix_alert_max_bytes}")"
|
ZABBIX_ALERT_MAX_BYTES="$(trim_value "${ZABBIX_ALERT_MAX_BYTES:-$cfg_zabbix_alert_max_bytes}")"
|
||||||
if ! [[ "${ZABBIX_ALERT_MAX_BYTES:-250}" =~ ^[0-9]+$ ]]; then
|
if ! [[ "${ZABBIX_ALERT_MAX_BYTES:-0}" =~ ^[0-9]+$ ]]; then
|
||||||
printf 'WARN: ZABBIX_ALERT_MAX_BYTES="%s" не число, использую 250\n' "${ZABBIX_ALERT_MAX_BYTES:-}" >&2
|
printf 'WARN: ZABBIX_ALERT_MAX_BYTES="%s" не число, использую 0\n' "${ZABBIX_ALERT_MAX_BYTES:-}" >&2
|
||||||
ZABBIX_ALERT_MAX_BYTES=250
|
ZABBIX_ALERT_MAX_BYTES=0
|
||||||
fi
|
fi
|
||||||
|
ZABBIX_SEQ_STATE_FILE="$(trim_value "${ZABBIX_SEQ_STATE_FILE:-$cfg_zabbix_seq_state_file}")"
|
||||||
LOG_FILE="${LOG_FILE:-$cfg_log_file}"
|
LOG_FILE="${LOG_FILE:-$cfg_log_file}"
|
||||||
LAST_HEARTBEAT_FILE="${LAST_HEARTBEAT_FILE:-$cfg_last_heartbeat_file}"
|
LAST_HEARTBEAT_FILE="${LAST_HEARTBEAT_FILE:-$cfg_last_heartbeat_file}"
|
||||||
LAST_REPORT_FILE="${LAST_REPORT_FILE:-$cfg_last_report_file}"
|
LAST_REPORT_FILE="${LAST_REPORT_FILE:-$cfg_last_report_file}"
|
||||||
@@ -265,6 +268,8 @@ load_config() {
|
|||||||
MAIL_TO="$(trim_value "${MAIL_TO:-$cfg_mail_to}")"
|
MAIL_TO="$(trim_value "${MAIL_TO:-$cfg_mail_to}")"
|
||||||
MAIL_SMTP_STARTTLS="${MAIL_SMTP_STARTTLS:-$cfg_mail_smtp_starttls}"
|
MAIL_SMTP_STARTTLS="${MAIL_SMTP_STARTTLS:-$cfg_mail_smtp_starttls}"
|
||||||
MAIL_SMTP_SSL="${MAIL_SMTP_SSL:-$cfg_mail_smtp_ssl}"
|
MAIL_SMTP_SSL="${MAIL_SMTP_SSL:-$cfg_mail_smtp_ssl}"
|
||||||
|
|
||||||
|
_sshmon_zbx_load_seq_state
|
||||||
}
|
}
|
||||||
|
|
||||||
# Дата/время для ежедневного отчёта (час DAILY_REPORT_HOUR и «сегодня» в LAST_REPORT_FILE).
|
# Дата/время для ежедневного отчёта (час DAILY_REPORT_HOUR и «сегодня» в LAST_REPORT_FILE).
|
||||||
@@ -602,17 +607,31 @@ PY
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# zabbix_sender с произвольными аргументами (-i файл или -k/-o и т.д.). Весь вывод в zlog.
|
# Восстановить счётчик seq из файла (после перезапуска не начинать с 1).
|
||||||
# Успех только по ответу сервера failed: 0 (exit-код и строка «sent:» часто обманчивы).
|
_sshmon_zbx_load_seq_state() {
|
||||||
# Ошибка разбора входа: обычно есть «Sending failed.» — не путать с успехом.
|
[ -z "${ZABBIX_SEQ_STATE_FILE:-}" ] && return 0
|
||||||
_sshmon_zbx_send() {
|
[ ! -f "$ZABBIX_SEQ_STATE_FILE" ] && return 0
|
||||||
|
local s
|
||||||
|
s="$(tr -d ' \t\r\n' <"$ZABBIX_SEQ_STATE_FILE" 2>/dev/null || true)"
|
||||||
|
if [[ "$s" =~ ^[0-9]+$ ]] && [ "$s" -ge "${ZABBIX_EVENT_SEQ:-0}" ]; then
|
||||||
|
ZABBIX_EVENT_SEQ="$s"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_sshmon_zbx_save_seq_state() {
|
||||||
|
[ -z "${ZABBIX_SEQ_STATE_FILE:-}" ] && return 0
|
||||||
|
local d
|
||||||
|
d="$(dirname "$ZABBIX_SEQ_STATE_FILE")"
|
||||||
|
mkdir -p "$d" 2>/dev/null || true
|
||||||
|
printf '%s\n' "${ZABBIX_EVENT_SEQ:-0}" >"${ZABBIX_SEQ_STATE_FILE}.tmp" 2>/dev/null || return 0
|
||||||
|
mv -f "${ZABBIX_SEQ_STATE_FILE}.tmp" "$ZABBIX_SEQ_STATE_FILE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Разбор вывода zabbix_sender (stdout+stderr в zlog).
|
||||||
|
_sshmon_zbx_parse_sender_output() {
|
||||||
local log_tag="$1"
|
local log_tag="$1"
|
||||||
local zlog="$2"
|
local zlog="$2"
|
||||||
shift 2
|
local zrc="${3:-0}"
|
||||||
[ -n "${ZABBIX_SERVER:-}" ] && [ -n "${ZABBIX_HOST_NAME:-}" ] || return 1
|
|
||||||
: >"$zlog"
|
|
||||||
zabbix_sender -z "$ZABBIX_SERVER" -s "$ZABBIX_HOST_NAME" "$@" >"$zlog" 2>&1 || :
|
|
||||||
local zrc=$?
|
|
||||||
if grep -Fq 'Sending failed.' "$zlog" 2>/dev/null; then
|
if grep -Fq 'Sending failed.' "$zlog" 2>/dev/null; then
|
||||||
write_log "WARN: zabbix_sender ${log_tag}: exit ${zrc}, $(tr '\n\r' ' ' <"$zlog")"
|
write_log "WARN: zabbix_sender ${log_tag}: exit ${zrc}, $(tr '\n\r' ' ' <"$zlog")"
|
||||||
return 1
|
return 1
|
||||||
@@ -623,7 +642,7 @@ _sshmon_zbx_send() {
|
|||||||
if [ -s "$zlog" ]; then
|
if [ -s "$zlog" ]; then
|
||||||
write_log "WARN: zabbix_sender ${log_tag}: exit ${zrc}, $(tr '\n\r' ' ' <"$zlog")"
|
write_log "WARN: zabbix_sender ${log_tag}: exit ${zrc}, $(tr '\n\r' ' ' <"$zlog")"
|
||||||
if grep -Eq 'failed:[[:space:]]*[1-9][0-9]*[[:space:]]*;' "$zlog" 2>/dev/null; then
|
if grep -Eq 'failed:[[:space:]]*[1-9][0-9]*[[:space:]]*;' "$zlog" 2>/dev/null; then
|
||||||
write_log "WARN: Zabbix отклонил значение (${log_tag}): хост/ключ/тип item; длинный UTF-8 (эмодзи, кириллица) часто не влезает в trapper «Character» (~255 байт) — смените item на «Text» или задайте ZABBIX_ALERT_MAX_BYTES."
|
write_log "WARN: Zabbix отклонил значение (${log_tag}): хост, ключ trapper, тип item, препроцессинг или лимит; для длинных текстов — ZABBIX_ALERT_MAX_BYTES."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
write_log "WARN: zabbix_sender ${log_tag}: exit ${zrc}, вывод пуст (PATH zabbix_sender, сеть до ${ZABBIX_SERVER})"
|
write_log "WARN: zabbix_sender ${log_tag}: exit ${zrc}, вывод пуст (PATH zabbix_sender, сеть до ${ZABBIX_SERVER})"
|
||||||
@@ -631,12 +650,25 @@ _sshmon_zbx_send() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# zabbix_sender с произвольными аргументами (-i файл или -k/-o и т.д.). Весь вывод в zlog.
|
||||||
|
# Успех только по ответу сервера failed: 0 (exit-код и строка «sent:» часто обманчивы).
|
||||||
|
_sshmon_zbx_send() {
|
||||||
|
local log_tag="$1"
|
||||||
|
local zlog="$2"
|
||||||
|
shift 2
|
||||||
|
[ -n "${ZABBIX_SERVER:-}" ] && [ -n "${ZABBIX_HOST_NAME:-}" ] || return 1
|
||||||
|
: >"$zlog"
|
||||||
|
zabbix_sender -z "$ZABBIX_SERVER" -s "$ZABBIX_HOST_NAME" "$@" >"$zlog" 2>&1 || :
|
||||||
|
local zrc=$?
|
||||||
|
_sshmon_zbx_parse_sender_output "$log_tag" "$zlog" "$zrc"
|
||||||
|
}
|
||||||
|
|
||||||
# Укладывает текст в лимит байт UTF-8 (Zabbix trapper «Character»). 0 = без усечения.
|
# Укладывает текст в лимит байт UTF-8 (Zabbix trapper «Character»). 0 = без усечения.
|
||||||
_sshmon_zbx_truncate_alert_value() {
|
_sshmon_zbx_truncate_alert_value() {
|
||||||
local text="$1"
|
local text="$1"
|
||||||
local maxb="${ZABBIX_ALERT_MAX_BYTES:-250}"
|
local maxb="${ZABBIX_ALERT_MAX_BYTES:-0}"
|
||||||
case "$maxb" in
|
case "$maxb" in
|
||||||
''|*[!0-9]*) maxb=250 ;;
|
''|*[!0-9]*) maxb=0 ;;
|
||||||
esac
|
esac
|
||||||
if [ "$maxb" -eq 0 ]; then
|
if [ "$maxb" -eq 0 ]; then
|
||||||
printf '%s' "$text"
|
printf '%s' "$text"
|
||||||
@@ -702,28 +734,50 @@ send_zabbix_trapper() {
|
|||||||
|
|
||||||
ZABBIX_EVENT_SEQ=$((ZABBIX_EVENT_SEQ + 1))
|
ZABBIX_EVENT_SEQ=$((ZABBIX_EVENT_SEQ + 1))
|
||||||
|
|
||||||
local tmp zlog alert_rc seq_rc
|
local zlog alert_rc seq_rc tmp=""
|
||||||
tmp="$(mktemp "${TMPDIR:-/tmp}/ssh-monitor.zbx.al.XXXXXX" 2>/dev/null)" || return 1
|
zlog="$(mktemp "${TMPDIR:-/tmp}/ssh-monitor.zbx.log.XXXXXX" 2>/dev/null)" || return 1
|
||||||
zlog="$(mktemp "${TMPDIR:-/tmp}/ssh-monitor.zbx.log.XXXXXX" 2>/dev/null)" || {
|
|
||||||
rm -f "$tmp"
|
alert_rc=1
|
||||||
|
seq_rc=1
|
||||||
|
# UTF-8 в -o через python3 (argv) надёжнее, чем -i файл на части сборок zabbix_sender.
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
: >"$zlog"
|
||||||
|
printf '%s' "$one_line" | SSHMON_ZBX_TRAPPER_K="$key_al" SSHMON_ZBX_TRAPPER_LOG="$zlog" python3 -c '
|
||||||
|
import os, subprocess, sys
|
||||||
|
val = sys.stdin.read()
|
||||||
|
k = os.environ["SSHMON_ZBX_TRAPPER_K"]
|
||||||
|
path = os.environ["SSHMON_ZBX_TRAPPER_LOG"]
|
||||||
|
r = subprocess.run(
|
||||||
|
["zabbix_sender", "-z", os.environ["ZABBIX_SERVER"],
|
||||||
|
"-s", os.environ["ZABBIX_HOST_NAME"], "-k", k, "-o", val],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
with open(path, "w", encoding="utf-8", newline="\n") as fh:
|
||||||
|
fh.write((r.stdout or "") + (r.stderr or ""))
|
||||||
|
' || true
|
||||||
|
if _sshmon_zbx_parse_sender_output "alert (${key_al})" "$zlog" 0; then
|
||||||
|
alert_rc=0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
tmp="$(mktemp "${TMPDIR:-/tmp}/ssh-monitor.zbx.al.XXXXXX" 2>/dev/null)" || {
|
||||||
|
rm -f "$zlog"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
printf '%s\t%s\n' "$key_al" "$one_line" >"$tmp" || {
|
printf '%s\t%s\n' "$key_al" "$one_line" >"$tmp" || {
|
||||||
rm -f "$tmp" "$zlog"
|
rm -f "$tmp" "$zlog"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
alert_rc=1
|
|
||||||
seq_rc=1
|
|
||||||
if _sshmon_zbx_send "alert (${key_al})" "$zlog" -i "$tmp"; then
|
if _sshmon_zbx_send "alert (${key_al})" "$zlog" -i "$tmp"; then
|
||||||
alert_rc=0
|
alert_rc=0
|
||||||
fi
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
if _sshmon_zbx_send "seq (${key_sq})" "$zlog" -k "$key_sq" -o "${ZABBIX_EVENT_SEQ}"; then
|
if _sshmon_zbx_send "seq (${key_sq})" "$zlog" -k "$key_sq" -o "${ZABBIX_EVENT_SEQ}"; then
|
||||||
seq_rc=0
|
seq_rc=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$tmp" "$zlog"
|
rm -f "$zlog"
|
||||||
|
_sshmon_zbx_save_seq_state
|
||||||
|
|
||||||
# Раньше один файл с двумя строками: при failed по seq весь пакет считался неуспешным.
|
# Раньше один файл с двумя строками: при failed по seq весь пакет считался неуспешным.
|
||||||
# Текст оповещения — основной результат; seq нужен для триггеров по изменению.
|
# Текст оповещения — основной результат; seq нужен для триггеров по изменению.
|
||||||
@@ -736,8 +790,12 @@ send_zabbix_trapper() {
|
|||||||
|
|
||||||
notify_send() {
|
notify_send() {
|
||||||
local message="$1"
|
local message="$1"
|
||||||
|
local zabbix_plain="${2:-}"
|
||||||
if [ "${DRY_RUN:-0}" = "1" ]; then
|
if [ "${DRY_RUN:-0}" = "1" ]; then
|
||||||
printf '[DRY-RUN notify] %s\n' "$(printf '%s' "$message" | tr '\n' ' ')" >&2
|
printf '[DRY-RUN notify] %s\n' "$(printf '%s' "$message" | tr '\n' ' ')" >&2
|
||||||
|
if [ -n "$zabbix_plain" ]; then
|
||||||
|
printf '[DRY-RUN notify] zabbix (plain): %s\n' "$(printf '%s' "$zabbix_plain" | tr '\n' ' ')" >&2
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -753,7 +811,7 @@ notify_send() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
zabbix)
|
zabbix)
|
||||||
if send_zabbix_trapper "$message"; then
|
if send_zabbix_trapper "${zabbix_plain:-$message}"; then
|
||||||
any_ok=1
|
any_ok=1
|
||||||
else
|
else
|
||||||
write_log "WARN: канал zabbix: не удалось отправить (zabbix_sender, сеть, имя хоста/ключи в Zabbix)"
|
write_log "WARN: канал zabbix: не удалось отправить (zabbix_sender, сеть, имя хоста/ключи в Zabbix)"
|
||||||
@@ -940,7 +998,8 @@ block_ip() {
|
|||||||
message="${message}📊 Неудачных попыток: ${attempts}"$'\n'
|
message="${message}📊 Неудачных попыток: ${attempts}"$'\n'
|
||||||
message="${message}⏱️ Блокировка на: ${ban_time_hours} час(ов)"$'\n'
|
message="${message}⏱️ Блокировка на: ${ban_time_hours} час(ов)"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$message"
|
local zbx_ban="IP заблокирован: $(server_display_name_with_ip), адрес ${ip}, неудачных попыток ${attempts}, блок ${ban_time_hours} ч."
|
||||||
|
notify_send "$message" "$zbx_ban"
|
||||||
write_log "AUTO-BAN: IP $ip заблокирован на $ban_time_hours часов ($attempts попыток)"
|
write_log "AUTO-BAN: IP $ip заблокирован на $ban_time_hours часов ($attempts попыток)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,7 +1024,8 @@ unblock_expired_ips() {
|
|||||||
local message=" 🔓 IP РАЗБЛОКИРОВАН "$'\n'
|
local message=" 🔓 IP РАЗБЛОКИРОВАН "$'\n'
|
||||||
message="${message}🌐 IP: ${ip}"$'\n'
|
message="${message}🌐 IP: ${ip}"$'\n'
|
||||||
message="${message}🕐 Время разблокировки: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время разблокировки: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$message"
|
local zbx_un="IP разблокирован: $(server_display_name_with_ip), адрес ${ip}"
|
||||||
|
notify_send "$message" "$zbx_un"
|
||||||
|
|
||||||
unblocked=$((unblocked + 1))
|
unblocked=$((unblocked + 1))
|
||||||
fi
|
fi
|
||||||
@@ -1136,13 +1196,15 @@ monitor_ssh() {
|
|||||||
message="${message}👤 Пользователь: ${user}"$'\n'
|
message="${message}👤 Пользователь: ${user}"$'\n'
|
||||||
message="${message}🌐 IP адрес: ${ip:-local}"$'\n'
|
message="${message}🌐 IP адрес: ${ip:-local}"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$message"
|
local zbx_ssh="Новый SSH-вход: $(server_display_name_with_ip), пользователь ${user}, адрес ${ip:-local}"
|
||||||
|
notify_send "$message" "$zbx_ssh"
|
||||||
write_log "SSH успешный вход: $user с IP ${ip:-local}"
|
write_log "SSH успешный вход: $user с IP ${ip:-local}"
|
||||||
if [ "$user" = "root" ]; then
|
if [ "$user" = "root" ]; then
|
||||||
local rmsg=" 🔑 SSH ВХОД ПОД ROOT "$'\n'
|
local rmsg=" 🔑 SSH ВХОД ПОД ROOT "$'\n'
|
||||||
rmsg="${rmsg}🌐 IP: ${ip:-local}"$'\n'
|
rmsg="${rmsg}🌐 IP: ${ip:-local}"$'\n'
|
||||||
rmsg="${rmsg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
rmsg="${rmsg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$rmsg"
|
local zbx_root="Новый SSH-вход под root: $(server_display_name_with_ip), адрес ${ip:-local}"
|
||||||
|
notify_send "$rmsg" "$zbx_root"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
elif echo "$line" | grep -q "Failed password"; then
|
elif echo "$line" | grep -q "Failed password"; then
|
||||||
@@ -1171,7 +1233,8 @@ monitor_ssh() {
|
|||||||
message="${message}🌐 IP адрес: ${ip}"$'\n'
|
message="${message}🌐 IP адрес: ${ip}"$'\n'
|
||||||
message="${message}📊 Попытка ${current_attempts} из ${MAX_ATTEMPTS}"$'\n'
|
message="${message}📊 Попытка ${current_attempts} из ${MAX_ATTEMPTS}"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$message"
|
local zbx_fail="Неудачный SSH: $(server_display_name_with_ip), пользователь ${user}, адрес ${ip}, попытка ${current_attempts} из ${MAX_ATTEMPTS}"
|
||||||
|
notify_send "$message" "$zbx_fail"
|
||||||
fi
|
fi
|
||||||
write_log "SSH неудачная попытка: $user с IP $ip (попытка $current_attempts из $MAX_ATTEMPTS)"
|
write_log "SSH неудачная попытка: $user с IP $ip (попытка $current_attempts из $MAX_ATTEMPTS)"
|
||||||
fi
|
fi
|
||||||
@@ -1286,7 +1349,15 @@ monitor_sudo() {
|
|||||||
message="${message}💻 Команда: ${sudo_cmd}"$'\n'
|
message="${message}💻 Команда: ${sudo_cmd}"$'\n'
|
||||||
message="${message}📁 Директория: ${pwd:-unknown}"$'\n'
|
message="${message}📁 Директория: ${pwd:-unknown}"$'\n'
|
||||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$message"
|
local cmdz="${sudo_cmd}"
|
||||||
|
if [ "${#cmdz}" -gt 200 ]; then
|
||||||
|
cmdz="${cmdz:0:200}…"
|
||||||
|
fi
|
||||||
|
local zbx_sudo="Повышение привилегий (sudo): $(server_display_name_with_ip), пользователь ${user}, команда: ${cmdz}"
|
||||||
|
if [ -n "${run_as:-}" ]; then
|
||||||
|
zbx_sudo="${zbx_sudo}, от имени ${run_as}"
|
||||||
|
fi
|
||||||
|
notify_send "$message" "$zbx_sudo"
|
||||||
write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}"
|
write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}"
|
||||||
fi
|
fi
|
||||||
done <<<"$sudo_logs"
|
done <<<"$sudo_logs"
|
||||||
@@ -1327,7 +1398,8 @@ monitor_security_events() {
|
|||||||
local m=" 🔐 ВНИМАНИЕ: изменение SSH host key / MITM? "$'\n'
|
local m=" 🔐 ВНИМАНИЕ: изменение SSH host key / MITM? "$'\n'
|
||||||
m="${m}$(printf '%s\n' "$hk")"
|
m="${m}$(printf '%s\n' "$hk")"
|
||||||
m="${m}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
m="${m}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$m"
|
local zbx_hk="SSH host key / MITM: $(server_display_name_with_ip), см. журнал"
|
||||||
|
notify_send "$m" "$zbx_hk"
|
||||||
write_log "SECURITY: обнаружено сообщение об изменении host key"
|
write_log "SECURITY: обнаружено сообщение об изменении host key"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1348,7 +1420,8 @@ monitor_security_events() {
|
|||||||
bm="${bm}🌐 IP: ${ip}"$'\n'
|
bm="${bm}🌐 IP: ${ip}"$'\n'
|
||||||
bm="${bm}📊 Неудачных попыток за ~${BRUTE_WINDOW_SEC}s: ${count}"$'\n'
|
bm="${bm}📊 Неудачных попыток за ~${BRUTE_WINDOW_SEC}s: ${count}"$'\n'
|
||||||
bm="${bm}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
bm="${bm}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$bm"
|
local zbx_br="SSH брутфорс: $(server_display_name_with_ip), IP ${ip}, попыток ~${count} за ${BRUTE_WINDOW_SEC}s"
|
||||||
|
notify_send "$bm" "$zbx_br"
|
||||||
write_log "SECURITY: массовый брутфорс? IP=$ip fails=${count} за окно ${BRUTE_WINDOW_SEC}s"
|
write_log "SECURITY: массовый брутфорс? IP=$ip fails=${count} за окно ${BRUTE_WINDOW_SEC}s"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1459,7 +1532,8 @@ monitor_logind() {
|
|||||||
msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n'
|
msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n'
|
||||||
msg="${msg}🆔 Сессия: ${sid:-?}"$'\n'
|
msg="${msg}🆔 Сессия: ${sid:-?}"$'\n'
|
||||||
msg="${msg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
msg="${msg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$msg"
|
local zbx_ln="Сессия logind: $(server_display_name_with_ip), пользователь ${user:-?}, sid ${sid:-?}"
|
||||||
|
notify_send "$msg" "$zbx_ln"
|
||||||
write_log "LOGIND: новая сессия: ${line}"
|
write_log "LOGIND: новая сессия: ${line}"
|
||||||
else
|
else
|
||||||
write_log "LOGIND: новая сессия (разбор user/sid не удался): ${line}"
|
write_log "LOGIND: новая сессия (разбор user/sid не удался): ${line}"
|
||||||
@@ -1476,7 +1550,8 @@ monitor_logind() {
|
|||||||
local msg_r=" 🚪 СЕССИЯ ЗАКРЫТА (systemd-logind) "$'\n'
|
local msg_r=" 🚪 СЕССИЯ ЗАКРЫТА (systemd-logind) "$'\n'
|
||||||
msg_r="${msg_r}🆔 Сессия: ${sess_r:-?}"$'\n'
|
msg_r="${msg_r}🆔 Сессия: ${sess_r:-?}"$'\n'
|
||||||
msg_r="${msg_r}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
msg_r="${msg_r}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$msg_r"
|
local zbx_lr="Сессия закрыта (logind): $(server_display_name_with_ip), sid ${sess_r:-?}"
|
||||||
|
notify_send "$msg_r" "$zbx_lr"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "$low" == *"failed"* ]]; then
|
elif [[ "$low" == *"failed"* ]]; then
|
||||||
@@ -1484,7 +1559,12 @@ monitor_logind() {
|
|||||||
local msg_f=" ❌ СБОЙ (systemd-logind) "$'\n'
|
local msg_f=" ❌ СБОЙ (systemd-logind) "$'\n'
|
||||||
msg_f="${msg_f}📝 ${line}"$'\n'
|
msg_f="${msg_f}📝 ${line}"$'\n'
|
||||||
msg_f="${msg_f}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
msg_f="${msg_f}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||||
notify_send "$msg_f"
|
local ln_f="$line"
|
||||||
|
if [ "${#ln_f}" -gt 180 ]; then
|
||||||
|
ln_f="${ln_f:0:180}…"
|
||||||
|
fi
|
||||||
|
local zbx_lf="Сбой logind: $(server_display_name_with_ip), ${ln_f}"
|
||||||
|
notify_send "$msg_f" "$zbx_lf"
|
||||||
write_log "LOGIND: сбой: $line"
|
write_log "LOGIND: сбой: $line"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1613,7 +1693,8 @@ send_daily_report() {
|
|||||||
message="${message} Нет активных пользователей"$'\n'
|
message="${message} Нет активных пользователей"$'\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
notify_send "$message"
|
local zbx_rep="Отчет ssh-monitor 24h: $(server_display_name_with_ip), ok=${successful_logins:-0} fail=${failed_logins:-0} sudo=${sudo_commands:-0} bans=${active_bans}"
|
||||||
|
notify_send "$message" "$zbx_rep"
|
||||||
write_log "Ежедневный отчет отправлен"
|
write_log "Ежедневный отчет отправлен"
|
||||||
|
|
||||||
echo "$(report_date '+%F')" >"$LAST_REPORT_FILE"
|
echo "$(report_date '+%F')" >"$LAST_REPORT_FILE"
|
||||||
@@ -1672,7 +1753,8 @@ send_heartbeat() {
|
|||||||
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
||||||
message="${message}🕐 Время: ${timestamp_human}"
|
message="${message}🕐 Время: ${timestamp_human}"
|
||||||
|
|
||||||
notify_send "$message"
|
local zbx_hb="Heartbeat ssh-monitor: $(server_display_name_with_ip)"
|
||||||
|
notify_send "$message" "$zbx_hb"
|
||||||
echo "$timestamp" >"$LAST_HEARTBEAT_FILE"
|
echo "$timestamp" >"$LAST_HEARTBEAT_FILE"
|
||||||
write_log "Heartbeat отправлен"
|
write_log "Heartbeat отправлен"
|
||||||
}
|
}
|
||||||
@@ -1736,7 +1818,8 @@ send_startup_notification() {
|
|||||||
message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n'
|
message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n'
|
||||||
message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)"
|
message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)"
|
||||||
|
|
||||||
notify_send "$message"
|
local zbx_su="Старт ssh-monitor v${SSH_MONITOR_VERSION:-?}: $(server_display_name_with_ip), whitelist=${whitelist_count}"
|
||||||
|
notify_send "$message" "$zbx_su"
|
||||||
write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)"
|
write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1750,7 +1833,8 @@ send_shutdown_notification() {
|
|||||||
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
message="${message}🖥️ Сервер: $(server_display_name_with_ip)"$'\n'
|
||||||
message="${message}🕐 Время остановки: ${timestamp}"
|
message="${message}🕐 Время остановки: ${timestamp}"
|
||||||
|
|
||||||
notify_send "$message"
|
local zbx_sd="Стоп ssh-monitor: $(server_display_name_with_ip)"
|
||||||
|
notify_send "$message" "$zbx_sd"
|
||||||
write_log "Скрипт мониторинга остановлен"
|
write_log "Скрипт мониторинга остановлен"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1809,8 +1893,8 @@ run_check_config() {
|
|||||||
printf 'LOGIND_NOTIFY_NEW=%s LOGIND_NOTIFY_REMOVED=%s LOGIND_NOTIFY_FAILED=%s LOGIND_SKIP_REMOTE=%s\n' \
|
printf 'LOGIND_NOTIFY_NEW=%s LOGIND_NOTIFY_REMOVED=%s LOGIND_NOTIFY_FAILED=%s LOGIND_SKIP_REMOTE=%s\n' \
|
||||||
"${LOGIND_NOTIFY_NEW:-}" "${LOGIND_NOTIFY_REMOVED:-}" "${LOGIND_NOTIFY_FAILED:-}" "${LOGIND_SKIP_REMOTE:-}"
|
"${LOGIND_NOTIFY_NEW:-}" "${LOGIND_NOTIFY_REMOVED:-}" "${LOGIND_NOTIFY_FAILED:-}" "${LOGIND_SKIP_REMOTE:-}"
|
||||||
printf 'ZABBIX_SERVER=%s ZABBIX_HOST_NAME=%s\n' "${ZABBIX_SERVER:-}" "${ZABBIX_HOST_NAME:-}"
|
printf 'ZABBIX_SERVER=%s ZABBIX_HOST_NAME=%s\n' "${ZABBIX_SERVER:-}" "${ZABBIX_HOST_NAME:-}"
|
||||||
printf 'ZABBIX_ALERT_KEY=%s ZABBIX_SEQ_KEY=%s ZABBIX_ALERT_MAX_BYTES=%s (0=без усечения)\n' \
|
printf 'ZABBIX_ALERT_KEY=%s ZABBIX_SEQ_KEY=%s ZABBIX_ALERT_MAX_BYTES=%s ZABBIX_SEQ_STATE_FILE=%s\n' \
|
||||||
"${ZABBIX_ALERT_KEY:-}" "${ZABBIX_SEQ_KEY:-}" "${ZABBIX_ALERT_MAX_BYTES:-250}"
|
"${ZABBIX_ALERT_KEY:-}" "${ZABBIX_SEQ_KEY:-}" "${ZABBIX_ALERT_MAX_BYTES:-0}" "${ZABBIX_SEQ_STATE_FILE:-}"
|
||||||
if [ -n "${ZABBIX_SERVER:-}" ] && [ -n "${ZABBIX_HOST_NAME:-}" ]; then
|
if [ -n "${ZABBIX_SERVER:-}" ] && [ -n "${ZABBIX_HOST_NAME:-}" ]; then
|
||||||
if command -v zabbix_sender >/dev/null 2>&1; then
|
if command -v zabbix_sender >/dev/null 2>&1; then
|
||||||
printf 'zabbix_sender: найден (%s)\n' "$(command -v zabbix_sender)"
|
printf 'zabbix_sender: найден (%s)\n' "$(command -v zabbix_sender)"
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ ZABBIX_SERVER=""
|
|||||||
ZABBIX_HOST_NAME=""
|
ZABBIX_HOST_NAME=""
|
||||||
ZABBIX_ALERT_KEY="ssh.monitor.alert"
|
ZABBIX_ALERT_KEY="ssh.monitor.alert"
|
||||||
ZABBIX_SEQ_KEY="ssh.monitor.seq"
|
ZABBIX_SEQ_KEY="ssh.monitor.seq"
|
||||||
# Макс. байт UTF-8 для значения trapper (по умолчанию 250 — типичный лимит «Character» в Zabbix).
|
# В Zabbix уходит короткий plain-текст (второй аргумент notify_send); 0 = не урезать длину.
|
||||||
# Полные тексты как в Telegram — item ssh.monitor.alert лучше типа «Text», тогда можно ZABBIX_ALERT_MAX_BYTES="0".
|
ZABBIX_ALERT_MAX_BYTES="0"
|
||||||
ZABBIX_ALERT_MAX_BYTES="250"
|
# Последний ssh.monitor.seq на диске — после перезапуска счётчик не сбрасывается.
|
||||||
|
ZABBIX_SEQ_STATE_FILE="/var/log/ssh_monitor_zabbix_seq"
|
||||||
|
|
||||||
# SMTP (python3). Для авто-режима почты нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
# SMTP (python3). Для авто-режима почты нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
||||||
MAIL_SMTP_HOST=""
|
MAIL_SMTP_HOST=""
|
||||||
|
|||||||
Reference in New Issue
Block a user