fix: RD Gateway startup replay, 1226 benign, SAC occurred_at (2.0.14-SAC)
Persist RD Gateway poll cursor to avoid replaying 302/303 on monitor restart; treat ErrorCode 1226 as normal disconnect; pass event TimeCreated to SAC ingest. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+80
-21
@@ -89,7 +89,7 @@ $script:SkipLogDetailLimit = 15
|
|||||||
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
|
||||||
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
|
||||||
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
|
||||||
$ScriptVersion = "2.0.13-SAC"
|
$ScriptVersion = "2.0.14-SAC"
|
||||||
|
|
||||||
# Логи (все под InstallRoot)
|
# Логи (все под InstallRoot)
|
||||||
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
||||||
@@ -106,6 +106,7 @@ $HeartbeatStaleAlertMultiplier = 2
|
|||||||
# Один RDP-вход часто даёт 2+ события 4624 с одним временем — не слать дубли в Telegram/SAC.
|
# Один RDP-вход часто даёт 2+ события 4624 с одним временем — не слать дубли в Telegram/SAC.
|
||||||
$LoginSuccessNotifyDedupSeconds = 90
|
$LoginSuccessNotifyDedupSeconds = 90
|
||||||
$HeartbeatFile = Join-Path $script:InstallRoot "Logs\last_heartbeat.txt"
|
$HeartbeatFile = Join-Path $script:InstallRoot "Logs\last_heartbeat.txt"
|
||||||
|
$GatewayPollCursorFile = Join-Path $script:InstallRoot "Logs\last_rdgateway_poll.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 в репозитории).
|
||||||
$script:IgnoreListPath = Join-Path $script:InstallRoot "ignore.lst"
|
$script:IgnoreListPath = Join-Path $script:InstallRoot "ignore.lst"
|
||||||
@@ -125,7 +126,7 @@ $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 (чтобы не пропустить события до запуска монитора).
|
# Макс. возраст сохранённого курсора RD Gateway (мин): если файл старше — replay не делаем.
|
||||||
$GatewayEventsLookbackMinutes = 60
|
$GatewayEventsLookbackMinutes = 60
|
||||||
|
|
||||||
# RDP Remote Connection Manager (workstations): User authentication succeeded — событие 1149
|
# RDP Remote Connection Manager (workstations): User authentication succeeded — событие 1149
|
||||||
@@ -976,7 +977,8 @@ function Send-MonitorNotification {
|
|||||||
[string]$SacSeverity = 'info',
|
[string]$SacSeverity = 'info',
|
||||||
[string]$SacTitle = '',
|
[string]$SacTitle = '',
|
||||||
[string]$SacSummary = '',
|
[string]$SacSummary = '',
|
||||||
[hashtable]$SacDetails = $null
|
[hashtable]$SacDetails = $null,
|
||||||
|
[datetime]$SacOccurredAt = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
$title = if (-not [string]::IsNullOrWhiteSpace($SacTitle)) { $SacTitle } else { $EmailSubject }
|
$title = if (-not [string]::IsNullOrWhiteSpace($SacTitle)) { $SacTitle } else { $EmailSubject }
|
||||||
@@ -991,7 +993,7 @@ function Send-MonitorNotification {
|
|||||||
$sacMode = Get-SacNormalizedMode
|
$sacMode = Get-SacNormalizedMode
|
||||||
if ($sacMode -ne 'off') {
|
if ($sacMode -ne 'off') {
|
||||||
return Send-NotifyOrSac -EventType $etype -Severity $SacSeverity -Title $title -Summary $summary `
|
return Send-NotifyOrSac -EventType $etype -Severity $SacSeverity -Title $title -Summary $summary `
|
||||||
-TelegramMessage $Message -EmailSubject $EmailSubject -Details $SacDetails
|
-TelegramMessage $Message -EmailSubject $EmailSubject -Details $SacDetails -OccurredAt $SacOccurredAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2962,6 +2964,64 @@ function Get-FailedLogonRateLimitDecision4625 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-RdpGatewayPollCursor {
|
||||||
|
$now = Get-Date
|
||||||
|
$fresh = $now.AddSeconds(-10)
|
||||||
|
if (-not (Test-Path -LiteralPath $GatewayPollCursorFile)) {
|
||||||
|
return $fresh
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$raw = (Get-Content -LiteralPath $GatewayPollCursorFile -TotalCount 1 -ErrorAction Stop).Trim()
|
||||||
|
if ([string]::IsNullOrWhiteSpace($raw)) { return $fresh }
|
||||||
|
$parsed = [datetime]::Parse($raw, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::RoundtripKind)
|
||||||
|
if ($parsed.Kind -eq [DateTimeKind]::Utc) {
|
||||||
|
$parsed = $parsed.ToLocalTime()
|
||||||
|
}
|
||||||
|
$maxAgeMin = [math]::Max(1, [int]$GatewayEventsLookbackMinutes)
|
||||||
|
if ($parsed -lt $now.AddMinutes(-1 * $maxAgeMin)) {
|
||||||
|
Write-Log "RD Gateway: сохранённый cursor старше $maxAgeMin мин — без replay, курсор с now-10с"
|
||||||
|
return $fresh
|
||||||
|
}
|
||||||
|
return $parsed
|
||||||
|
} catch {
|
||||||
|
Write-Log "RD Gateway: не удалось прочитать cursor ($GatewayPollCursorFile) — курсор с now-10с"
|
||||||
|
return $fresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-RdpGatewayPollCursor {
|
||||||
|
param([Parameter(Mandatory = $true)][datetime]$Cursor)
|
||||||
|
try {
|
||||||
|
$dir = Split-Path -Parent $GatewayPollCursorFile
|
||||||
|
if (-not (Test-Path -LiteralPath $dir)) {
|
||||||
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||||
|
}
|
||||||
|
Write-TextFileUtf8Bom -Path $GatewayPollCursorFile -Text ($Cursor.ToString('o'))
|
||||||
|
} catch {
|
||||||
|
Write-Log "WARN: RD Gateway cursor save failed: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-RdgGatewayBenignErrorCode {
|
||||||
|
param([string]$ErrorCode)
|
||||||
|
if ([string]::IsNullOrWhiteSpace($ErrorCode)) { return $true }
|
||||||
|
$code = $ErrorCode.Trim()
|
||||||
|
if ($code -eq '0' -or $code -eq 'N/A') { return $true }
|
||||||
|
# RD Gateway: 1226 — штатное закрытие туннеля (не инцидент).
|
||||||
|
if ($code -eq '1226') { return $true }
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RdgGatewaySacEventType {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][int]$EventId,
|
||||||
|
[string]$ErrorCode = ''
|
||||||
|
)
|
||||||
|
if ($EventId -eq 302) { return 'rdg.connection.success' }
|
||||||
|
if (Test-RdgGatewayBenignErrorCode -ErrorCode $ErrorCode) { return 'rdg.connection.disconnected' }
|
||||||
|
return 'rdg.connection.failed'
|
||||||
|
}
|
||||||
|
|
||||||
function Update-MonitorPollCursor {
|
function Update-MonitorPollCursor {
|
||||||
param(
|
param(
|
||||||
[datetime]$CurrentCursor,
|
[datetime]$CurrentCursor,
|
||||||
@@ -3204,10 +3264,10 @@ function Format-RDGatewayEvent {
|
|||||||
$message = "<b>"
|
$message = "<b>"
|
||||||
if ($EventID -eq 302) { $message += "🖥️ ПОДКЛЮЧЕНИЕ ЧЕРЕЗ RD GATEWAY" }
|
if ($EventID -eq 302) { $message += "🖥️ ПОДКЛЮЧЕНИЕ ЧЕРЕЗ RD GATEWAY" }
|
||||||
elseif ($EventID -eq 303) {
|
elseif ($EventID -eq 303) {
|
||||||
if ($ErrorCode -ne "0" -and $ErrorCode -ne "N/A" -and -not [string]::IsNullOrWhiteSpace($ErrorCode)) {
|
if (Test-RdgGatewayBenignErrorCode -ErrorCode $ErrorCode) {
|
||||||
$message += "⚠️ СЕАНС ЧЕРЕЗ RD GATEWAY ЗАВЕРШЁН С ОШИБКОЙ"
|
|
||||||
} else {
|
|
||||||
$message += "ℹ️ СЕАНС ЧЕРЕЗ RD GATEWAY ЗАВЕРШЁН"
|
$message += "ℹ️ СЕАНС ЧЕРЕЗ RD GATEWAY ЗАВЕРШЁН"
|
||||||
|
} else {
|
||||||
|
$message += "⚠️ СЕАНС ЧЕРЕЗ RD GATEWAY ЗАВЕРШЁН С ОШИБКОЙ"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { $message += "⚠️ СОБЫТИЕ RD GATEWAY" }
|
else { $message += "⚠️ СОБЫТИЕ RD GATEWAY" }
|
||||||
@@ -3220,7 +3280,7 @@ function Format-RDGatewayEvent {
|
|||||||
if ($EventID -eq 303 -and $SessionDurationSec -gt 0) {
|
if ($EventID -eq 303 -and $SessionDurationSec -gt 0) {
|
||||||
$message += "⏱️ Длительность сессии: $(ConvertTo-TelegramHtml ([string]$SessionDurationSec)) с`r`n"
|
$message += "⏱️ Длительность сессии: $(ConvertTo-TelegramHtml ([string]$SessionDurationSec)) с`r`n"
|
||||||
}
|
}
|
||||||
if ($EventID -eq 303 -and $ErrorCode -ne "0" -and $ErrorCode -ne "N/A" -and -not [string]::IsNullOrWhiteSpace($ErrorCode)) {
|
if ($EventID -eq 303 -and -not (Test-RdgGatewayBenignErrorCode -ErrorCode $ErrorCode)) {
|
||||||
$message += "⚠️ Код ошибки: $(ConvertTo-TelegramHtml $ErrorCode)`r`n"
|
$message += "⚠️ Код ошибки: $(ConvertTo-TelegramHtml $ErrorCode)`r`n"
|
||||||
}
|
}
|
||||||
$message += "🕐 Время: $hTime`r`n"
|
$message += "🕐 Время: $hTime`r`n"
|
||||||
@@ -3582,8 +3642,8 @@ function Start-LoginMonitor {
|
|||||||
$nextReportCheck = Check-AndSendDailyReport
|
$nextReportCheck = Check-AndSendDailyReport
|
||||||
$lastCheckTime = (Get-Date).AddSeconds(-10)
|
$lastCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
if ($rdGatewayAvailable) {
|
if ($rdGatewayAvailable) {
|
||||||
$lastGatewayCheckTime = (Get-Date).AddMinutes(-1 * [math]::Max(1, $GatewayEventsLookbackMinutes))
|
$lastGatewayCheckTime = Get-RdpGatewayPollCursor
|
||||||
Write-Log "RD Gateway: опрос 302/303 включён, lookback ${GatewayEventsLookbackMinutes} мин (с $($lastGatewayCheckTime.ToString('dd.MM.yyyy HH:mm:ss')))"
|
Write-Log "RD Gateway: опрос 302/303 включён, cursor=$($lastGatewayCheckTime.ToString('dd.MM.yyyy HH:mm:ss'))"
|
||||||
} else {
|
} else {
|
||||||
$lastGatewayCheckTime = (Get-Date).AddSeconds(-10)
|
$lastGatewayCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
if ($EnableRDGatewayMonitoring) {
|
if ($EnableRDGatewayMonitoring) {
|
||||||
@@ -3734,6 +3794,7 @@ function Start-LoginMonitor {
|
|||||||
-SacEventType 'rdp.login.failed' -SacSeverity 'warning' `
|
-SacEventType 'rdp.login.failed' -SacSeverity 'warning' `
|
||||||
-SacTitle 'RDP login failed' `
|
-SacTitle 'RDP login failed' `
|
||||||
-SacSummary "4625 $($eventInfo.Username) from $($eventInfo.SourceIP)" `
|
-SacSummary "4625 $($eventInfo.Username) from $($eventInfo.SourceIP)" `
|
||||||
|
-SacOccurredAt $eventInfo.TimeCreated `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
user = $eventInfo.Username
|
user = $eventInfo.Username
|
||||||
ip_address = $eventInfo.SourceIP
|
ip_address = $eventInfo.SourceIP
|
||||||
@@ -3773,6 +3834,7 @@ function Start-LoginMonitor {
|
|||||||
-SacEventType $sacType -SacSeverity 'info' `
|
-SacEventType $sacType -SacSeverity 'info' `
|
||||||
-SacTitle "RDP login event $($event.Id)" `
|
-SacTitle "RDP login event $($event.Id)" `
|
||||||
-SacSummary "Event $($event.Id) $($eventInfo.Username) from $($eventInfo.SourceIP)" `
|
-SacSummary "Event $($event.Id) $($eventInfo.Username) from $($eventInfo.SourceIP)" `
|
||||||
|
-SacOccurredAt $eventInfo.TimeCreated `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
user = $eventInfo.Username
|
user = $eventInfo.Username
|
||||||
ip_address = $eventInfo.SourceIP
|
ip_address = $eventInfo.SourceIP
|
||||||
@@ -3817,23 +3879,15 @@ function Start-LoginMonitor {
|
|||||||
-ErrorCode $ei.ErrorCode `
|
-ErrorCode $ei.ErrorCode `
|
||||||
-TimeCreated $ei.TimeCreated `
|
-TimeCreated $ei.TimeCreated `
|
||||||
-SessionDurationSec $ei.SessionDurationSec
|
-SessionDurationSec $ei.SessionDurationSec
|
||||||
$hasRdgError = ($ei.ErrorCode -ne '0' -and $ei.ErrorCode -ne 'N/A' -and -not [string]::IsNullOrWhiteSpace($ei.ErrorCode))
|
$rdgType = Get-RdgGatewaySacEventType -EventId $event.Id -ErrorCode $ei.ErrorCode
|
||||||
if ($event.Id -eq 302) {
|
$rdgSev = if ($rdgType -eq 'rdg.connection.failed') { 'warning' } else { 'info' }
|
||||||
$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"
|
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) -> $($ei.InternalIP)" `
|
-SacSummary "RDG $($event.Id) $($ei.Username) -> $($ei.InternalIP)" `
|
||||||
|
-SacOccurredAt $ei.TimeCreated `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
user = $ei.Username
|
user = $ei.Username
|
||||||
external_ip = $ei.ExternalIP
|
external_ip = $ei.ExternalIP
|
||||||
@@ -3848,6 +3902,7 @@ function Start-LoginMonitor {
|
|||||||
$gatewayNotified++
|
$gatewayNotified++
|
||||||
}
|
}
|
||||||
$lastGatewayCheckTime = Update-MonitorPollCursor -CurrentCursor $lastGatewayCheckTime -Events $gatewaySorted
|
$lastGatewayCheckTime = Update-MonitorPollCursor -CurrentCursor $lastGatewayCheckTime -Events $gatewaySorted
|
||||||
|
Set-RdpGatewayPollCursor -Cursor $lastGatewayCheckTime
|
||||||
if ($gatewayFetched -gt 0) {
|
if ($gatewayFetched -gt 0) {
|
||||||
Write-Log "RD Gateway poll: fetched=$gatewayFetched notified=$gatewayNotified cursor=$($lastGatewayCheckTime.ToString('dd.MM.yyyy HH:mm:ss'))"
|
Write-Log "RD Gateway poll: fetched=$gatewayFetched notified=$gatewayNotified cursor=$($lastGatewayCheckTime.ToString('dd.MM.yyyy HH:mm:ss'))"
|
||||||
}
|
}
|
||||||
@@ -3877,6 +3932,7 @@ function Start-LoginMonitor {
|
|||||||
-SacEventType 'rdp.login.success' -SacSeverity 'info' `
|
-SacEventType 'rdp.login.success' -SacSeverity 'info' `
|
||||||
-SacTitle 'RDP connection (RCM 1149)' `
|
-SacTitle 'RDP connection (RCM 1149)' `
|
||||||
-SacSummary "RCM 1149 $($rcmInfo.Username) $($rcmInfo.ClientIP)" `
|
-SacSummary "RCM 1149 $($rcmInfo.Username) $($rcmInfo.ClientIP)" `
|
||||||
|
-SacOccurredAt $rcmInfo.TimeCreated `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
user = $rcmInfo.Username
|
user = $rcmInfo.Username
|
||||||
ip_address = $rcmInfo.ClientIP
|
ip_address = $rcmInfo.ClientIP
|
||||||
@@ -3917,6 +3973,7 @@ function Start-LoginMonitor {
|
|||||||
-SacEventType $sacType -SacSeverity 'warning' `
|
-SacEventType $sacType -SacSeverity 'warning' `
|
||||||
-SacTitle "RDS Shadow Control $($event.Id)" `
|
-SacTitle "RDS Shadow Control $($event.Id)" `
|
||||||
-SacSummary "Shadow $($event.Id) $($sh.ShadowerUser) -> $($sh.TargetUser)" `
|
-SacSummary "Shadow $($event.Id) $($sh.ShadowerUser) -> $($sh.TargetUser)" `
|
||||||
|
-SacOccurredAt $sh.TimeCreated `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
event_id_windows = [int]$event.Id
|
event_id_windows = [int]$event.Id
|
||||||
shadow_mode = 'control'
|
shadow_mode = 'control'
|
||||||
@@ -3968,6 +4025,7 @@ function Start-LoginMonitor {
|
|||||||
-SacEventType 'winrm.session.started' -SacSeverity 'warning' `
|
-SacEventType 'winrm.session.started' -SacSeverity 'warning' `
|
||||||
-SacTitle 'WinRM remote shell (Enter-PSSession)' `
|
-SacTitle 'WinRM remote shell (Enter-PSSession)' `
|
||||||
-SacSummary "WinRM 91 $($wr.User) from $($wr.SourceIP)" `
|
-SacSummary "WinRM 91 $($wr.User) from $($wr.SourceIP)" `
|
||||||
|
-SacOccurredAt $wr.TimeCreated `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
event_id_windows = [int]$event.Id
|
event_id_windows = [int]$event.Id
|
||||||
user = $wr.User
|
user = $wr.User
|
||||||
@@ -4012,6 +4070,7 @@ function Start-LoginMonitor {
|
|||||||
-SacEventType 'auth.account.locked' -SacSeverity 'high' `
|
-SacEventType 'auth.account.locked' -SacSeverity 'high' `
|
||||||
-SacTitle "Account locked $($lo.Username)" `
|
-SacTitle "Account locked $($lo.Username)" `
|
||||||
-SacSummary "4740 $($lo.Username)" `
|
-SacSummary "4740 $($lo.Username)" `
|
||||||
|
-SacOccurredAt $lo.TimeCreated `
|
||||||
-SacDetails @{
|
-SacDetails @{
|
||||||
user = $lo.Username
|
user = $lo.Username
|
||||||
domain = $lo.Domain
|
domain = $lo.Domain
|
||||||
|
|||||||
+23
-10
@@ -1,4 +1,4 @@
|
|||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Клиент Security Alert Center для RDP-login-monitor.
|
Клиент Security Alert Center для RDP-login-monitor.
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
@@ -167,6 +167,16 @@ function Get-SacOccurredAtIso {
|
|||||||
return [DateTimeOffset]::Now.ToString('yyyy-MM-ddTHH:mm:ss.fffK')
|
return [DateTimeOffset]::Now.ToString('yyyy-MM-ddTHH:mm:ss.fffK')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-SacOccurredAtIsoFromDateTime {
|
||||||
|
param([Parameter(Mandatory = $true)][datetime]$When)
|
||||||
|
|
||||||
|
$dt = $When
|
||||||
|
if ($dt.Kind -eq [DateTimeKind]::Unspecified) {
|
||||||
|
$dt = [datetime]::SpecifyKind($dt, [DateTimeKind]::Local)
|
||||||
|
}
|
||||||
|
return ([DateTimeOffset]$dt).ToString('yyyy-MM-ddTHH:mm:ss.fffK')
|
||||||
|
}
|
||||||
|
|
||||||
function Convert-AnyToJsonSerializable {
|
function Convert-AnyToJsonSerializable {
|
||||||
param($Value)
|
param($Value)
|
||||||
if ($null -eq $Value) { return $null }
|
if ($null -eq $Value) { return $null }
|
||||||
@@ -331,7 +341,8 @@ function New-SacEventPayload {
|
|||||||
[Parameter(Mandatory = $true)][string]$Severity,
|
[Parameter(Mandatory = $true)][string]$Severity,
|
||||||
[Parameter(Mandatory = $true)][string]$Title,
|
[Parameter(Mandatory = $true)][string]$Title,
|
||||||
[Parameter(Mandatory = $true)][string]$Summary,
|
[Parameter(Mandatory = $true)][string]$Summary,
|
||||||
[hashtable]$Details = $null
|
[hashtable]$Details = $null,
|
||||||
|
[datetime]$OccurredAt = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
$Title = Limit-SacString -Text $Title -MaxLen 256 -Label 'title'
|
$Title = Limit-SacString -Text $Title -MaxLen 256 -Label 'title'
|
||||||
@@ -340,7 +351,7 @@ function New-SacEventPayload {
|
|||||||
$payload = [ordered]@{
|
$payload = [ordered]@{
|
||||||
schema_version = '1.0'
|
schema_version = '1.0'
|
||||||
event_id = [guid]::NewGuid().ToString()
|
event_id = [guid]::NewGuid().ToString()
|
||||||
occurred_at = (Get-SacOccurredAtIso)
|
occurred_at = if ($null -ne $OccurredAt) { (Get-SacOccurredAtIsoFromDateTime -When $OccurredAt) } else { (Get-SacOccurredAtIso) }
|
||||||
source = [ordered]@{
|
source = [ordered]@{
|
||||||
product = 'rdp-login-monitor'
|
product = 'rdp-login-monitor'
|
||||||
product_version = if ($ScriptVersion) { [string]$ScriptVersion } else { 'unknown' }
|
product_version = if ($ScriptVersion) { [string]$ScriptVersion } else { 'unknown' }
|
||||||
@@ -644,7 +655,8 @@ function Send-SacEvent {
|
|||||||
[Parameter(Mandatory = $true)][string]$Severity,
|
[Parameter(Mandatory = $true)][string]$Severity,
|
||||||
[Parameter(Mandatory = $true)][string]$Title,
|
[Parameter(Mandatory = $true)][string]$Title,
|
||||||
[Parameter(Mandatory = $true)][string]$Summary,
|
[Parameter(Mandatory = $true)][string]$Summary,
|
||||||
[hashtable]$Details = $null
|
[hashtable]$Details = $null,
|
||||||
|
[datetime]$OccurredAt = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
if (-not (Test-SacConfigured)) {
|
if (-not (Test-SacConfigured)) {
|
||||||
@@ -657,7 +669,7 @@ function Send-SacEvent {
|
|||||||
$mergedDetails = $null
|
$mergedDetails = $null
|
||||||
}
|
}
|
||||||
|
|
||||||
$payload = $(New-SacEventPayload -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $mergedDetails)
|
$payload = $(New-SacEventPayload -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $mergedDetails -OccurredAt $OccurredAt)
|
||||||
if ($payload -is [System.Array]) {
|
if ($payload -is [System.Array]) {
|
||||||
$payload = $payload[-1]
|
$payload = $payload[-1]
|
||||||
}
|
}
|
||||||
@@ -727,7 +739,8 @@ function Send-NotifyOrSac {
|
|||||||
[Parameter(Mandatory = $true)][string]$Summary,
|
[Parameter(Mandatory = $true)][string]$Summary,
|
||||||
[string]$TelegramMessage = '',
|
[string]$TelegramMessage = '',
|
||||||
[string]$EmailSubject = 'RDP Login Monitor',
|
[string]$EmailSubject = 'RDP Login Monitor',
|
||||||
[hashtable]$Details = $null
|
[hashtable]$Details = $null,
|
||||||
|
[datetime]$OccurredAt = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
if ([string]::IsNullOrWhiteSpace($TelegramMessage)) {
|
if ([string]::IsNullOrWhiteSpace($TelegramMessage)) {
|
||||||
@@ -742,7 +755,7 @@ function Send-NotifyOrSac {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
$hbDetails = Merge-SacNotifyDetails -Details $Details -TelegramVia 'sac'
|
$hbDetails = Merge-SacNotifyDetails -Details $Details -TelegramVia 'sac'
|
||||||
return (Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $hbDetails)
|
return (Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $hbDetails -OccurredAt $OccurredAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($mode) {
|
switch ($mode) {
|
||||||
@@ -751,16 +764,16 @@ function Send-NotifyOrSac {
|
|||||||
}
|
}
|
||||||
'exclusive' {
|
'exclusive' {
|
||||||
$merged = Merge-SacNotifyDetails -Details $Details -TelegramVia 'sac'
|
$merged = Merge-SacNotifyDetails -Details $Details -TelegramVia 'sac'
|
||||||
return (Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $merged)
|
return (Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $merged -OccurredAt $OccurredAt)
|
||||||
}
|
}
|
||||||
'dual' {
|
'dual' {
|
||||||
$merged = Merge-SacNotifyDetails -Details $Details -TelegramVia 'agent'
|
$merged = Merge-SacNotifyDetails -Details $Details -TelegramVia 'agent'
|
||||||
Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $merged | Out-Null
|
Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $merged -OccurredAt $OccurredAt | Out-Null
|
||||||
return (Send-SacLocalChannels -TelegramMessage $TelegramMessage -EmailSubject $EmailSubject)
|
return (Send-SacLocalChannels -TelegramMessage $TelegramMessage -EmailSubject $EmailSubject)
|
||||||
}
|
}
|
||||||
'fallback' {
|
'fallback' {
|
||||||
$merged = Merge-SacNotifyDetails -Details $Details -TelegramVia 'sac'
|
$merged = Merge-SacNotifyDetails -Details $Details -TelegramVia 'sac'
|
||||||
if (Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $merged) {
|
if (Send-SacEvent -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $merged -OccurredAt $OccurredAt) {
|
||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
return (Send-SacLocalChannels -TelegramMessage $TelegramMessage -EmailSubject $EmailSubject)
|
return (Send-SacLocalChannels -TelegramMessage $TelegramMessage -EmailSubject $EmailSubject)
|
||||||
|
|||||||
@@ -47,3 +47,9 @@ if ($map['SessionDuration'] -ne '0') {
|
|||||||
throw "Expected SessionDuration=0, got $($map['SessionDuration'])"
|
throw "Expected SessionDuration=0, got $($map['SessionDuration'])"
|
||||||
}
|
}
|
||||||
Write-Host 'OK: RD Gateway EventInfo XML fields parsed correctly (ErrorCode != BytesReceived).'
|
Write-Host 'OK: RD Gateway EventInfo XML fields parsed correctly (ErrorCode != BytesReceived).'
|
||||||
|
|
||||||
|
# 1226 в sample — типичный штатный код закрытия туннеля (в Login_Monitor.ps1 → disconnected, не failed).
|
||||||
|
if ($map['ErrorCode'] -ne '1226') {
|
||||||
|
throw 'Expected sample ErrorCode 1226 for standard RDG disconnect'
|
||||||
|
}
|
||||||
|
Write-Host 'OK: sample 303 ErrorCode 1226 (standard RD Gateway disconnect).'
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
2.0.13-SAC
|
2.0.14-SAC
|
||||||
|
|||||||
Reference in New Issue
Block a user