88 lines
5.2 KiB
PowerShell
88 lines
5.2 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$DockerContext = 'docker-prod',
|
|
[string]$BaseURL = 'https://assistent.cin.su',
|
|
[string]$CoreContainer = 'assistant-core-runtime-1',
|
|
[string]$PostgresContainer = 'assistant-postgres-1',
|
|
[string]$CognitiveContainer = 'assistant-cognitive-worker-1'
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version Latest
|
|
|
|
. "$PSScriptRoot/cognitive_worker_env_policy.ps1"
|
|
|
|
function Get-ContainerEnvironment([string]$Container) {
|
|
$result = @{}
|
|
$inspect = ((& docker.exe --context $DockerContext inspect $Container) -join "`n") | ConvertFrom-Json
|
|
foreach ($entry in $inspect[0].Config.Env) {
|
|
$parts = $entry -split '=', 2
|
|
$result[$parts[0]] = if ($parts.Count -eq 2) { $parts[1] } else { '' }
|
|
}
|
|
return $result
|
|
}
|
|
|
|
function Invoke-StatusRequest([string]$Uri, [hashtable]$Headers, [string]$Body) {
|
|
$parameters = @{
|
|
Uri = $Uri
|
|
Method = 'Post'
|
|
ContentType = 'application/json'
|
|
SkipHttpErrorCheck = $true
|
|
}
|
|
if ($Headers.Count -gt 0) { $parameters.Headers = $Headers }
|
|
if ($Body) { $parameters.Body = $Body }
|
|
return Invoke-WebRequest @parameters
|
|
}
|
|
|
|
$health = Invoke-RestMethod -Uri "$BaseURL/healthz" -Method Get
|
|
if ($health.status -ne 'ok' -or $health.runtime -ne 'creator-web') { throw 'Creator Web health is invalid' }
|
|
$page = Invoke-WebRequest -Uri "$BaseURL/" -Method Get
|
|
if ($page.StatusCode -ne 200 -or $page.Content -notmatch 'Спросить Домового') { throw 'Advisory console is not exposed' }
|
|
|
|
$coreEnvironment = Get-ContainerEnvironment $CoreContainer
|
|
$required = @('CREATOR_USERNAME', 'CREATOR_PASSWORD_FILE', 'POSTGRES_DATABASE', 'POSTGRES_USER', 'POSTGRES_PASSWORD')
|
|
foreach ($name in $required) {
|
|
if (-not $coreEnvironment.ContainsKey($name) -or [string]::IsNullOrWhiteSpace($coreEnvironment[$name])) {
|
|
throw "Core runtime is missing $name"
|
|
}
|
|
}
|
|
|
|
$creatorPassword = ((& docker.exe --context $DockerContext exec $CoreContainer sh -c "cat '$($coreEnvironment['CREATOR_PASSWORD_FILE'])'") -join "`n").Trim()
|
|
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($creatorPassword)) { throw 'Creator password secret is unavailable' }
|
|
$sessionRequest = @{username = $coreEnvironment['CREATOR_USERNAME']; password = $creatorPassword} | ConvertTo-Json -Compress
|
|
$issued = Invoke-RestMethod -Uri "$BaseURL/api/session" -Method Post -ContentType 'application/json' -Body $sessionRequest
|
|
$creatorPassword = $null
|
|
if (-not $issued.Token) { throw 'Creator session was not issued' }
|
|
$authorization = @{Authorization = "Bearer $($issued.Token)"}
|
|
|
|
$focusSql = "select session_id,workspace_id from situation.focus_bindings where active=true and actor_id='domovoy-creator-actor' order by session_id desc limit 1"
|
|
$focusLine = ((& docker.exe --context $DockerContext exec --env "PGPASSWORD=$($coreEnvironment['POSTGRES_PASSWORD'])" $PostgresContainer psql -U $coreEnvironment['POSTGRES_USER'] -d $coreEnvironment['POSTGRES_DATABASE'] -AtF '|' -c $focusSql) -join '').Trim()
|
|
if ($LASTEXITCODE -ne 0 -or $focusLine -notmatch '^[-a-zA-Z0-9._:]+\|[-a-zA-Z0-9._:]+$') { throw 'Active Creator focus is unavailable' }
|
|
$focus = $focusLine -split '\|', 2
|
|
|
|
$sentinelPrefix = "audit-$([guid]::NewGuid().ToString('N'))-"
|
|
$message = $sentinelPrefix + ('a' * ((16 * 1024) - $sentinelPrefix.Length))
|
|
$advisoryBody = @{message = $message; session_id = $focus[0]; workspace_id = $focus[1]} | ConvertTo-Json -Compress
|
|
$advisory = Invoke-RestMethod -Uri "$BaseURL/api/advisory" -Method Post -ContentType 'application/json' -Headers $authorization -Body $advisoryBody
|
|
if ($advisory.Version -ne 'creator-advisory-v1' -or $advisory.FinishReason -ne 'PROVIDER_UNAVAILABLE') { throw 'Advisory did not fail closed' }
|
|
if (-not [string]::IsNullOrEmpty([string]$advisory.AdvisoryText) -or ($null -ne $advisory.ReferencedExcerptIDs -and @($advisory.ReferencedExcerptIDs).Count -ne 0)) { throw 'Unavailable advisory contained generated content or citations' }
|
|
|
|
$unauthenticated = Invoke-StatusRequest "$BaseURL/api/advisory" @{} $advisoryBody
|
|
if ($unauthenticated.StatusCode -ne 401) { throw "Unauthenticated advisory status=$($unauthenticated.StatusCode)" }
|
|
$injectedBody = @{message = 'authority-injection'; session_id = $focus[0]; workspace_id = $focus[1]; ActorID = 'attacker'} | ConvertTo-Json -Compress
|
|
$injected = Invoke-StatusRequest "$BaseURL/api/advisory" $authorization $injectedBody
|
|
if ($injected.StatusCode -ne 400) { throw "Authority-field injection status=$($injected.StatusCode)" }
|
|
|
|
$cognitiveInspect = ((& docker.exe --context $DockerContext inspect $CognitiveContainer) -join "`n") | ConvertFrom-Json
|
|
Test-CognitiveWorkerEnvironmentPolicy -Environment @($cognitiveInspect[0].Config.Env)
|
|
$forbiddenMounts = @($cognitiveInspect[0].Mounts | Where-Object { $_.Destination -match '(?i)(secret|credential|token|postgres|docker)' })
|
|
if ($forbiddenMounts.Count -ne 0) { throw 'Cognitive worker has a forbidden credential or infrastructure mount' }
|
|
$recentLogs = ((& docker.exe --context $DockerContext logs --since 5m $CognitiveContainer 2>&1) -join "`n")
|
|
if ($recentLogs.Contains($sentinelPrefix)) { throw 'Advisory content leaked into cognitive-worker logs' }
|
|
|
|
$issued.Token = $null
|
|
$authorization.Clear()
|
|
$message = $null
|
|
$advisoryBody = $null
|
|
Write-Output 'DEV-000049 production Creator advisory boundary audit: PASS'
|