fix(notify): отправка во все каналы NOTIFY_ORDER, не только первый успех

- BACKUP_WEBHOOK только если все каналы неуспешны

- README/conf.example; версия 0.9.5.3
This commit is contained in:
ssh-monitor
2026-04-14 10:50:38 +10:00
parent 98f63e77ad
commit 27219355c2
3 changed files with 29 additions and 16 deletions
+4 -4
View File
@@ -14,14 +14,14 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
- для **Telegram**: `TELEGRAM_BOT_TOKEN` и `TELEGRAM_CHAT_ID`; - для **Telegram**: `TELEGRAM_BOT_TOKEN` и `TELEGRAM_CHAT_ID`;
- или настройте **Zabbix** / **почту** по переменным ниже при `NOTIFY_ORDER=""` (автовыбор каналов). - или настройте **Zabbix** / **почту** по переменным ниже при `NOTIFY_ORDER=""` (автовыбор каналов).
**Обязательное условие:** после загрузки конфигурации в цепочке **`NOTIFY_CHAIN`** должен быть **минимум один** канал (`telegram`, `zabbix` или `email`). Если каналов **нет** (пустой `NOTIFY_ORDER` и ни Telegram, ни Zabbix, ни SMTP не удовлетворяют критериям «настроен», либо в `NOTIFY_ORDER` остались только неизвестные имена), скрипт **не входит** в основной цикл и завершается с кодом **1**, в stderr: **`Не настроен ни один канал отправки оповещений`**. Это же правило действует для **`--check-config`** и **`--dry-run`**. Резервный **`BACKUP_WEBHOOK_URL`** в эту проверку **не входит** — он используется только когда основные каналы уже есть, но доставка через них не удалась. **Обязательное условие:** после загрузки конфигурации в цепочке **`NOTIFY_CHAIN`** должен быть **минимум один** канал (`telegram`, `zabbix` или `email`). Если каналов **нет** (пустой `NOTIFY_ORDER` и ни Telegram, ни Zabbix, ни SMTP не удовлетворяют критериям «настроен», либо в `NOTIFY_ORDER` остались только неизвестные имена), скрипт **не входит** в основной цикл и завершается с кодом **1**, в stderr: **`Не настроен ни один канал отправки оповещений`**. Это же правило действует для **`--check-config`** и **`--dry-run`**. Резервный **`BACKUP_WEBHOOK_URL`** в эту проверку **не входит** — он используется только если **ни один** канал из **`NOTIFY_CHAIN`** не смог доставить сообщение.
Поддерживаемые параметры: Поддерживаемые параметры:
- `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID` - `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`
- `NOTIFY_ORDER`явная очередь каналов оповещений (`telegram`, `zabbix`, `email` и сокращения); пусто = в цепочку попадают только настроенные каналы (порядок по умолчанию см. в `ssh-monitor.conf.example`). Если итоговая цепочка пуста — скрипт не стартует (см. раздел **«Каналы оповещений и пустой NOTIFY_CHAIN»**). - `NOTIFY_ORDER`список каналов (`telegram`, `zabbix`, `email` и сокращения **tg**, **zbx**, **mail**); при каждом оповещении скрипт **пытается отправить во все перечисленные** каналы по порядку (не «первый успешный — и стоп»). Пусто = в цепочку попадают только настроенные каналы (порядок по умолчанию см. в `ssh-monitor.conf.example`). Если итоговая цепочка пуста — скрипт не стартует (см. раздел **«Каналы оповещений и пустой NOTIFY_CHAIN»**).
- `ZABBIX_SERVER`, `ZABBIX_HOST_NAME`, `ZABBIX_ALERT_KEY`, `ZABBIX_SEQ_KEY` — отправка в Zabbix через `zabbix_sender`; подробнее — раздел **«Zabbix (`zabbix_sender`)»** ниже - `ZABBIX_SERVER`, `ZABBIX_HOST_NAME`, `ZABBIX_ALERT_KEY`, `ZABBIX_SEQ_KEY` — отправка в Zabbix через `zabbix_sender`; подробнее — раздел **«Zabbix (`zabbix_sender`)»** ниже
- `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` (опционально), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS` - `DAILY_REPORT_HOUR` (0..23), `DAILY_REPORT_TZ` (опционально), `NOTIFY_TZ` (опционально), `SSH_ACCEPT_NOTIFY_DEDUP_SEC`, `DAILY_REPORT_TOP_IPS`
@@ -38,7 +38,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **`
- `TELEGRAM_BOT_TOKEN` — токен Telegram-бота для отправки уведомлений. - `TELEGRAM_BOT_TOKEN` — токен Telegram-бота для отправки уведомлений.
- `TELEGRAM_CHAT_ID` — ID чата/пользователя, куда отправляются уведомления. - `TELEGRAM_CHAT_ID` — ID чата/пользователя, куда отправляются уведомления.
- `NOTIFY_ORDER` — CSV имён каналов (`telegram`, `zabbix`, `email` или `tg`, `zbx`, `mail`). Пустая строка: автоматически собирается цепочка только из реально настроенных каналов (по умолчанию порядок telegram → zabbix → email; критерии «настроен» — **таблица** в разделе **«Каналы оповещений и пустой NOTIFY_CHAIN»**). Если после сборки цепочки **нет ни одного** канала, скрипт **сразу завершается** с ошибкой: **«Не настроен ни один канал отправки оповещений»** (в том числе режим `--check-config` и `--dry-run`). - `NOTIFY_ORDER` — CSV имён каналов (`telegram`, `zabbix`, `email` или `tg`, `zbx`, `mail`). Для **каждого** оповещения выполняется попытка доставки **во все** перечисленные каналы (при ошибке одного остальные всё равно пробуются; неудачи пишутся в **`LOG_FILE`**). Пустая строка: автоматически собирается цепочка только из реально настроенных каналов (по умолчанию порядок telegram → zabbix → email; критерии «настроен» — **таблица** в разделе **«Каналы оповещений и пустой NOTIFY_CHAIN»**). Если после сборки цепочки **нет ни одного** канала, скрипт **сразу завершается** с ошибкой: **«Не настроен ни один канал отправки оповещений»** (в том числе режим `--check-config` и `--dry-run`).
- `ZABBIX_SERVER` — имя или IP сервера Zabbix для `zabbix_sender -z` (пусто = канал Zabbix отключён). - `ZABBIX_SERVER` — имя или IP сервера Zabbix для `zabbix_sender -z` (пусто = канал Zabbix отключён).
- `ZABBIX_HOST_NAME` — имя **хоста в Zabbix**, как в конфигурации агента/шаблона (`-s` у `zabbix_sender`); должно совпадать с тем, для какого хоста созданы trapper-элементы. - `ZABBIX_HOST_NAME` — имя **хоста в Zabbix**, как в конфигурации агента/шаблона (`-s` у `zabbix_sender`); должно совпадать с тем, для какого хоста созданы trapper-элементы.
- `ZABBIX_ALERT_KEY` — ключ **первого** trapper-элемента: в него уходит **текст** оповещения (одна строка, переводы строк заменены пробелами). - `ZABBIX_ALERT_KEY` — ключ **первого** trapper-элемента: в него уходит **текст** оповещения (одна строка, переводы строк заменены пробелами).
+22 -11
View File
@@ -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.2" SSH_MONITOR_VERSION="0.9.5.3"
TELEGRAM_BOT_TOKEN="" TELEGRAM_BOT_TOKEN=""
TELEGRAM_CHAT_ID="" TELEGRAM_CHAT_ID=""
@@ -291,7 +291,7 @@ is_email_configured() {
} }
# Заполняет NOTIFY_CHAIN: при пустом NOTIFY_ORDER — только реально настроенные каналы (telegram → zabbix → email). # Заполняет NOTIFY_CHAIN: при пустом NOTIFY_ORDER — только реально настроенные каналы (telegram → zabbix → email).
# При непустом NOTIFY_ORDER — ровно указанная очередь (имена каналов), без добавления «лишних». # При непустом NOTIFY_ORDER — ровно указанный список (имена каналов), без добавления «лишних»; notify_send шлёт во ВСЕ каналы из списка.
refresh_notify_chain() { refresh_notify_chain() {
NOTIFY_CHAIN=() NOTIFY_CHAIN=()
local order_raw local order_raw
@@ -470,7 +470,7 @@ write_log() {
} }
# ============================================ # ============================================
# УВЕДОМЛЕНИЯ: telegram / zabbix / email (каскад по NOTIFY_CHAIN) # УВЕДОМЛЕНИЯ: telegram / zabbix / email по NOTIFY_CHAIN во все перечисленные каналы; BACKUP_WEBHOOK только если все неуспешны.
# ============================================ # ============================================
send_email_raw() { send_email_raw() {
@@ -638,33 +638,44 @@ notify_send() {
return 0 return 0
fi fi
local any_ok=0
local ch local ch
for ch in "${NOTIFY_CHAIN[@]}"; do for ch in "${NOTIFY_CHAIN[@]}"; do
case "$ch" in case "$ch" in
telegram) telegram)
if send_telegram_raw "$message"; then if send_telegram_raw "$message"; then
return 0 any_ok=1
else
write_log "WARN: канал telegram: не удалось отправить уведомление"
fi fi
;; ;;
zabbix) zabbix)
if send_zabbix_trapper "$message"; then if send_zabbix_trapper "$message"; then
return 0 any_ok=1
else
write_log "WARN: канал zabbix: не удалось отправить (zabbix_sender, сеть, имя хоста/ключи в Zabbix)"
fi fi
;; ;;
email) email)
if send_email_raw "$message"; then if send_email_raw "$message"; then
return 0 any_ok=1
else
write_log "WARN: канал email: не удалось отправить уведомление"
fi fi
;; ;;
esac esac
done done
if send_backup_webhook_raw "$message"; then if [ "$any_ok" -eq 1 ]; then
write_log "WARN: основные каналы недоступны, сообщение ушло в BACKUP_WEBHOOK_URL"
return 0 return 0
fi fi
write_log "WARN: не удалось доставить уведомление (каналы: $(notify_chain_human); BACKUP_WEBHOOK не задан или недоступен)" if send_backup_webhook_raw "$message"; then
write_log "WARN: все каналы из NOTIFY_CHAIN недоступны, сообщение ушло в BACKUP_WEBHOOK_URL"
return 0
fi
write_log "WARN: не удалось доставить уведомление ни в один канал ($(notify_chain_human); BACKUP_WEBHOOK не задан или недоступен)"
return 1 return 1
} }
@@ -1620,7 +1631,7 @@ send_startup_notification() {
message="${message}🚫 Автоблокировка IP после ${MAX_ATTEMPTS} попыток на ${BAN_TIME} секунд"$'\n' message="${message}🚫 Автоблокировка IP после ${MAX_ATTEMPTS} попыток на ${BAN_TIME} секунд"$'\n'
message="${message}⚪ Белый список: ${whitelist_count} IP/подсетей"$'\n' message="${message}⚪ Белый список: ${whitelist_count} IP/подсетей"$'\n'
message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n' message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n'
message="${message}📢 Каналы уведомлений (каскад): $(notify_chain_human)" message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)"
notify_send "$message" notify_send "$message"
write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)" write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)"
@@ -1678,7 +1689,7 @@ run_check_config() {
printf 'Проверка конфигурации ssh-monitor\n' printf 'Проверка конфигурации ssh-monitor\n'
printf 'SSH_MONITOR_VERSION=%s\n' "${SSH_MONITOR_VERSION:-}" printf 'SSH_MONITOR_VERSION=%s\n' "${SSH_MONITOR_VERSION:-}"
printf 'CONFIG_FILE=%s\n' "$CONFIG_FILE" printf 'CONFIG_FILE=%s\n' "$CONFIG_FILE"
printf 'NOTIFY_ORDER=%s (пусто = авто: только настроенные, порядок telegram,zabbix,email)\n' "${NOTIFY_ORDER:-}" printf 'NOTIFY_ORDER=%s (пусто = авто: только настроенные; при событии — отправка во все каналы списка)\n' "${NOTIFY_ORDER:-}"
printf 'NOTIFY_CHAIN (эффективная очередь)=%s\n' "$(notify_chain_human)" printf 'NOTIFY_CHAIN (эффективная очередь)=%s\n' "$(notify_chain_human)"
printf 'TELEGRAM: %s\n' "$([ -n "$TELEGRAM_BOT_TOKEN" ] && echo настроен || echo НЕ настроен)" printf 'TELEGRAM: %s\n' "$([ -n "$TELEGRAM_BOT_TOKEN" ] && echo настроен || echo НЕ настроен)"
printf 'BACKUP_WEBHOOK_URL: %s\n' "$([ -n "${BACKUP_WEBHOOK_URL:-}" ] && echo настроен || echo не задан)" printf 'BACKUP_WEBHOOK_URL: %s\n' "$([ -n "${BACKUP_WEBHOOK_URL:-}" ] && echo настроен || echo не задан)"
+3 -1
View File
@@ -1,7 +1,7 @@
TELEGRAM_BOT_TOKEN="replace_with_telegram_bot_token" TELEGRAM_BOT_TOKEN="replace_with_telegram_bot_token"
TELEGRAM_CHAT_ID="replace_with_telegram_chat_id" TELEGRAM_CHAT_ID="replace_with_telegram_chat_id"
# Резервный webhook, если все каналы из NOTIFY_CHAIN недоступны (JSON {"text":"..."}). # Резервный webhook, если ВСЕ каналы из NOTIFY_CHAIN не смогли доставить сообщение (JSON {"text":"..."}).
# ВАЖНО: BACKUP_WEBHOOK_URL НЕ заменяет основной канал — без хотя бы одного из telegram/zabbix/email # ВАЖНО: BACKUP_WEBHOOK_URL НЕ заменяет основной канал — без хотя бы одного из telegram/zabbix/email
# скрипт сразу завершится с ошибкой в stderr: «Не настроен ни один канал отправки оповещений» (код 1), # скрипт сразу завершится с ошибкой в stderr: «Не настроен ни один канал отправки оповещений» (код 1),
# в том числе при --check-config и --dry-run. Сначала настройте Telegram, Zabbix или SMTP ниже. # в том числе при --check-config и --dry-run. Сначала настройте Telegram, Zabbix или SMTP ниже.
@@ -9,6 +9,8 @@ BACKUP_WEBHOOK_URL=""
# --- Очередь оповещений (NOTIFY_ORDER) --- # --- Очередь оповещений (NOTIFY_ORDER) ---
# CSV: telegram, zabbix, email (или сокращения tg, zbx, mail). # CSV: telegram, zabbix, email (или сокращения tg, zbx, mail).
# При каждом событии скрипт ПЫТАЕТСЯ ОТПРАВИТЬ ВО ВСЕ перечисленные каналы по порядку (не только в первый успешный).
# Неудача одного канала не отменяет отправку в остальные; подробности — в LOG_FILE (WARN по каналам).
# #
# Пустая строка NOTIFY_ORDER="" = авто: в цепочку NOTIFY_CHAIN попадают ТОЛЬКО реально настроенные каналы, # Пустая строка NOTIFY_ORDER="" = авто: в цепочку NOTIFY_CHAIN попадают ТОЛЬКО реально настроенные каналы,
# порядок по умолчанию: telegram → zabbix → email (см. критерии «настроен» в README, раздел про каналы). # порядок по умолчанию: telegram → zabbix → email (см. критерии «настроен» в README, раздел про каналы).