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:
+59
-5
@@ -80,7 +80,7 @@ $script:MonitorLoopInitialized = $false
|
||||
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
||||
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
||||
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
||||
$ScriptVersion = "1.2.17-SAC"
|
||||
$ScriptVersion = "1.2.18-SAC"
|
||||
|
||||
# Логи (все под InstallRoot)
|
||||
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
||||
@@ -94,6 +94,8 @@ $LogRotationMinute = 0
|
||||
# Heartbeat (файл; при отсутствии обновления > HeartbeatStaleAlertMultiplier × интервал — оповещение)
|
||||
$HeartbeatInterval = 3600
|
||||
$HeartbeatStaleAlertMultiplier = 2
|
||||
# Один RDP-вход часто даёт 2+ события 4624 с одним временем — не слать дубли в Telegram/SAC.
|
||||
$LoginSuccessNotifyDedupSeconds = 90
|
||||
$HeartbeatFile = Join-Path $script:InstallRoot "Logs\last_heartbeat.txt"
|
||||
$DeployUpdateMarkerFile = Join-Path $script:InstallRoot "deploy_last_update.txt"
|
||||
# Построчные правила подавления уведомлений Security 4624/4625 (см. ignore.lst.example в репозитории).
|
||||
@@ -1900,6 +1902,37 @@ function Format-LoginEvent {
|
||||
}
|
||||
|
||||
$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 {
|
||||
param(
|
||||
@@ -2634,34 +2667,56 @@ function Start-LoginMonitor {
|
||||
$eventInfo = Get-LoginEventInfo -Event $event
|
||||
$logonTypeName = Get-LogonTypeName -LogonType $eventInfo.LogonType
|
||||
$shouldIgnore = $false
|
||||
$ignoreReason = ''
|
||||
|
||||
if ($MonitorInteractiveOnly -and -not $MonitorAllEvents) {
|
||||
if ($event.Id -eq 4648) {
|
||||
$shouldIgnore = $true
|
||||
$ignoreReason = 'EventID 4648 excluded (MonitorInteractiveOnly)'
|
||||
} elseif ($event.Id -in 4624, 4625) {
|
||||
if ($osKind.IsWorkstation) {
|
||||
$interactiveTypes = @(10)
|
||||
$modeLabel = 'workstation LT10'
|
||||
} else {
|
||||
$interactiveTypes = @(2, 3, 10)
|
||||
$modeLabel = 'server LT2/3/10'
|
||||
}
|
||||
if ($interactiveTypes -notcontains $eventInfo.LogonType) {
|
||||
$shouldIgnore = $true
|
||||
$ignoreReason = "LogonType $($eventInfo.LogonType) not in $modeLabel"
|
||||
}
|
||||
} else {
|
||||
$shouldIgnore = $true
|
||||
$ignoreReason = "EventID $($event.Id) not monitored"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $shouldIgnore -and -not $MonitorAllEvents) {
|
||||
$shouldIgnore = Should-IgnoreEvent -Username $eventInfo.Username `
|
||||
if (Should-IgnoreEvent -Username $eventInfo.Username `
|
||||
-ProcessName $eventInfo.ProcessName `
|
||||
-ComputerName $eventInfo.ComputerName `
|
||||
-EventID $event.Id `
|
||||
-LogonType $eventInfo.LogonType `
|
||||
-SourceIP $eventInfo.SourceIP
|
||||
-SourceIP $eventInfo.SourceIP) {
|
||||
$shouldIgnore = $true
|
||||
$ignoreReason = 'ignore.lst or built-in exclusion (user/process/IP/workstation)'
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldIgnore) {
|
||||
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 (-not $shouldIgnore) {
|
||||
if ($event.Id -eq 4625 -and $FailedLogonRateLimitEnabled) {
|
||||
$rl = Get-FailedLogonRateLimitDecision4625 -SourceIP $eventInfo.SourceIP `
|
||||
-ComputerName $eventInfo.ComputerName -Username $eventInfo.Username `
|
||||
@@ -2733,7 +2788,6 @@ function Start-LoginMonitor {
|
||||
} | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
$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 подготовки).
|
||||
- При старте в Telegram/Email: строка **«Каналы уведомлений»** (фактический порядок доставки), плюс режим RDS/4740 по конфигурации.
|
||||
- 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) Автоматический перезапуск при падении
|
||||
|
||||
|
||||
@@ -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