Уведомления о SSH-входах
Незамеченный вход по SSH под root — худшее, что может случиться с сервером. Сделаем так, чтобы каждый успешный вход (и опционально — попытки) приходил вам в Notifly с указанием пользователя, IP и времени.
Самый простой способ — через PAM
Заголовок раздела «Самый простой способ — через PAM»PAM умеет вызывать произвольный скрипт после успешной аутентификации.
Скрипт-нотификатор
Заголовок раздела «Скрипт-нотификатор»Сохраните как /usr/local/bin/notifly-ssh-login:
#!/usr/bin/env bashset -euset -a; source /etc/notifly.env; set +a
# PAM передаёт переменные окружения PAM_*USER="${PAM_USER:-unknown}"RHOST="${PAM_RHOST:-unknown}"SERVICE="${PAM_SERVICE:-unknown}"TYPE="${PAM_TYPE:-unknown}"
# Реагируем только на открытие сессии sshd[ "$SERVICE" = "sshd" ] || exit 0[ "$TYPE" = "open_session" ] || exit 0
HOST=$(hostname -s)PRIO=5[ "$USER" = "root" ] && PRIO=9
/usr/local/bin/notifly-send \ "🔐 SSH-вход: $USER@$HOST" \ "Пользователь: $USERИсточник: $RHOSTСервер: $HOSTВремя: $(date '+%Y-%m-%d %H:%M:%S %Z')" \ "$PRIO" || trueСделайте исполняемым:
sudo chmod +x /usr/local/bin/notifly-ssh-loginПодключение к sshd
Заголовок раздела «Подключение к sshd»Откройте /etc/pam.d/sshd и добавьте в самом конце:
session optional pam_exec.so /usr/local/bin/notifly-ssh-loginГотово. Перезапускать sshd не нужно — PAM-конфиг перечитывается при каждом подключении.
С другой машины:
ssh user@your-serverВ Notifly прилетит сообщение с приоритетом 5 (или 9 для root).
Уведомления о неудачных попытках
Заголовок раздела «Уведомления о неудачных попытках»pam_exec срабатывает только при успехе. Для отслеживания неудачных попыток
проще читать journalctl через systemd. Создайте сервис-watcher:
/usr/local/bin/notifly-ssh-failed:
#!/usr/bin/env bashset -euset -a; source /etc/notifly.env; set +aHOST=$(hostname -s)
journalctl -u ssh -u sshd -f -o cat --since now | \while read -r line; do if echo "$line" | grep -q "Failed password"; then USER=$(echo "$line" | grep -oP 'for \K\S+' | head -1) IP=$(echo "$line" | grep -oP 'from \K\S+' | head -1) /usr/local/bin/notifly-send \ "⚠️ Неудачный SSH: $USER@$HOST" \ "IP: $IPПользователь: $USER" 4 || true fidone/etc/systemd/system/notifly-ssh-failed.service:
[Unit]Description=Notify about failed SSH attemptsAfter=ssh.service network-online.target
[Service]ExecStart=/usr/local/bin/notifly-ssh-failedRestart=alwaysRestartSec=10s
[Install]WantedBy=multi-user.targetsudo chmod +x /usr/local/bin/notifly-ssh-failedsudo systemctl daemon-reloadsudo systemctl enable --now notifly-ssh-failedWindows: RDP / WinRM / SSH через Security Event Log
Заголовок раздела «Windows: RDP / WinRM / SSH через Security Event Log»Аналог для Windows-серверов. Все входы — интерактивные, RDP, WinRM, OpenSSH — пишутся в Security Event Log. Нужные коды:
- 4624 — успешный вход (
LogonType=10— RDP,5— сервис,3— сеть,2— локальный). - 4625 — неудачный вход.
Скрипт-нотификатор
Заголовок раздела «Скрипт-нотификатор»param([int]$EventId = 4624). C:\scripts\Notifly.ps1
# Берём последнее событие из Security Log$ev = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=$EventId} -MaxEvents 1if (-not $ev) { return }
# Разбираем EventData$xml = [xml]$ev.ToXml()$data = @{}$xml.Event.EventData.Data | ForEach-Object { $data[$_.Name] = $_.'#text' }
$user = $data.TargetUserName$logonType = $data.LogonType$ip = $data.IpAddress$workstation = $data.WorkstationName
# Игнорируем сервисные входы и анонимныхif ($user -in @("SYSTEM", "ANONYMOUS LOGON", "$env:COMPUTERNAME$")) { return }if ($user -like "*$") { return } # компьютерные учётки
$logonName = switch ($logonType) { "2" { "Локальный" } "3" { "Сеть" } "7" { "Разблокировка" } "10" { "RDP" } "11" { "Кэшированный" } default { "Type=$logonType" }}
$prio = if ($EventId -eq 4625) { 5 } else { 5 }if ($user -in @("Administrator", "Администратор")) { $prio = 9 }
$icon = if ($EventId -eq 4625) { "⚠️ Неудачный" } else { "🔐 Вход" }
Send-Notifly ` -Title "$icon $($logonName): $user@$env:COMPUTERNAME" ` -Message "Пользователь: $user`nИсточник: $ip $workstation`nСервер: $env:COMPUTERNAME`nВремя: $($ev.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'))" ` -Priority $prioПодписка на события
Заголовок раздела «Подписка на события»# Событие 4624 — все успешные входы$xml = @"<QueryList> <Query Id="0" Path="Security"> <Select Path="Security"> *[System[EventID=4624 and (EventData/Data[@Name='LogonType']='10' or EventData/Data[@Name='LogonType']='2' or EventData/Data[@Name='LogonType']='7')]] </Select> </Query></QueryList>"@$Trigger = New-ScheduledTaskTrigger -AtStartup$Trigger.Subscription = $xml$Action = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Logon.ps1 -EventId 4624"Register-ScheduledTask -TaskName "Notifly Logon Watch" -Trigger $Trigger -Action $Action ` -Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest)
# Неудачные попытки (Event ID 4625)$xml2 = $xml -replace '4624', '4625' -replace "\(EventData.*\)", "true"$T2 = New-ScheduledTaskTrigger -AtStartup$T2.Subscription = $xml2$A2 = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Logon.ps1 -EventId 4625"Register-ScheduledTask -TaskName "Notifly Failed Logon Watch" -Trigger $T2 -Action $A2 ` -Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest)- Каждый успешный вход подтверждает: это были вы. Если пришло уведомление про чей-то вход в 3 ночи из чужой страны — это сигнал.
- Root всегда с приоритетом 9 — отдельный громкий сигнал на телефоне, даже если вы спите.
- Неудачные попытки превращаются в живой индикатор bruteforce: если поток
идёт нон-стоп — пора ставить Fail2ban или
ужесточать
MaxAuthTries.
Что улучшить дальше
Заголовок раздела «Что улучшить дальше»- Добавить геолокацию IP через
geoiplookup— заголовок «🌍 Вход из RU/Москва». - Не присылать уведомление для разрешённого списка IP (свой офис/VPN).
- Соединить с Fail2ban — получать ещё и события банов.