fix: SAC POST не срезать '{' из-за StartsWith(BOM) в PowerShell (1.2.13-SAC)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -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.12-SAC"
|
$ScriptVersion = "1.2.13-SAC"
|
||||||
|
|
||||||
# Логи (все под InstallRoot)
|
# Логи (все под InstallRoot)
|
||||||
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
|
||||||
|
|||||||
+79
-16
@@ -4,7 +4,7 @@
|
|||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Dot-source после login_monitor.settings.ps1 и функции Write-Log.
|
Dot-source после login_monitor.settings.ps1 и функции Write-Log.
|
||||||
Ожидает: $UseSAC, $SacUrl, $SacApiKey, $ScriptVersion, $script:InstallRoot.
|
Ожидает: $UseSAC, $SacUrl, $SacApiKey, $ScriptVersion, $script:InstallRoot.
|
||||||
Release: 1.2.12-SAC (fix null+JSON body from pipeline; no ConvertFrom-Json on ingest).
|
Release: 1.2.13-SAC (fix BOM strip: never StartsWith([char]0xFEFF) on PS strings).
|
||||||
#>
|
#>
|
||||||
|
|
||||||
function Test-SacIngestAcceptedStatus {
|
function Test-SacIngestAcceptedStatus {
|
||||||
@@ -27,16 +27,46 @@ function Complete-SacIngestSuccess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-SacUtf8Bytes {
|
function Remove-SacLeadingBomChar {
|
||||||
param([Parameter(Mandatory = $true)][string]$Text)
|
param([string]$Text)
|
||||||
$t = Get-SacSingleString -Value $Text -Label 'request body'
|
$t = $Text
|
||||||
if ($t.StartsWith([char]0xFEFF)) { $t = $t.Substring(1) }
|
# Нельзя $t.StartsWith([char]0xFEFF): в PS нет StartsWith(char), char→string даёт ложные совпадения и срезает '{'.
|
||||||
# PowerShell иногда склеивает $null и JSON в "null{...}" — ломает FastAPI json parser
|
while ($t.Length -gt 0 -and [int][char]$t[0] -eq 0xFEFF) {
|
||||||
|
Write-SacLog 'WARN: SAC stripped U+FEFF BOM character before JSON body'
|
||||||
|
$t = $t.Substring(1)
|
||||||
|
}
|
||||||
|
return $t
|
||||||
|
}
|
||||||
|
|
||||||
|
function Repair-SacJsonText {
|
||||||
|
param([string]$Text)
|
||||||
|
$t = Get-SacSingleString -Value $Text -Label 'json body'
|
||||||
|
$t = Remove-SacLeadingBomChar -Text $t
|
||||||
|
$t = $t.TrimStart()
|
||||||
if ($t.Length -gt 5 -and $t.StartsWith('null', [System.StringComparison]::Ordinal) -and $t[4] -eq '{') {
|
if ($t.Length -gt 5 -and $t.StartsWith('null', [System.StringComparison]::Ordinal) -and $t[4] -eq '{') {
|
||||||
Write-SacLog 'WARN: SAC stripped accidental null prefix before JSON body'
|
Write-SacLog 'WARN: SAC stripped accidental null prefix before JSON body'
|
||||||
$t = $t.Substring(4)
|
$t = $t.Substring(4).TrimStart()
|
||||||
}
|
}
|
||||||
return (New-Object System.Text.UTF8Encoding $false).GetBytes($t)
|
if ($t.Length -gt 0 -and $t[0] -ne '{') {
|
||||||
|
$brace = $t.IndexOf('{')
|
||||||
|
if ($brace -gt 0) {
|
||||||
|
Write-SacLog "WARN: SAC stripped $brace chars before first '{' in JSON body"
|
||||||
|
$t = $t.Substring($brace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $t
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SacUtf8Bytes {
|
||||||
|
param([Parameter(Mandatory = $true)][string]$Text)
|
||||||
|
$t = Repair-SacJsonText -Text $Text
|
||||||
|
$enc = New-Object System.Text.UTF8Encoding $false
|
||||||
|
$bytes = $enc.GetBytes($t)
|
||||||
|
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
|
||||||
|
Write-SacLog 'WARN: SAC stripped UTF-8 BOM bytes before JSON body'
|
||||||
|
$bytes = $bytes[3..($bytes.Length - 1)]
|
||||||
|
}
|
||||||
|
return $bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
function Write-SacLog {
|
function Write-SacLog {
|
||||||
@@ -113,18 +143,26 @@ function Get-SacAgentInstanceId {
|
|||||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||||
}
|
}
|
||||||
if (Test-Path -LiteralPath $idFile) {
|
if (Test-Path -LiteralPath $idFile) {
|
||||||
$existing = (Get-Content -LiteralPath $idFile -TotalCount 1 -ErrorAction SilentlyContinue)
|
$existing = Read-SacAgentIdFileText -Path $idFile
|
||||||
if (-not [string]::IsNullOrWhiteSpace($existing)) {
|
if (-not [string]::IsNullOrWhiteSpace($existing)) {
|
||||||
|
$existing = Remove-SacLeadingBomChar -Text $existing.Trim()
|
||||||
return $existing.Trim()
|
return $existing.Trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$newId = [guid]::NewGuid().ToString()
|
$newId = [guid]::NewGuid().ToString()
|
||||||
try {
|
try {
|
||||||
Set-Content -LiteralPath $idFile -Value $newId -Encoding UTF8 -NoNewline
|
[System.IO.File]::WriteAllText($idFile, $newId, (New-Object System.Text.UTF8Encoding $false))
|
||||||
} catch { }
|
} catch { }
|
||||||
return $newId
|
return $newId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Read-SacAgentIdFileText {
|
||||||
|
param([string]$Path)
|
||||||
|
if (-not (Test-Path -LiteralPath $Path)) { return $null }
|
||||||
|
$enc = New-Object System.Text.UTF8Encoding $false
|
||||||
|
return [System.IO.File]::ReadAllText($Path, $enc).Trim()
|
||||||
|
}
|
||||||
|
|
||||||
function Get-SacOccurredAtIso {
|
function Get-SacOccurredAtIso {
|
||||||
return [DateTimeOffset]::Now.ToString('yyyy-MM-ddTHH:mm:ss.fffK')
|
return [DateTimeOffset]::Now.ToString('yyyy-MM-ddTHH:mm:ss.fffK')
|
||||||
}
|
}
|
||||||
@@ -160,10 +198,10 @@ function ConvertTo-SacJsonText {
|
|||||||
$ser = New-Object System.Web.Script.Serialization.JavaScriptSerializer
|
$ser = New-Object System.Web.Script.Serialization.JavaScriptSerializer
|
||||||
$ser.MaxJsonLength = 16777216
|
$ser.MaxJsonLength = 16777216
|
||||||
$ser.RecursionLimit = 32
|
$ser.RecursionLimit = 32
|
||||||
return $ser.Serialize($serializable)
|
return ,$ser.Serialize($serializable)
|
||||||
} catch {
|
} catch {
|
||||||
Write-SacLog "WARN: JavaScriptSerializer unavailable, fallback ConvertTo-Json ($($_.Exception.Message))"
|
Write-SacLog "WARN: JavaScriptSerializer unavailable, fallback ConvertTo-Json ($($_.Exception.Message))"
|
||||||
return ($serializable | ConvertTo-Json -Depth 12 -Compress)
|
return ,($serializable | ConvertTo-Json -Depth 12 -Compress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +380,29 @@ function Move-SacSpoolToRejected {
|
|||||||
Move-Item -LiteralPath $src -Destination $dst -Force -ErrorAction SilentlyContinue
|
Move-Item -LiteralPath $src -Destination $dst -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Write-SacPostBodyDiagnostic {
|
||||||
|
param(
|
||||||
|
[byte[]]$BodyBytes,
|
||||||
|
[string]$EventId
|
||||||
|
)
|
||||||
|
if ($null -eq $BodyBytes -or $BodyBytes.Length -eq 0) { return }
|
||||||
|
$take = [Math]::Min(32, $BodyBytes.Length)
|
||||||
|
$hex = (($BodyBytes[0..($take - 1)] | ForEach-Object { '{0:X2}' -f $_ }) -join ' ')
|
||||||
|
Write-SacLog "WARN: SAC POST body prefix ($take bytes hex): $hex"
|
||||||
|
if ([string]::IsNullOrWhiteSpace($script:InstallRoot)) { return }
|
||||||
|
try {
|
||||||
|
$logDir = Join-Path $script:InstallRoot 'Logs'
|
||||||
|
if (-not (Test-Path -LiteralPath $logDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
$path = Join-Path $logDir 'sac-last-post.json'
|
||||||
|
[System.IO.File]::WriteAllBytes($path, $BodyBytes)
|
||||||
|
Write-SacLog "WARN: SAC saved last POST body to $path (event_id=$EventId)"
|
||||||
|
} catch {
|
||||||
|
Write-SacLog "WARN: SAC could not save sac-last-post.json: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Invoke-SacPostPayload {
|
function Invoke-SacPostPayload {
|
||||||
param([string]$JsonBody)
|
param([string]$JsonBody)
|
||||||
|
|
||||||
@@ -351,7 +412,7 @@ function Invoke-SacPostPayload {
|
|||||||
$ingest = Get-SacIngestUrl
|
$ingest = Get-SacIngestUrl
|
||||||
if ([string]::IsNullOrWhiteSpace($ingest)) { return $false }
|
if ([string]::IsNullOrWhiteSpace($ingest)) { return $false }
|
||||||
|
|
||||||
$jsonText = Get-SacSingleString -Value $JsonBody -Label 'spool payload'
|
$jsonText = Repair-SacJsonText -Text (Get-SacSingleString -Value $JsonBody -Label 'spool payload')
|
||||||
if ($jsonText -notmatch '"event_id"\s*:\s*"([0-9a-fA-F-]{36})"') {
|
if ($jsonText -notmatch '"event_id"\s*:\s*"([0-9a-fA-F-]{36})"') {
|
||||||
Write-SacLog 'WARN: SAC JSON has no event_id (uuid); skip POST'
|
Write-SacLog 'WARN: SAC JSON has no event_id (uuid); skip POST'
|
||||||
return $false
|
return $false
|
||||||
@@ -381,6 +442,7 @@ function Invoke-SacPostPayload {
|
|||||||
if ($resp.StatusCode -eq 422) {
|
if ($resp.StatusCode -eq 422) {
|
||||||
$spoolOnFailure = $false
|
$spoolOnFailure = $false
|
||||||
Write-SacLog "WARN: SAC POST HTTP 422 validation (not spooled) type=$eventType event_id=$eventId"
|
Write-SacLog "WARN: SAC POST HTTP 422 validation (not spooled) type=$eventType event_id=$eventId"
|
||||||
|
Write-SacPostBodyDiagnostic -BodyBytes $bodyBytes -EventId $eventId
|
||||||
}
|
}
|
||||||
if ($body.Length -gt 0) {
|
if ($body.Length -gt 0) {
|
||||||
$snippet = $body.Substring(0, [Math]::Min(800, $body.Length))
|
$snippet = $body.Substring(0, [Math]::Min(800, $body.Length))
|
||||||
@@ -401,6 +463,7 @@ function Invoke-SacPostPayload {
|
|||||||
if ($code -eq 422) {
|
if ($code -eq 422) {
|
||||||
$spoolOnFailure = $false
|
$spoolOnFailure = $false
|
||||||
Write-SacLog "WARN: SAC POST HTTP 422 validation (not spooled) type=$eventType event_id=$eventId"
|
Write-SacLog "WARN: SAC POST HTTP 422 validation (not spooled) type=$eventType event_id=$eventId"
|
||||||
|
Write-SacPostBodyDiagnostic -BodyBytes $bodyBytes -EventId $eventId
|
||||||
}
|
}
|
||||||
$err = $_.Exception.Message
|
$err = $_.Exception.Message
|
||||||
if ($body.Length -gt 0) {
|
if ($body.Length -gt 0) {
|
||||||
@@ -440,12 +503,12 @@ function Send-SacEvent {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
$payload = New-SacEventPayload -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $Details
|
$payload = $(New-SacEventPayload -EventType $EventType -Severity $Severity -Title $Title -Summary $Summary -Details $Details)
|
||||||
if ($payload -is [System.Array]) {
|
if ($payload -is [System.Array]) {
|
||||||
$payload = $payload[-1]
|
$payload = $payload[-1]
|
||||||
}
|
}
|
||||||
$json = ConvertTo-SacJsonText -Payload $payload
|
$json = $(ConvertTo-SacJsonText -Payload $payload)
|
||||||
$json = Get-SacSingleString -Value $json -Label 'event json'
|
$json = Repair-SacJsonText -Text (Get-SacSingleString -Value $json -Label 'event json')
|
||||||
if ([string]::IsNullOrWhiteSpace($json) -or $json[0] -ne '{') {
|
if ([string]::IsNullOrWhiteSpace($json) -or $json[0] -ne '{') {
|
||||||
Write-SacLog "WARN: SAC invalid JSON for type=$EventType (empty or does not start with {{)"
|
Write-SacLog "WARN: SAC invalid JSON for type=$EventType (empty or does not start with {{)"
|
||||||
return $false
|
return $false
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.2.12-SAC
|
1.2.13-SAC
|
||||||
|
|||||||
Reference in New Issue
Block a user