Co-authored-by: Cursor <cursoragent@cursor.com>
26 KiB
ssh-monitor
Bash-скрипт мониторинга SSH, SUDO и событий systemd-logind (локальные/графические сессии и др.) с уведомлениями в Telegram и по электронной почте (SMTP), опциональным резервным webhook, авто-блокировкой IP (ipset + iptables / ip6tables: наборы hash:ip с таймаутом, одно правило -m set; нюансы — IPSET-NOTES.txt) и ежедневным отчётом.
Состав репозитория
| Файл | Назначение |
|---|---|
ssh-monitor |
Основной цикл: разбор журналов (journalctl: sshd, sudo, «тяжёлые» события, systemd-logind), цепочка уведомлений (Telegram, SMTP через python3, резервный webhook), при ENABLE_IP_BAN=1 — учёт неудачных попыток, бан в ipset и согласование с iptables/ip6tables, ежедневный отчёт, heartbeat, опционально Prometheus textfile и JSON health. Режимы: --check-config, --dry-run. |
ssh-monitor-watchdog |
Отдельный скрипт (обычно по systemd timer): проверяет, что сервис монитора активен и файл heartbeat не старше порога; при сбое выполняет systemctl restart и шлёт уведомление в Telegram или в BACKUP_WEBHOOK_URL. Читает те же TELEGRAM_* / webhook из /etc/ssh-monitor.conf. |
update_ssh_monitor.sh |
Опциональное автообновление: git pull / clone, сравнение SHA256 с /usr/local/bin/ssh-monitor, синхронизация sac-client.sh рядом с монитором, перезапуск ssh-monitor.service. См. docs/auto-update.ru.md. |
Системные пакеты и зависимости (что поставить на сервер до запуска): пошагово в docs/install-prerequisites.ru.md.
Конфигурация
git.kalinamall.ru: в этом закрытом репозитории допустимо хранить
ssh-monitor.confс боевыми токенами и паролями (файл не в.gitignore). В публичном GitHub такой файл игнорируется — используйте толькоssh-monitor.conf.example.
Скрипт читает параметры из /etc/ssh-monitor.conf в формате KEY="value".
- Скопируйте пример:
sudo cp ./ssh-monitor.conf.example /etc/ssh-monitor.conf
- Ограничьте доступ:
sudo chmod 600 /etc/ssh-monitor.conf
- Заполните минимум — хотя бы один канал доставки оповещений (иначе скрипт сразу завершится с ошибкой; см. абзац «Обязательное условие» ниже и раздел «Каналы оповещений и пустой NOTIFY_CHAIN»):
- для Telegram:
TELEGRAM_BOT_TOKENиTELEGRAM_CHAT_ID; - или настройте почту (SMTP) по переменным ниже при
NOTIFY_ORDER=""(автовыбор каналов).
Обязательное условие: после загрузки конфигурации в цепочке NOTIFY_CHAIN должен быть минимум один канал (telegram или email). Если каналов нет (пустой NOTIFY_ORDER и ни Telegram, ни SMTP не удовлетворяют критериям «настроен», либо в NOTIFY_ORDER остались только неизвестные имена), скрипт не входит в основной цикл и завершается с кодом 1, в stderr: Не настроен ни один канал отправки оповещений. Это же правило действует для --check-config и --dry-run. Резервный BACKUP_WEBHOOK_URL в эту проверку не входит — он используется только если ни один канал из NOTIFY_CHAIN не смог доставить сообщение.
Поддерживаемые параметры:
TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_IDNOTIFY_ORDER— список каналов (telegram,emailи сокращения tg, mail); при каждом оповещении скрипт пытается отправить во все перечисленные каналы по порядку (не «первый успешный — и стоп»). Пусто = в цепочку попадают только настроенные каналы (порядок по умолчанию см. вssh-monitor.conf.example). Если итоговая цепочка пуста — скрипт не стартует (см. раздел «Каналы оповещений и пустой NOTIFY_CHAIN»).MAIL_SMTP_HOST,MAIL_SMTP_PORT,MAIL_SMTP_USER,MAIL_SMTP_PASSWORD,MAIL_FROM,MAIL_TO,MAIL_SMTP_STARTTLS,MAIL_SMTP_SSL— отправка почты через python3 (см. таблицу каналов ниже)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_FILEENABLE_LOGIND_MONITOR,LOGIND_NOTIFY_NEW,LOGIND_NOTIFY_REMOVED,LOGIND_NOTIFY_FAILED,LOGIND_SKIP_REMOTE— см. раздел «Мониторинг systemd-logind» ниже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_IPSBRUTE_WINDOW_SEC,BRUTE_MIN_FAILS,BRUTE_NOTIFY_COOLDOWN_SECPROMETHEUS_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_INTERVALWHITELIST_IPS,WHITELIST_SUBNETS(CSV, напримерip1,ip2)WATCHDOG_MAX_HEARTBEAT_AGE,WATCHDOG_LOG_FILE,WATCHDOG_SERVICE_NAME,WATCHDOG_NOTIFY_ON_RECOVERY
Если файл /etc/ssh-monitor.conf отсутствует или часть значений пустая, используются значения по умолчанию из скрипта.
Описание переменных в /etc/ssh-monitor.conf
TELEGRAM_BOT_TOKEN— токен Telegram-бота для отправки уведомлений.TELEGRAM_CHAT_ID— ID чата/пользователя, куда отправляются уведомления.NOTIFY_ORDER— CSV имён каналов (telegram,emailилиtg,mail). Для каждого оповещения выполняется попытка доставки во все перечисленные каналы (при ошибке одного остальные всё равно пробуются; неудачи пишутся вLOG_FILE). Пустая строка: автоматически собирается цепочка только из реально настроенных каналов (по умолчанию порядок telegram → email; критерии «настроен» — таблица в разделе «Каналы оповещений и пустой NOTIFY_CHAIN»). Если после сборки цепочки нет ни одного канала, скрипт сразу завершается с ошибкой: «Не настроен ни один канал отправки оповещений» (в том числе режим--check-configи--dry-run).MAIL_SMTP_HOST,MAIL_SMTP_PORT,MAIL_SMTP_USER,MAIL_SMTP_PASSWORD,MAIL_FROM,MAIL_TO,MAIL_SMTP_STARTTLS,MAIL_SMTP_SSL— параметры SMTP для каналаemail(отправка через python3; см. таблицу «настроен»).BACKUP_WEBHOOK_URL— URL для резервной отправки (тело JSON{"text":"..."}); только fallback при сбое основных каналов, не засчитывается как «настроенный канал» (см. шаг 3).LOG_FILE— путь к основному лог-файлу скрипта.LAST_HEARTBEAT_FILE— файл с timestamp последнего heartbeat-сообщения.LAST_REPORT_FILE— дата последнего ежедневного отчёта в форматеYYYY-MM-DD(старый формат Unix-времени при первом запуске будет автоматически интерпретирован).LAST_SSH_CHECK_FILE— файл с меткой времени последней проверки SSH-событий.LAST_SUDO_CHECK_FILE— файл с меткой времени последней проверки sudo-событий.LAST_SECURITY_EVENTS_FILE— метка последней проверки «тяжёлых» событий безопасности в журнале.LAST_LOGIND_CHECK_FILE— метка последней обработки журналаsystemd-logind(unix-время); при первом запуске подтягивается около 30 минут истории.BAN_LIST_FILE— файл состояния банов (IP, время окончания бана и метаданные).ENABLE_LOGIND_MONITOR—1включает опросjournalctl -u systemd-logind,0полностью отключает этот блок.LOGIND_NOTIFY_NEW—1отправлять Telegram при появлении новой сессии в logind (строки вида New session … of user …).LOGIND_NOTIFY_REMOVED—1отправлять Telegram при завершении сессии (Removed session …); по умолчанию0(только запись вLOG_FILE, чтобы не заспамить канал).LOGIND_NOTIFY_FAILED—1отправлять Telegram по строкам logind, содержащим failed (широкий фильтр; при необходимости отключите).LOGIND_SKIP_REMOTE—1(по умолчанию): для новой сессии logind, если доступенloginctl, не отправляется второе Telegram, если сессия уже учтена вmonitor_ssh: приType=sshили еслиServiceуказывает на sshd (частоType=tty+Service=sshd); при пустом ответеloginctlделаются короткие повторы. Поставьте0, если нужны отдельные алерты logind и для SSH-сессий.DAILY_REPORT_HOUR— час (0..23), после наступления которого в текущих календарных сутках (в выбранной ниже зоне) отправляется не более одного ежедневного отчёта.DAILY_REPORT_ENABLED—1(по умолчанию) отправлятьreport.daily.sshс агента;0— не слать (суточный отчёт формирует только SAC, см.SAC_DAILY_REPORT_*).DAILY_REPORT_TZ— необязательная IANA-зона (Europe/Moscow,Asia/Yekaterinburg, …). Если пусто, для отчёта используется та же зона, что и у командыdateу процесса монитора (как правило, совпадает сtimedatectl//etc/localtimeна сервере). Если сервис запускается сTZ=UTCв unit-файле, безDAILY_REPORT_TZотчёт ориентируется на UTC — тогда задайте явную зону в конфиге.NOTIFY_TZ— IANA-зона для строк «🕐 Время» в Telegram/email (и аналогичных текстах). Пусто — используетсяDAILY_REPORT_TZ, если она задана, иначе зона процесса. Частая причина «время в теле сообщения UTC, а в заголовке чата локальное»: вssh-monitor.serviceзаданоEnvironment=TZ=UTC— тогда задайтеNOTIFY_TZ(илиDAILY_REPORT_TZ) на вашу локаль, напримерAsia/Vladivostok.SERVER_DISPLAY_NAME— подпись в строке «🖥️ Сервер» во всех уведомлениях. Пусто —hostnameи при возможности основной IPv4 (hostname (10.x.x.x)). Задайте явное имя, если несколько хостов с одинаковым hostname шлют в один чат. Подробнее: docs/notifications.ru.md.SERVER_IPV4— опциональный override для SAChost.ipv4. Если пусто, IPv4 определяется автоматически.SSH_ACCEPT_NOTIFY_DEDUP_SEC— не чаще одного Telegram по успешному SSH (Accepted) на одну пару пользователь + IP за указанное число секунд (по умолчанию 5). Снимает дубли, когда в journal две строки с разными портами клиента за один вход.0— отключить этот антидубль (останется только дедупликация внутри одного проходаmonitor_sshпоuser|ip|port).DAILY_REPORT_TOP_IPS— сколько IP показывать в топе неудачных попыток за 24 часа.BRUTE_WINDOW_SEC— окно (секунды) для оценки «массового» брутфорса поjournalctl.BRUTE_MIN_FAILS— минимум неудачных попыток за окно для тревоги.BRUTE_NOTIFY_COOLDOWN_SEC— пауза между повторными уведомлениями по одному и тому же IP.PROMETHEUS_TEXTFILE_DIR— если задан существующий каталог, на каждой итерации пишетсяssh_monitor.promс метрикойssh_monitor_last_loop_unixtime.HEALTHCHECK_STATUS_FILE— если задан, на каждой итерации обновляется JSON{ "ts", "hostname", "dry_run" }.ENABLE_IP_BAN— включить автобан IP (1) или работать только как монитор с уведомлениями (0; ipset не требуется).BAN_TIME— длительность бана IP в секундах (используется приENABLE_IP_BAN=1).MAX_ATTEMPTS— число неудачных SSH-попыток до автоматического бана.BAN_CHECK_INTERVAL— интервал проверки просроченных банов и аудита согласованности бан-листа с firewall (секунды).MONITOR_INTERVAL— пауза между итерациями основного цикла мониторинга (секунды).WHITELIST_IPS— белый список IP через запятую (IPv4 и IPv6 — точное совпадение).WHITELIST_SUBNETS— белый список подсетей только IPv4 CIDR через запятую (например10.0.0.0/24).WATCHDOG_MAX_HEARTBEAT_AGE— через сколько секунд heartbeat считается устаревшим.WATCHDOG_LOG_FILE— лог-файл watchdog-скрипта.WATCHDOG_SERVICE_NAME— имя systemd-сервиса, который контролирует watchdog (по умолчаниюssh-monitor.service).WATCHDOG_NOTIFY_ON_RECOVERY—1включает служебные сообщения watchdog при штатном состоянии,0отключает.
Каналы оповещений и пустой NOTIFY_CHAIN
Кратко, что считается настроенным каналом (только такие попадают в автоматическую цепочку при NOTIFY_ORDER=""):
| Канал | Условие «настроен» |
|---|---|
telegram |
заданы TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID |
email |
непустые MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO, в PATH есть python3 |
Если итоговый NOTIFY_CHAIN пуст, скрипт завершается с сообщением Не настроен ни один канал отправки оповещений (см. шаг 3 в разделе «Конфигурация» выше).
Мониторинг systemd-logind
- Собираются сообщения юнита
systemd-logindчерезjournalctl(-o cat): новые и завершённые сессии, а также строки с подстрокой failed (если включено). - Без
journalctlна хосте этот блок не работает (как и часть других функций, завязанных на journal). - Для разбора «новой сессии» используются типичные англоязычные форматы (
New session … of user …). При несовпадении формата строка всё равно попадёт в лог с пометкой о неудачном разборе. loginctlнужен дляLOGIND_SKIP_REMOTE: поTypeиServiceрешается, не дублировать ли Telegram сmonitor_ssh(см. описаниеLOGIND_SKIP_REMOTEвыше).monitor_ssh: события sshd читаются из journal преимущественно какjournalctl _COMM=sshd(запасные варианты — юнитыsshd/ssh), строкиsort -u, дляAcceptedв одном проходе — дедуп по пользователь|IP|порт; между проходами цикла — паузаSSH_ACCEPT_NOTIFY_DEDUP_SECдля той же пары пользователь|IP (см. переменную в конфиге).
Команда ssh-monitor --check-config выводит актуальные значения, в том числе параметры logind.
Ограничения на хостах без journald и нюансы /var/log/auth.log: см. раздел 4 в docs/install-prerequisites.ru.md.
Идентификация сервера в уведомлениях
Во всех оповещениях (SSH, sudo, бан IP, logind, heartbeat, ежедневный отчёт и т.д.) после заголовка автоматически добавляется строка «🖥️ Сервер: …» — формат hostname (IPv4) или только hostname, если IPv4 определить не удалось. Логика в notify_send(); дублирование в тексте сообщения не требуется.
- Утилиты
hostnameиip(см. docs/install-prerequisites.ru.md). - Опционально
SERVER_DISPLAY_NAMEв конфиге — фиксированная подпись вместо hostname (удобно при нескольких серверах в одном Telegram-чате). - Типы сообщений и примеры: docs/notifications.ru.md.
Автообновление скрипта
Скрипт update_ssh_monitor.sh по расписанию (cron или systemd timer) выполняет git pull / clone, сравнивает SHA256 с /usr/local/bin/ssh-monitor, всегда подтягивает /usr/local/bin/sac-client.sh и при изменении любого из файлов перезапускает ssh-monitor.service. Конфиг /etc/ssh-monitor.conf не трогается.
Пошаговая настройка: docs/auto-update.ru.md.
Режимы запуска
Обычный запуск (root):
sudo bash ./ssh-monitor
Проверка конфигурации и синтаксиса без запуска цикла, без изменений iptables и без обязательного Telegram:
sudo bash ./ssh-monitor --check-config
Режим dry-run (правила firewall не меняются; уведомления выводятся в stderr вместо отправки):
sudo bash ./ssh-monitor --dry-run
Проверка синтаксиса
bash -n ./ssh-monitor
Релизный архив
Версия задаётся в SSH_MONITOR_VERSION в файле ssh-monitor и дублируется в version.txt (сейчас 1.2.9-SAC). Модуль sac-client.sh использует ту же версию для SAC product_version. Сборка tarball из текущего git-дерева:
make dist
Появится файл ssh-monitor-<версия>.tar.gz (через git archive). Готовые архивы для установки без клона репозитория прикладываются к релизам на GitHub.
Установка скрипта, конфигурации и systemd
Автоматическая установка (рекомендуется)
sudo ./first_deploy.sh
# или: sudo ./update_ssh_monitor.sh --deploy
См. docs/auto-update.ru.md — клон из git, копирование в /usr/local/bin/, sac-client.sh, пример /etc/ssh-monitor.conf, unit systemd.
Ручная установка
- Скопируйте скрипт в постоянное место:
sudo install -m 750 ./ssh-monitor /usr/local/bin/ssh-monitorsudo install -m 644 ./sac-client.sh /usr/local/bin/sac-client.sh
- Убедитесь, что конфиг создан (см. раздел «Конфигурация» выше):
sudo cp ./ssh-monitor.conf.example /etc/ssh-monitor.confsudo chmod 600 /etc/ssh-monitor.conf
- Создайте unit-файл
/etc/systemd/system/ssh-monitor.service(илиsudo cp ssh-monitor.service.example ...):
[Unit]
Description=SSH Monitor with Telegram alerts
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/ssh-monitor
Restart=always
RestartSec=5
User=root
[Install]
WantedBy=multi-user.target
- Включите и запустите сервис:
sudo systemctl daemon-reload
sudo systemctl enable ssh-monitor
sudo systemctl start ssh-monitor
- Проверка состояния и логов:
sudo systemctl status ssh-monitor
sudo journalctl -u ssh-monitor -f
Ротация логов
Пример для logrotate лежит в репозитории: contrib/logrotate.d/ssh-monitor. Скопируйте файл в /etc/logrotate.d/ и при необходимости поправьте пути под ваши LOG_FILE / WATCHDOG_LOG_FILE и другие файлы состояния в /var/log/ (в т.ч. LAST_*, если решите их ротировать отдельно).
Watchdog (автоматическое восстановление)
Watchdog проверяет активность ssh-monitor.service и актуальность heartbeat (LAST_HEARTBEAT_FILE). При сбое выполняет systemctl restart и отправляет уведомление (Telegram и при сбое — BACKUP_WEBHOOK_URL, если задан).
Пошаговая установка скрипта ssh-monitor-watchdog и timer в systemd: раздел 6 в docs/install-prerequisites.ru.md.
Ключевые темы (для поиска): мониторинг SSH и sshd, bash-скрипт для Linux-сервера, уведомления в Telegram и по SMTP / электронной почте, systemd-logind, sudo, journalctl, iptables / ip6tables, автоматический бан IP и whitelist, systemd unit, ежедневный отчёт, heartbeat, OpenSSH, безопасность сервера, Prometheus textfile, опционально watchdog для сервиса.
Лицензия
Проект распространяется по лицензии MIT — см. файл LICENSE.