diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index a26679c..0420a6a 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -90,7 +90,7 @@ $script:SkipLogDetailLimit = 15 # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). -$ScriptVersion = "2.0.35-SAC" +$ScriptVersion = "2.0.36-SAC" # Логи (все под InstallRoot) $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" @@ -111,6 +111,7 @@ $HeartbeatStaleAlertMultiplier = 2 $LoginSuccessNotifyDedupSeconds = 90 $HeartbeatFile = Join-Path $script:InstallRoot "Logs\last_heartbeat.txt" $GatewayPollCursorFile = Join-Path $script:InstallRoot "Logs\last_rdgateway_poll.txt" +$SecurityPollCursorFile = Join-Path $script:InstallRoot "Logs\last_security_poll.txt" $DeployUpdateMarkerFile = Join-Path $script:InstallRoot "deploy_last_update.txt" # Построчные правила подавления уведомлений Security 4624/4625 (см. ignore.lst.example в репозитории). $script:IgnoreListPath = Join-Path $script:InstallRoot "ignore.lst" @@ -132,6 +133,8 @@ $RDGatewayLogName = "Microsoft-Windows-TerminalServices-Gateway/Operational" $RDGatewayEvents = @(302, 303) # Макс. возраст сохранённого курсора RD Gateway (мин): если файл старше — replay не делаем. $GatewayEventsLookbackMinutes = 60 +# Security 4624/4625/4648: персистентный cursor + replay не старше N мин (медленный boot / поздний старт агента). +$SecurityEventsLookbackMinutes = 60 # RDP Remote Connection Manager (workstations): User authentication succeeded — событие 1149 $RcmLogName = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational" @@ -3563,6 +3566,51 @@ function Set-RdpGatewayPollCursor { } } +function Get-RdpSecurityPollCursor { + $now = Get-Date + $fresh = $now.AddSeconds(-10) + $maxAgeMin = [math]::Max(1, [int]$SecurityEventsLookbackMinutes) + $lookbackFloor = $now.AddMinutes(-1 * $maxAgeMin) + + if (-not (Test-Path -LiteralPath $SecurityPollCursorFile)) { + Write-Log "Security: cursor отсутствует — replay с $($lookbackFloor.ToString('dd.MM.yyyy HH:mm:ss')) (lookback ${maxAgeMin} мин)." + return $lookbackFloor + } + try { + $raw = (Get-Content -LiteralPath $SecurityPollCursorFile -TotalCount 1 -ErrorAction Stop).Trim() + if ([string]::IsNullOrWhiteSpace($raw)) { + Write-Log "Security: cursor пуст — replay с $($lookbackFloor.ToString('dd.MM.yyyy HH:mm:ss')) (lookback ${maxAgeMin} мин)." + return $lookbackFloor + } + $parsed = [datetime]::Parse($raw, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::RoundtripKind) + if ($parsed.Kind -eq [DateTimeKind]::Utc) { + $parsed = $parsed.ToLocalTime() + } + if ($parsed -lt $lookbackFloor) { + Write-Log "Security: cursor старше $maxAgeMin мин — replay с $($lookbackFloor.ToString('dd.MM.yyyy HH:mm:ss'))." + return $lookbackFloor + } + Write-Log "Security: cursor=$($parsed.ToString('dd.MM.yyyy HH:mm:ss')) (lookback cap ${maxAgeMin} мин)." + return $parsed + } catch { + Write-Log "Security: не удалось прочитать cursor ($SecurityPollCursorFile) — replay с $($lookbackFloor.ToString('dd.MM.yyyy HH:mm:ss'))." + return $lookbackFloor + } +} + +function Set-RdpSecurityPollCursor { + param([Parameter(Mandatory = $true)][datetime]$Cursor) + try { + $dir = Split-Path -Parent $SecurityPollCursorFile + if (-not (Test-Path -LiteralPath $dir)) { + New-Item -ItemType Directory -Path $dir -Force | Out-Null + } + Write-TextFileUtf8Bom -Path $SecurityPollCursorFile -Text ($Cursor.ToString('o')) + } catch { + Write-Log "WARN: Security cursor save failed: $($_.Exception.Message)" + } +} + function Test-RdgGatewayBenignErrorCode { param([string]$ErrorCode) if ([string]::IsNullOrWhiteSpace($ErrorCode)) { return $true } @@ -4215,7 +4263,7 @@ function Start-LoginMonitor { $nextInventoryTime = (Get-Date).AddSeconds($InventoryIntervalSec) $nextRotationCheck = Check-AndRotateLog $nextReportCheck = Check-AndSendDailyReport - $lastCheckTime = (Get-Date).AddSeconds(-10) + $lastCheckTime = Get-RdpSecurityPollCursor if ($rdGatewayAvailable) { $lastGatewayCheckTime = Get-RdpGatewayPollCursor Write-Log "RD Gateway: опрос 302/303 включён, cursor=$($lastGatewayCheckTime.ToString('dd.MM.yyyy HH:mm:ss'))" @@ -4425,6 +4473,7 @@ function Start-LoginMonitor { } $lastCheckTime = Update-MonitorPollCursor -CurrentCursor $lastCheckTime -Events @($events) } + Set-RdpSecurityPollCursor -Cursor $lastCheckTime if ($rdGatewayAvailable) { Set-MonitorLoopPhase -Phase 'poll_rdgateway' diff --git a/version.txt b/version.txt index 7d0946d..e17fcc9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.35-SAC +2.0.36-SAC