← Все заметки
Мониторинг и безопасность

Оповещения о входе по SSH через XMPP с sendxmpp и проверкой TLS

Практическая схема отправки уведомлений о входе по SSH на собственный XMPP-сервер без Telegram, встроенных в скрипт паролей и отключения проверки TLS.

Статус: актуальноОпубликовано: 2026-09-16Обновлено: 2026-09-16

Проверено на: sendxmpp · OpenSSH · Linux

Контекст

Оповещение о входе по SSH полезно как дополнительный сигнал для администратора, но сам механизм уведомления не должен создавать новую проблему с хранением секретов или задерживать вход, если внешний сервис недоступен.

Практичный вариант — выделенная XMPP-учётная запись для уведомлений и отправка через sendxmpp по TLS. Учётные данные хранятся в отдельном root-only файле, а JID получателя задаётся отдельно.

Такой подход позволяет отказаться от Telegram Bot API и оставить канал оповещения внутри собственного XMPP-сервиса.

Схема

событие входа по SSH
  -> скрипт уведомления
  -> sendxmpp
  -> XMPP-сервер
  -> JID администратора

Разделяйте:

Учётная запись sendxmpp

Создайте отдельный конфигурационный файл, например:

/root/.sendxmpprc-ssh-alert

Пример содержимого:

username: ssh-alert
jserver: jabber.example.net
port: 5222
password: replace-with-real-password

Ограничьте доступ:

chown root:root /root/.sendxmpprc-ssh-alert
chmod 600 /root/.sendxmpprc-ssh-alert

Реальный пароль не должен находиться в самом скрипте, репозитории, shell history или документации.

Сначала проверьте отправку вручную

printf 'test ssh alert\n' | \
sendxmpp \
  -f /root/.sendxmpprc-ssh-alert \
  -r ssh-alert \
  --tls \
  --tls-ca-path /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem \
  admin@example.net \
  -v

Путь к CA bundle зависит от дистрибутива. Не отключайте проверку сертификата только ради того, чтобы команда начала работать.

Скрипт уведомления

Несекретные параметры удобно вынести в начало файла:

#!/usr/bin/env bash
set -u

XMPP_CONFIG="/root/.sendxmpprc-ssh-alert"
XMPP_TO="admin@example.net"
XMPP_FROM_RESOURCE="ssh-alert"
XMPP_TLS_CA_PATH="/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"

HOSTNAME="$(hostname -f 2>/dev/null || hostname)"
IP_ADDRESS_SERVER="$(hostname -I 2>/dev/null | awk '{print $1}')"
CURRENT_USER="${PAM_USER:-${USER:-unknown}}"
IP_ADDRESS_CLIENT="${PAM_RHOST:-}"

if [[ -z "$IP_ADDRESS_CLIENT" && -n "${SSH_CLIENT:-}" ]]; then
  IP_ADDRESS_CLIENT="${SSH_CLIENT%% *}"
fi

[[ -n "$IP_ADDRESS_CLIENT" ]] || IP_ADDRESS_CLIENT="unknown"
[[ -n "$IP_ADDRESS_SERVER" ]] || IP_ADDRESS_SERVER="unknown"

MESSAGE="SSH login detected on server: ${HOSTNAME} (IP: ${IP_ADDRESS_SERVER}). User: ${CURRENT_USER} from IP: ${IP_ADDRESS_CLIENT}"

if command -v sendxmpp >/dev/null 2>&1; then
  printf '%s\n' "$MESSAGE" | \
    sendxmpp \
      -f "$XMPP_CONFIG" \
      -r "$XMPP_FROM_RESOURCE" \
      --tls \
      --tls-ca-path "$XMPP_TLS_CA_PATH" \
      "$XMPP_TO"
fi

Получатель задаётся отдельно:

XMPP_TO="admin@example.net"

Для смены JID не требуется менять учётные данные или остальную логику.

Для PAM используйте данные самой SSH-сессии

Если скрипт запускается из PAM, PAM_USER и PAM_RHOST точнее описывают реальную сессию, чем whoami и переменные shell profile.

Оповещать стоит только об открытии SSH-сессии:

[[ ${PAM_TYPE:-} == "open_session" ]] || exit 0
[[ ${PAM_SERVICE:-} == "sshd" ]] || exit 0

Это также снижает риск дублирования сообщений.

Не связывайте доступность XMPP с доступностью SSH

Синхронный сетевой запрос внутри authentication path нежелателен. Проблемы DNS, TLS или самого XMPP-сервера не должны задерживать администратора во время аварии.

На systemd-хосте доставку можно вынести в отдельный transient service:

systemd-run --quiet --collect --no-block -- \
  /usr/local/sbin/ssh-login-alert --worker \
  --user "$PAM_USER" \
  --remote "${PAM_RHOST:-unknown}"

PAM hook после постановки задачи должен завершаться успешно, а ошибки доставки логироваться отдельно.

Интеграция с PAM

Перед изменением PAM держите уже открытую привилегированную SSH-сессию и сделайте резервную копию:

cp -a /etc/pam.d/sshd /etc/pam.d/sshd.before-ssh-login-alert

Типичная optional session rule:

session optional pam_exec.so quiet /usr/local/sbin/ssh-login-alert

optional здесь принципиален: уведомление — это наблюдаемость, а не условие успешной аутентификации.

Установка

install -o root -g root -m 0755 \
  notify_jabber.sh \
  /usr/local/sbin/ssh-login-alert

Файл с учётными данными устанавливайте отдельно с mode 0600.

Старое имя вроде notify_telegram.sh можно временно оставить как compatibility wrapper, но в постоянной эксплуатации название лучше привести к реальному транспорту.

Не скрывайте ошибки полностью

Постоянный >/dev/null 2>&1 усложняет диагностику.

Лучше держать успешную отправку тихой, а ошибки писать в journal:

if ! printf '%s\n' "$MESSAGE" | sendxmpp ...; then
  logger -t ssh-login-alert -- \
    "XMPP delivery failed for user=${CURRENT_USER} remote=${IP_ADDRESS_CLIENT}"
fi

Пароль XMPP в лог попадать не должен.

Что это не заменяет

Такое уведомление не заменяет:

XMPP-сообщение — быстрый операторский сигнал, а не основной журнал аудита.

Проверка

После внедрения подтвердите:

sendxmpp установлен
конфигурационный файл принадлежит root и имеет mode 0600
TLS verification проходит успешно
ручное тестовое сообщение приходит на нужный JID
при недоступном XMPP вход по SSH всё равно проходит
один open_session создаёт одно уведомление
имя пользователя и remote IP корректны
ошибка доставки видна в journal

Когда остановить внедрение

Не продолжайте, если пароль оказался в скрипте или репозитории, для работы приходится отключать TLS verification, уведомление способно задержать/заблокировать SSH-вход, один login создаёт несколько сообщений или PAM тестируется из единственной привилегированной сессии.

Ссылки