fix: restore RD Gateway 302/303 polling and SAC ingest 1.2.35-SAC
Fix poll cursor skipping events at time boundary, add 60min lookback on start, parse RU gateway messages, and map 303 disconnect to rdg.connection.disconnected. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+102
-14
@@ -82,7 +82,7 @@ $script:MonitorStopRequested = $false
|
|||||||
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
||||||
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
||||||
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
||||||
$ScriptVersion = "1.2.34-SAC"
|
$ScriptVersion = "1.2.35-SAC"
|
||||||
|
|
||||||
# Логи (все под InstallRoot)
|
# Логи (все под InstallRoot)
|
||||||
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
||||||
@@ -118,6 +118,8 @@ $LastReportFile = Join-Path $script:InstallRoot "Logs\last_daily_report.txt"
|
|||||||
$EnableRDGatewayMonitoring = $true
|
$EnableRDGatewayMonitoring = $true
|
||||||
$RDGatewayLogName = "Microsoft-Windows-TerminalServices-Gateway/Operational"
|
$RDGatewayLogName = "Microsoft-Windows-TerminalServices-Gateway/Operational"
|
||||||
$RDGatewayEvents = @(302, 303)
|
$RDGatewayEvents = @(302, 303)
|
||||||
|
# При старте цикла опроса — сколько минут назад читать 302/303 (чтобы не пропустить события до запуска монитора).
|
||||||
|
$GatewayEventsLookbackMinutes = 60
|
||||||
|
|
||||||
# RDP Remote Connection Manager (workstations): User authentication succeeded — событие 1149
|
# RDP Remote Connection Manager (workstations): User authentication succeeded — событие 1149
|
||||||
$RcmLogName = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational"
|
$RcmLogName = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational"
|
||||||
@@ -2792,6 +2794,22 @@ function Get-FailedLogonRateLimitDecision4625 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Update-MonitorPollCursor {
|
||||||
|
param(
|
||||||
|
[datetime]$CurrentCursor,
|
||||||
|
[array]$Events
|
||||||
|
)
|
||||||
|
if (-not $Events -or @($Events).Count -eq 0) {
|
||||||
|
return $CurrentCursor
|
||||||
|
}
|
||||||
|
$afterCursor = @($Events | Where-Object { $_.TimeCreated -gt $CurrentCursor })
|
||||||
|
if ($afterCursor.Count -eq 0) {
|
||||||
|
return $CurrentCursor
|
||||||
|
}
|
||||||
|
$maxTime = ($afterCursor | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum)
|
||||||
|
return $maxTime.AddMilliseconds(500)
|
||||||
|
}
|
||||||
|
|
||||||
function Test-RDGatewayLog {
|
function Test-RDGatewayLog {
|
||||||
try {
|
try {
|
||||||
$logExists = Get-WinEvent -ListLog $RDGatewayLogName -ErrorAction SilentlyContinue
|
$logExists = Get-WinEvent -ListLog $RDGatewayLogName -ErrorAction SilentlyContinue
|
||||||
@@ -2805,6 +2823,26 @@ function Test-RDGatewayLog {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-RDGatewayMessageFallback {
|
||||||
|
param([string]$Message)
|
||||||
|
if ([string]::IsNullOrWhiteSpace($Message)) { return $null }
|
||||||
|
$result = @{}
|
||||||
|
if ($Message -match '(?i)Пользователь\s+"([^"]+)"|User\s+"([^"]+)"') {
|
||||||
|
$result.Username = if ($Matches[1]) { $Matches[1] } else { $Matches[2] }
|
||||||
|
}
|
||||||
|
if ($Message -match '(?i)(?:компьютере|computer)\s+"([0-9a-fA-F:\.]+)"') {
|
||||||
|
$result.ExternalIP = $Matches[1]
|
||||||
|
}
|
||||||
|
if ($Message -match '(?i)(?:ресурсу|resource)\s*:\s*"([^"]+)"|(?:ресурсу|resource)\s+"([^"]+)"') {
|
||||||
|
$result.InternalIP = if ($Matches[1]) { $Matches[1] } else { $Matches[2] }
|
||||||
|
}
|
||||||
|
if ($Message -match '(?i)(?:протокол подключения|connection protocol)\s+"([^"]+)"') {
|
||||||
|
$result.Protocol = $Matches[1]
|
||||||
|
}
|
||||||
|
if ($result.Count -eq 0) { return $null }
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
function Test-RdpMonitorStringLooksLikeIPv4 {
|
function Test-RdpMonitorStringLooksLikeIPv4 {
|
||||||
param([string]$Value)
|
param([string]$Value)
|
||||||
if ([string]::IsNullOrWhiteSpace($Value)) { return $false }
|
if ([string]::IsNullOrWhiteSpace($Value)) { return $false }
|
||||||
@@ -2909,6 +2947,27 @@ function Get-RDGatewayEventInfo {
|
|||||||
$eventData.InternalIP = $eventData.Protocol.Trim()
|
$eventData.InternalIP = $eventData.Protocol.Trim()
|
||||||
$eventData.Protocol = 'N/A'
|
$eventData.Protocol = 'N/A'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$needsFallback = ($eventData.Username -eq 'N/A' -or [string]::IsNullOrWhiteSpace($eventData.Username)) -or
|
||||||
|
($eventData.ExternalIP -eq 'N/A' -or [string]::IsNullOrWhiteSpace($eventData.ExternalIP)) -or
|
||||||
|
($eventData.InternalIP -eq 'N/A' -or [string]::IsNullOrWhiteSpace($eventData.InternalIP))
|
||||||
|
if ($needsFallback) {
|
||||||
|
$fb = Get-RDGatewayMessageFallback -Message ([string]$Event.Message)
|
||||||
|
if ($null -ne $fb) {
|
||||||
|
if (($eventData.Username -eq 'N/A' -or [string]::IsNullOrWhiteSpace($eventData.Username)) -and $fb.Username) {
|
||||||
|
$eventData.Username = [string]$fb.Username
|
||||||
|
}
|
||||||
|
if (($eventData.ExternalIP -eq 'N/A' -or [string]::IsNullOrWhiteSpace($eventData.ExternalIP)) -and $fb.ExternalIP) {
|
||||||
|
$eventData.ExternalIP = [string]$fb.ExternalIP
|
||||||
|
}
|
||||||
|
if (($eventData.InternalIP -eq 'N/A' -or [string]::IsNullOrWhiteSpace($eventData.InternalIP)) -and $fb.InternalIP) {
|
||||||
|
$eventData.InternalIP = [string]$fb.InternalIP
|
||||||
|
}
|
||||||
|
if (($eventData.Protocol -eq 'N/A' -or [string]::IsNullOrWhiteSpace($eventData.Protocol)) -and $fb.Protocol) {
|
||||||
|
$eventData.Protocol = [string]$fb.Protocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Write-Log "Ошибка при извлечении RD Gateway события: $($_.Exception.Message)"
|
Write-Log "Ошибка при извлечении RD Gateway события: $($_.Exception.Message)"
|
||||||
}
|
}
|
||||||
@@ -3266,7 +3325,15 @@ function Start-LoginMonitor {
|
|||||||
$nextRotationCheck = Check-AndRotateLog
|
$nextRotationCheck = Check-AndRotateLog
|
||||||
$nextReportCheck = Check-AndSendDailyReport
|
$nextReportCheck = Check-AndSendDailyReport
|
||||||
$lastCheckTime = (Get-Date).AddSeconds(-10)
|
$lastCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
$lastGatewayCheckTime = (Get-Date).AddSeconds(-10)
|
if ($rdGatewayAvailable) {
|
||||||
|
$lastGatewayCheckTime = (Get-Date).AddMinutes(-1 * [math]::Max(1, $GatewayEventsLookbackMinutes))
|
||||||
|
Write-Log "RD Gateway: опрос 302/303 включён, lookback ${GatewayEventsLookbackMinutes} мин (с $($lastGatewayCheckTime.ToString('dd.MM.yyyy HH:mm:ss')))"
|
||||||
|
} else {
|
||||||
|
$lastGatewayCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
|
if ($EnableRDGatewayMonitoring) {
|
||||||
|
Write-Log "RD Gateway: журнал недоступен — опрос 302/303 отключён ($RDGatewayLogName)"
|
||||||
|
}
|
||||||
|
}
|
||||||
$lastRcmCheckTime = (Get-Date).AddSeconds(-10)
|
$lastRcmCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
$lastRcmShadowCheckTime = (Get-Date).AddSeconds(-10)
|
$lastRcmShadowCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
$lastWinRmCheckTime = (Get-Date).AddSeconds(-10)
|
$lastWinRmCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
@@ -3440,7 +3507,7 @@ function Start-LoginMonitor {
|
|||||||
} | Out-Null
|
} | Out-Null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$lastCheckTime = ($events | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
$lastCheckTime = Update-MonitorPollCursor -CurrentCursor $lastCheckTime -Events @($events)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($rdGatewayAvailable) {
|
if ($rdGatewayAvailable) {
|
||||||
@@ -3451,10 +3518,16 @@ function Start-LoginMonitor {
|
|||||||
} -ErrorAction SilentlyContinue
|
} -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
if ($gatewayEvents) {
|
if ($gatewayEvents) {
|
||||||
foreach ($event in $gatewayEvents) {
|
$gatewaySorted = @($gatewayEvents | Sort-Object TimeCreated, RecordId)
|
||||||
|
$gatewayFetched = $gatewaySorted.Count
|
||||||
|
$gatewayNotified = 0
|
||||||
|
foreach ($event in $gatewaySorted) {
|
||||||
if ($event.TimeCreated -le $lastGatewayCheckTime) { continue }
|
if ($event.TimeCreated -le $lastGatewayCheckTime) { continue }
|
||||||
$ei = Get-RDGatewayEventInfo -Event $event
|
$ei = Get-RDGatewayEventInfo -Event $event
|
||||||
if ($ei.Username -like "*$") { continue }
|
if ($ei.Username.EndsWith('$', [StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
Write-Log "Skip RDG $($event.Id): machine account $($ei.Username)"
|
||||||
|
continue
|
||||||
|
}
|
||||||
$msg = Format-RDGatewayEvent -EventID $event.Id `
|
$msg = Format-RDGatewayEvent -EventID $event.Id `
|
||||||
-Username $ei.Username `
|
-Username $ei.Username `
|
||||||
-ExternalIP $ei.ExternalIP `
|
-ExternalIP $ei.ExternalIP `
|
||||||
@@ -3462,22 +3535,37 @@ function Start-LoginMonitor {
|
|||||||
-Protocol $ei.Protocol `
|
-Protocol $ei.Protocol `
|
||||||
-ErrorCode $ei.ErrorCode `
|
-ErrorCode $ei.ErrorCode `
|
||||||
-TimeCreated $ei.TimeCreated
|
-TimeCreated $ei.TimeCreated
|
||||||
Write-Log "Notify RDG: ID=$($event.Id) User=$($ei.Username)"
|
$hasRdgError = ($ei.ErrorCode -ne '0' -and $ei.ErrorCode -ne 'N/A' -and -not [string]::IsNullOrWhiteSpace($ei.ErrorCode))
|
||||||
$rdgType = if ($event.Id -eq 302) { 'rdg.connection.success' } else { 'rdg.connection.failed' }
|
if ($event.Id -eq 302) {
|
||||||
$rdgSev = if ($event.Id -eq 302) { 'info' } else { 'warning' }
|
$rdgType = 'rdg.connection.success'
|
||||||
|
$rdgSev = 'info'
|
||||||
|
} elseif ($hasRdgError) {
|
||||||
|
$rdgType = 'rdg.connection.failed'
|
||||||
|
$rdgSev = 'warning'
|
||||||
|
} else {
|
||||||
|
$rdgType = 'rdg.connection.disconnected'
|
||||||
|
$rdgSev = 'info'
|
||||||
|
}
|
||||||
|
Write-Log "Notify RDG: ID=$($event.Id) User=$($ei.Username) Target=$($ei.InternalIP) Type=$rdgType"
|
||||||
Send-MonitorNotification -Message $msg `
|
Send-MonitorNotification -Message $msg `
|
||||||
-EmailSubject "RDP Login Monitor: RD Gateway ($($event.Id))" `
|
-EmailSubject "RDP Login Monitor: RD Gateway ($($event.Id))" `
|
||||||
-SacEventType $rdgType -SacSeverity $rdgSev `
|
-SacEventType $rdgType -SacSeverity $rdgSev `
|
||||||
-SacTitle "RD Gateway event $($event.Id)" `
|
-SacTitle "RD Gateway event $($event.Id)" `
|
||||||
-SacSummary "RDG $($event.Id) $($ei.Username)" `
|
-SacSummary "RDG $($event.Id) $($ei.Username) -> $($ei.InternalIP)" `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
user = $ei.Username
|
user = $ei.Username
|
||||||
external_ip = $ei.ExternalIP
|
external_ip = $ei.ExternalIP
|
||||||
internal_ip = $ei.InternalIP
|
internal_ip = $ei.InternalIP
|
||||||
|
protocol = $ei.Protocol
|
||||||
|
gateway_error_code = $ei.ErrorCode
|
||||||
event_id_windows = [int]$event.Id
|
event_id_windows = [int]$event.Id
|
||||||
} | Out-Null
|
} | Out-Null
|
||||||
|
$gatewayNotified++
|
||||||
|
}
|
||||||
|
$lastGatewayCheckTime = Update-MonitorPollCursor -CurrentCursor $lastGatewayCheckTime -Events $gatewaySorted
|
||||||
|
if ($gatewayFetched -gt 0) {
|
||||||
|
Write-Log "RD Gateway poll: fetched=$gatewayFetched notified=$gatewayNotified cursor=$($lastGatewayCheckTime.ToString('dd.MM.yyyy HH:mm:ss'))"
|
||||||
}
|
}
|
||||||
$lastGatewayCheckTime = ($gatewayEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3510,7 +3598,7 @@ function Start-LoginMonitor {
|
|||||||
event_id_windows = 1149
|
event_id_windows = 1149
|
||||||
} | Out-Null
|
} | Out-Null
|
||||||
}
|
}
|
||||||
$lastRcmCheckTime = ($rcmEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
$lastRcmCheckTime = Update-MonitorPollCursor -CurrentCursor $lastRcmCheckTime -Events @($rcmEvents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3554,7 +3642,7 @@ function Start-LoginMonitor {
|
|||||||
session_id = $sh.SessionId
|
session_id = $sh.SessionId
|
||||||
} | Out-Null
|
} | Out-Null
|
||||||
}
|
}
|
||||||
$lastRcmShadowCheckTime = ($shadowEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
$lastRcmShadowCheckTime = Update-MonitorPollCursor -CurrentCursor $lastRcmShadowCheckTime -Events @($shadowEvents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3604,7 +3692,7 @@ function Start-LoginMonitor {
|
|||||||
transport = 'winrm'
|
transport = 'winrm'
|
||||||
} | Out-Null
|
} | Out-Null
|
||||||
}
|
}
|
||||||
$lastWinRmCheckTime = ($winRmEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
$lastWinRmCheckTime = Update-MonitorPollCursor -CurrentCursor $lastWinRmCheckTime -Events @($winRmEvents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3645,7 +3733,7 @@ function Start-LoginMonitor {
|
|||||||
iis_client_ips = @($iisIps)
|
iis_client_ips = @($iisIps)
|
||||||
} | Out-Null
|
} | Out-Null
|
||||||
}
|
}
|
||||||
$lastLockout4740CheckTime = ($lockoutEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
$lastLockout4740CheckTime = Update-MonitorPollCursor -CurrentCursor $lastLockout4740CheckTime -Events @($lockoutEvents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.2.34-SAC
|
1.2.35-SAC
|
||||||
|
|||||||
Reference in New Issue
Block a user