fix: dedup 4624 login alerts and log Skip/dedup reasons (1.2.18-SAC)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+64
-10
@@ -80,7 +80,7 @@ $script:MonitorLoopInitialized = $false
|
|||||||
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
||||||
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
||||||
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
||||||
$ScriptVersion = "1.2.17-SAC"
|
$ScriptVersion = "1.2.18-SAC"
|
||||||
|
|
||||||
# Логи (все под InstallRoot)
|
# Логи (все под InstallRoot)
|
||||||
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
||||||
@@ -94,6 +94,8 @@ $LogRotationMinute = 0
|
|||||||
# Heartbeat (файл; при отсутствии обновления > HeartbeatStaleAlertMultiplier × интервал — оповещение)
|
# Heartbeat (файл; при отсутствии обновления > HeartbeatStaleAlertMultiplier × интервал — оповещение)
|
||||||
$HeartbeatInterval = 3600
|
$HeartbeatInterval = 3600
|
||||||
$HeartbeatStaleAlertMultiplier = 2
|
$HeartbeatStaleAlertMultiplier = 2
|
||||||
|
# Один RDP-вход часто даёт 2+ события 4624 с одним временем — не слать дубли в Telegram/SAC.
|
||||||
|
$LoginSuccessNotifyDedupSeconds = 90
|
||||||
$HeartbeatFile = Join-Path $script:InstallRoot "Logs\last_heartbeat.txt"
|
$HeartbeatFile = Join-Path $script:InstallRoot "Logs\last_heartbeat.txt"
|
||||||
$DeployUpdateMarkerFile = Join-Path $script:InstallRoot "deploy_last_update.txt"
|
$DeployUpdateMarkerFile = Join-Path $script:InstallRoot "deploy_last_update.txt"
|
||||||
# Построчные правила подавления уведомлений Security 4624/4625 (см. ignore.lst.example в репозитории).
|
# Построчные правила подавления уведомлений Security 4624/4625 (см. ignore.lst.example в репозитории).
|
||||||
@@ -1900,6 +1902,37 @@ function Format-LoginEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$script:FailedLogonBuckets = @{}
|
$script:FailedLogonBuckets = @{}
|
||||||
|
$script:LoginSuccessNotifyDedup = @{}
|
||||||
|
|
||||||
|
function Get-RdpLoginSuccessNotifyDedupKey {
|
||||||
|
param(
|
||||||
|
[string]$SecurityLogComputerName,
|
||||||
|
[string]$Username,
|
||||||
|
[string]$SourceIP,
|
||||||
|
[int]$LogonType
|
||||||
|
)
|
||||||
|
$hostPart = if ([string]::IsNullOrWhiteSpace($SecurityLogComputerName)) { $env:COMPUTERNAME } else { $SecurityLogComputerName.Trim() }
|
||||||
|
$userPart = if ($null -ne $Username) { $Username.Trim() } else { '-' }
|
||||||
|
$ipPart = if ($null -ne $SourceIP) { $SourceIP.Trim() } else { '-' }
|
||||||
|
return "$hostPart|4624|$userPart|$ipPart|$LogonType"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-RdpLoginSuccessNotifyDedupAllow {
|
||||||
|
param(
|
||||||
|
[string]$DedupKey,
|
||||||
|
[datetime]$TimeCreated
|
||||||
|
)
|
||||||
|
if ($LoginSuccessNotifyDedupSeconds -le 0) { return $true }
|
||||||
|
if ($script:LoginSuccessNotifyDedup.ContainsKey($DedupKey)) {
|
||||||
|
$lastUtc = $script:LoginSuccessNotifyDedup[$DedupKey]
|
||||||
|
$delta = ($TimeCreated.ToUniversalTime() - $lastUtc).TotalSeconds
|
||||||
|
if ($delta -ge 0 -and $delta -lt $LoginSuccessNotifyDedupSeconds) {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$script:LoginSuccessNotifyDedup[$DedupKey] = $TimeCreated.ToUniversalTime()
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
function Get-FailedLogonSourceKeyPart {
|
function Get-FailedLogonSourceKeyPart {
|
||||||
param(
|
param(
|
||||||
@@ -2634,35 +2667,57 @@ function Start-LoginMonitor {
|
|||||||
$eventInfo = Get-LoginEventInfo -Event $event
|
$eventInfo = Get-LoginEventInfo -Event $event
|
||||||
$logonTypeName = Get-LogonTypeName -LogonType $eventInfo.LogonType
|
$logonTypeName = Get-LogonTypeName -LogonType $eventInfo.LogonType
|
||||||
$shouldIgnore = $false
|
$shouldIgnore = $false
|
||||||
|
$ignoreReason = ''
|
||||||
|
|
||||||
if ($MonitorInteractiveOnly -and -not $MonitorAllEvents) {
|
if ($MonitorInteractiveOnly -and -not $MonitorAllEvents) {
|
||||||
if ($event.Id -eq 4648) {
|
if ($event.Id -eq 4648) {
|
||||||
$shouldIgnore = $true
|
$shouldIgnore = $true
|
||||||
|
$ignoreReason = 'EventID 4648 excluded (MonitorInteractiveOnly)'
|
||||||
} elseif ($event.Id -in 4624, 4625) {
|
} elseif ($event.Id -in 4624, 4625) {
|
||||||
if ($osKind.IsWorkstation) {
|
if ($osKind.IsWorkstation) {
|
||||||
$interactiveTypes = @(10)
|
$interactiveTypes = @(10)
|
||||||
|
$modeLabel = 'workstation LT10'
|
||||||
} else {
|
} else {
|
||||||
$interactiveTypes = @(2, 3, 10)
|
$interactiveTypes = @(2, 3, 10)
|
||||||
|
$modeLabel = 'server LT2/3/10'
|
||||||
}
|
}
|
||||||
if ($interactiveTypes -notcontains $eventInfo.LogonType) {
|
if ($interactiveTypes -notcontains $eventInfo.LogonType) {
|
||||||
$shouldIgnore = $true
|
$shouldIgnore = $true
|
||||||
|
$ignoreReason = "LogonType $($eventInfo.LogonType) not in $modeLabel"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$shouldIgnore = $true
|
$shouldIgnore = $true
|
||||||
|
$ignoreReason = "EventID $($event.Id) not monitored"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $shouldIgnore -and -not $MonitorAllEvents) {
|
if (-not $shouldIgnore -and -not $MonitorAllEvents) {
|
||||||
$shouldIgnore = Should-IgnoreEvent -Username $eventInfo.Username `
|
if (Should-IgnoreEvent -Username $eventInfo.Username `
|
||||||
-ProcessName $eventInfo.ProcessName `
|
-ProcessName $eventInfo.ProcessName `
|
||||||
-ComputerName $eventInfo.ComputerName `
|
-ComputerName $eventInfo.ComputerName `
|
||||||
-EventID $event.Id `
|
-EventID $event.Id `
|
||||||
-LogonType $eventInfo.LogonType `
|
-LogonType $eventInfo.LogonType `
|
||||||
-SourceIP $eventInfo.SourceIP
|
-SourceIP $eventInfo.SourceIP) {
|
||||||
|
$shouldIgnore = $true
|
||||||
|
$ignoreReason = 'ignore.lst or built-in exclusion (user/process/IP/workstation)'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $shouldIgnore) {
|
if ($shouldIgnore) {
|
||||||
if ($event.Id -eq 4625 -and $FailedLogonRateLimitEnabled) {
|
Write-Log "Skip $($event.Id): User=$($eventInfo.Username) LT=$($eventInfo.LogonType) IP=$($eventInfo.SourceIP) Wks=$($eventInfo.ComputerName) — $ignoreReason"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event.Id -eq 4624) {
|
||||||
|
$dedupKey = Get-RdpLoginSuccessNotifyDedupKey -SecurityLogComputerName $event.MachineName `
|
||||||
|
-Username $eventInfo.Username -SourceIP $eventInfo.SourceIP -LogonType $eventInfo.LogonType
|
||||||
|
if (-not (Test-RdpLoginSuccessNotifyDedupAllow -DedupKey $dedupKey -TimeCreated $eventInfo.TimeCreated)) {
|
||||||
|
Write-Log "Notify dedup 4624: User=$($eventInfo.Username) LT=$($eventInfo.LogonType) IP=$($eventInfo.SourceIP) (window ${LoginSuccessNotifyDedupSeconds}s)"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event.Id -eq 4625 -and $FailedLogonRateLimitEnabled) {
|
||||||
$rl = Get-FailedLogonRateLimitDecision4625 -SourceIP $eventInfo.SourceIP `
|
$rl = Get-FailedLogonRateLimitDecision4625 -SourceIP $eventInfo.SourceIP `
|
||||||
-ComputerName $eventInfo.ComputerName -Username $eventInfo.Username `
|
-ComputerName $eventInfo.ComputerName -Username $eventInfo.Username `
|
||||||
-LogonType $eventInfo.LogonType -TimeCreated $eventInfo.TimeCreated `
|
-LogonType $eventInfo.LogonType -TimeCreated $eventInfo.TimeCreated `
|
||||||
@@ -2731,7 +2786,6 @@ function Start-LoginMonitor {
|
|||||||
event_id_windows = [int]$event.Id
|
event_id_windows = [int]$event.Id
|
||||||
workstation_name = $eventInfo.ComputerName
|
workstation_name = $eventInfo.ComputerName
|
||||||
} | Out-Null
|
} | Out-Null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$lastCheckTime = ($events | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
$lastCheckTime = ($events | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\RDP-logi
|
|||||||
- Stale heartbeat: если **`last_heartbeat.txt`** не обновлялся дольше **`$HeartbeatStaleAlertMultiplier` × `$HeartbeatInterval`** — оповещение в Telegram/Email (см. п. 8 подготовки).
|
- Stale heartbeat: если **`last_heartbeat.txt`** не обновлялся дольше **`$HeartbeatStaleAlertMultiplier` × `$HeartbeatInterval`** — оповещение в Telegram/Email (см. п. 8 подготовки).
|
||||||
- При старте в Telegram/Email: строка **«Каналы уведомлений»** (фактический порядок доставки), плюс режим RDS/4740 по конфигурации.
|
- При старте в Telegram/Email: строка **«Каналы уведомлений»** (фактический порядок доставки), плюс режим RDS/4740 по конфигурации.
|
||||||
- Telegram при старте: при установленном **RD Session Host** (или аналогичных компонентах RDS, не только шлюз) — строка про входы по RDP/RDS на этом сервере; при доступном журнале **RD Gateway** — отдельная строка про подключения к **внутренним целевым ПК** через шлюз (302/303). Узел только с ролью RD Gateway не дублирует формулировку «хост сессий».
|
- Telegram при старте: при установленном **RD Session Host** (или аналогичных компонентах RDS, не только шлюз) — строка про входы по RDP/RDS на этом сервере; при доступном журнале **RD Gateway** — отдельная строка про подключения к **внутренним целевым ПК** через шлюз (302/303). Узел только с ролью RD Gateway не дублирует формулировку «хост сессий».
|
||||||
|
- **Дубли Telegram на один RDP-вход:** Windows часто пишет **несколько 4624** с одним временем; с версии **1.2.18-SAC** второе уведомление за **`$LoginSuccessNotifyDedupSeconds`** (90 с) подавляется (`Notify dedup 4624` в логе).
|
||||||
|
- **В логе нет `Notify`, но 4624 в Security есть:** монитор обрабатывает только события **после** своего `StartTime` (окно опроса ~10 с при старте). Ищите строки **`Skip 4624:`** (фильтр LogonType / ignore.lst). Диагностика: **`tools\Show-Rdp4624Recent.ps1`**.
|
||||||
|
|
||||||
## 5) Автоматический перезапуск при падении
|
## 5) Автоматический перезапуск при падении
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Просмотр недавних 4624 с полями для диагностики RDP-login-monitor.
|
||||||
|
.EXAMPLE
|
||||||
|
.\Show-Rdp4624Recent.ps1
|
||||||
|
.\Show-Rdp4624Recent.ps1 -Minutes 30 -User papatramp
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[int]$Minutes = 15,
|
||||||
|
[string]$User = '',
|
||||||
|
[int]$MaxEvents = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
$start = (Get-Date).AddMinutes(-$Minutes)
|
||||||
|
Write-Host "Security 4624 since $($start.ToString('yyyy-MM-dd HH:mm:ss')) (local time)" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$events = @(Get-WinEvent -FilterHashtable @{
|
||||||
|
LogName = 'Security'
|
||||||
|
Id = 4624
|
||||||
|
StartTime = $start
|
||||||
|
} -MaxEvents $MaxEvents -ErrorAction SilentlyContinue)
|
||||||
|
|
||||||
|
if ($events.Count -eq 0) {
|
||||||
|
Write-Host 'No 4624 events in window.'
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-EvProp($Event, [string]$Name) {
|
||||||
|
$xml = [xml]$Event.ToXml()
|
||||||
|
$n = $xml.Event.EventData.Data | Where-Object { $_.Name -eq $Name } | Select-Object -First 1
|
||||||
|
if ($null -eq $n) { return '-' }
|
||||||
|
return [string]$n.'#text'
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = foreach ($ev in $events) {
|
||||||
|
$u = Get-EvProp $ev 'TargetUserName'
|
||||||
|
if ($User -and $u -notlike "*$User*") { continue }
|
||||||
|
[pscustomobject]@{
|
||||||
|
TimeCreated = $ev.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
||||||
|
RecordId = $ev.RecordId
|
||||||
|
User = $u
|
||||||
|
LogonType = Get-EvProp $ev 'LogonType'
|
||||||
|
IpAddress = Get-EvProp $ev 'IpAddress'
|
||||||
|
Workstation = Get-EvProp $ev 'WorkstationName'
|
||||||
|
Process = Get-EvProp $ev 'LogonProcessName'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows | Format-Table -AutoSize
|
||||||
|
Write-Host "`nTip: monitor log — Select-String -Path 'C:\ProgramData\RDP-login-monitor\Logs\*.log' -Pattern 'Notify:|Skip 4624|Notify dedup'"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
param([string]$Path = (Join-Path $PSScriptRoot '..\Login_Monitor.ps1'))
|
||||||
|
$errs = $null
|
||||||
|
[void][System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $Path), [ref]$null, [ref]$errs)
|
||||||
|
if ($errs) {
|
||||||
|
$errs | ForEach-Object { $_.ToString() }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Output 'OK'
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.2.17-SAC
|
1.2.18-SAC
|
||||||
|
|||||||
Reference in New Issue
Block a user