Set-StrictMode -Version Latest
|
$ErrorActionPreference = 'Stop'
|
|
function New-AnaSemiTypedObject {
|
param([string]$TypeName, [System.Collections.Specialized.OrderedDictionary]$Properties)
|
$value = [pscustomobject]$Properties
|
$value.PSObject.TypeNames.Insert(0, $TypeName)
|
return $value
|
}
|
|
function Get-AnaSemiSha256Hex {
|
param([byte[]]$Bytes)
|
$sha = [System.Security.Cryptography.SHA256]::Create()
|
try { return ([BitConverter]::ToString($sha.ComputeHash($Bytes))).Replace('-', '').ToLowerInvariant() }
|
finally { $sha.Dispose() }
|
}
|
|
function ConvertTo-AnaSemiAuthorizationWindow {
|
[CmdletBinding()]
|
param(
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$AuthorizedAtRaw,
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ExpiresAtRaw
|
)
|
$authorizedTicks = $null
|
$expiresTicks = $null
|
$status = 'STOP'
|
$stop = $null
|
$format = "yyyy-MM-dd'T'HH:mm:ss.fffffffK"
|
try {
|
$authorized = [DateTimeOffset]::ParseExact($AuthorizedAtRaw, $format, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::None)
|
$authorizedTicks = [int64]$authorized.UtcDateTime.Ticks
|
} catch { $stop = 'STOP_AUTHORIZED_AT_PARSE' }
|
if ($null -eq $stop) {
|
try {
|
$expires = [DateTimeOffset]::ParseExact($ExpiresAtRaw, $format, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::None)
|
$expiresTicks = [int64]$expires.UtcDateTime.Ticks
|
} catch { $stop = 'STOP_EXPIRES_AT_PARSE' }
|
}
|
if (($null -eq $stop) -and ($expiresTicks -le $authorizedTicks)) { $stop = 'STOP_NON_POSITIVE_WINDOW' }
|
if ($null -eq $stop) { $status = 'PASS' }
|
return New-AnaSemiTypedObject 'ANA.SEMI.AUTHORIZATION.WINDOW.V001' ([ordered]@{
|
authorized_at_raw = $AuthorizedAtRaw
|
authorized_at_utc_ticks = $authorizedTicks
|
expires_at_raw = $ExpiresAtRaw
|
expires_at_utc_ticks = $expiresTicks
|
schema_id = 'ANA.SEMI.AUTHORIZATION.WINDOW.V001'
|
status = $status
|
stop_code = $stop
|
})
|
}
|
|
function Test-AnaSemiAuthorizationWindow {
|
[CmdletBinding()]
|
param(
|
[Parameter(Mandatory)][psobject]$WindowResult,
|
[Parameter(Mandatory)][DateTimeOffset]$Now
|
)
|
$a = $null; $e = $null; $n = $null; $inside = $null; $status = 'STOP'; $stop = $null
|
$names = @('authorized_at_raw','authorized_at_utc_ticks','expires_at_raw','expires_at_utc_ticks','schema_id','status','stop_code')
|
try {
|
$actualNames = @($WindowResult.PSObject.Properties.Name)
|
if (($WindowResult.PSTypeNames[0] -cne 'ANA.SEMI.AUTHORIZATION.WINDOW.V001') -or
|
(($actualNames -join "`n") -cne ($names -join "`n")) -or $WindowResult.status -cne 'PASS' -or
|
$null -eq $WindowResult.authorized_at_utc_ticks -or $null -eq $WindowResult.expires_at_utc_ticks) {
|
throw 'window contract'
|
}
|
$a = [int64]$WindowResult.authorized_at_utc_ticks
|
$e = [int64]$WindowResult.expires_at_utc_ticks
|
} catch { $stop = 'STOP_WINDOW_INPUT_CONTRACT' }
|
if ($null -eq $stop) {
|
try {
|
$n = [int64]$Now.UtcDateTime.Ticks
|
$inside = [bool](($a -le $n) -and ($n -lt $e))
|
$status = 'PASS'
|
} catch { $stop = 'STOP_NOW_CONVERSION' }
|
}
|
return New-AnaSemiTypedObject 'ANA.SEMI.AUTHORIZATION.WINDOW.TEST.V001' ([ordered]@{
|
authorized_at_utc_ticks = $a
|
expires_at_utc_ticks = $e
|
inside_window = $inside
|
now_utc_ticks = $n
|
schema_id = 'ANA.SEMI.AUTHORIZATION.WINDOW.TEST.V001'
|
status = $status
|
stop_code = $stop
|
})
|
}
|
|
function Select-AnaSemiExactRelatedProcess {
|
[CmdletBinding()]
|
param(
|
[Parameter(Mandatory)][AllowEmptyCollection()][object[]]$SnapshotRows,
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ExpectedOuterExecutablePath,
|
[Parameter(Mandatory)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedOuterCommandLineSha256,
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ExpectedOuterCommandLineBase64,
|
[Parameter(Mandatory)][uint32]$SourcePreflightProcessId,
|
[Parameter(Mandatory)][AllowEmptyCollection()][string[]]$ProhibitedExecutablePaths
|
)
|
$confirmed = $null; $dup = $null; $exact = $null; $matched = $null; $prohibited = $null
|
$related = $null; $rowCount = $null; $status = 'STOP'; $stop = $null; $uncertain = $null; $unknown = $null
|
try {
|
$bytes = [Convert]::FromBase64String($ExpectedOuterCommandLineBase64)
|
$utf8 = New-Object Text.UTF8Encoding($false, $true)
|
$decoded = $utf8.GetString($bytes)
|
if (-not [Linq.Enumerable]::SequenceEqual[byte]($bytes, $utf8.GetBytes($decoded))) { throw 'roundtrip' }
|
if ((Get-AnaSemiSha256Hex $bytes) -cne $ExpectedOuterCommandLineSha256) { throw 'hash' }
|
if (-not $decoded.StartsWith($ExpectedOuterExecutablePath + ' ', [StringComparison]::Ordinal)) { throw 'coordinate' }
|
} catch {
|
$uncertain = [string[]]@('expected_command_line')
|
$stop = 'STOP_EXPECTED_COMMANDLINE_CONTRACT'
|
}
|
if ($null -eq $stop) {
|
$rowCount = [int]$SnapshotRows.Count
|
$ids = @{}
|
$dupCount = 0
|
foreach ($row in $SnapshotRows) {
|
try { $pidValue = [uint32]$row.process_id } catch { $uncertain = [string[]]@('process_id'); $stop='STOP_SNAPSHOT_CONTRACT'; break }
|
if ($ids.ContainsKey($pidValue)) { $dupCount++ } else { $ids[$pidValue] = $true }
|
}
|
$dup = [int]$dupCount
|
if (($null -eq $stop) -and $dupCount -gt 0) { $stop='STOP_DUPLICATE_PROCESS_ID' }
|
}
|
if ($null -eq $stop) {
|
$exactIds = [Collections.Generic.List[uint32]]::new()
|
$prohibitedIds = [Collections.Generic.List[uint32]]::new()
|
$unknownFields = [Collections.Generic.List[string]]::new()
|
foreach ($row in $SnapshotRows) {
|
$pidValue = [uint32]$row.process_id
|
if ($pidValue -eq $SourcePreflightProcessId) { continue }
|
$path = $row.executable_path; $cmd = $row.command_line; $name = $row.name
|
$isExpectedPath = ($null -ne $path -and [string]::Equals([string]$path,$ExpectedOuterExecutablePath,[StringComparison]::OrdinalIgnoreCase))
|
$isProhibitedPath = $false
|
foreach ($p in $ProhibitedExecutablePaths) {
|
if ($null -ne $path -and [string]::Equals([string]$path,$p,[StringComparison]::OrdinalIgnoreCase)) { $isProhibitedPath=$true; break }
|
}
|
$looksRelevant = $isExpectedPath -or $isProhibitedPath -or (($null -ne $name) -and ([string]$name -match '^(powershell|csc|mysql|mysql_config_editor)(\.exe)?$'))
|
if ($looksRelevant -and ($null -eq $path -or $null -eq $name -or ($isExpectedPath -and $null -eq $cmd))) {
|
[void]$unknownFields.Add(('pid:{0}' -f $pidValue)); continue
|
}
|
if ($isExpectedPath -and [string]::Equals([string]$cmd,$decoded,[StringComparison]::Ordinal)) { [void]$exactIds.Add($pidValue) }
|
if ($isProhibitedPath) { [void]$prohibitedIds.Add($pidValue) }
|
}
|
$exact = [int]$exactIds.Count; $prohibited = [int]$prohibitedIds.Count; $unknown = [int]$unknownFields.Count
|
$allIds = @($exactIds.ToArray() + $prohibitedIds.ToArray() | Sort-Object -Unique)
|
$matched = [uint32[]]$allIds
|
$related = [int]$allIds.Count
|
if ($unknown -gt 0) { $uncertain=[string[]]@($unknownFields.ToArray() | Sort-Object); $stop='STOP_RELEVANT_PROCESS_FIELD_UNKNOWN' }
|
elseif ($exact -gt 1) { $stop='STOP_MULTIPLE_EXACT_OUTER'; $confirmed=$false }
|
elseif ($exact -eq 1 -or $prohibited -gt 0) { $stop='STOP_PROCESS_PRESENT'; $confirmed=$false }
|
else { $confirmed=$true; $status='PASS'; $stop=$null; $uncertain=$null; $matched=[uint32[]]@(); $related=0 }
|
}
|
return New-AnaSemiTypedObject 'ANA.SEMI.PROCESS.SELECTOR.RESULT.V001' ([ordered]@{
|
confirmed_zero = $confirmed
|
duplicate_process_id_count = $dup
|
exact_outer_count = $exact
|
matched_process_ids = $matched
|
prohibited_process_count = $prohibited
|
related_process_count = $related
|
schema_id = 'ANA.SEMI.PROCESS.SELECTOR.RESULT.V001'
|
snapshot_row_count = $rowCount
|
status = $status
|
stop_code = $stop
|
uncertain_fields = $uncertain
|
unknown_relevant_count = $unknown
|
})
|
}
|
|
function New-AnaSemiPreflightResult {
|
param([hashtable]$Values)
|
$keys = @('authorization_id','authorized_at_utc_ticks','authorized_outer_started','candidate_run_exists','credential_access','database_connection_performed','exception_type','expires_at_utc_ticks','finished_at','history_hash_mismatch_count','invocation_count','materialized_target_count','network_access','now_utc_ticks','preflight_stage','related_process_count','result_schema_id','started_at','status','stop_code','target_existing_count','uncertain_field')
|
$o = [ordered]@{}
|
foreach ($key in $keys) { $o[$key] = if ($Values.ContainsKey($key)) { $Values[$key] } else { $null } }
|
return New-AnaSemiTypedObject 'ANA.SEMI.PRECONSUMPTION.RESULT.V002' $o
|
}
|
|
function Test-AnaSemiProviderObject {
|
param([object]$Value,[string]$TypeName,[string[]]$PropertyNames)
|
$items = @($Value)
|
if ($items.Count -ne 1 -or $null -eq $items[0]) { return $false }
|
$item = $items[0]
|
if ($item.PSTypeNames[0] -cne $TypeName) { return $false }
|
return ((@($item.PSObject.Properties.Name) -join "`n") -ceq ($PropertyNames -join "`n"))
|
}
|
|
function Invoke-AnaSemiPreconsumptionPreflight {
|
[CmdletBinding()]
|
param(
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$AuthorizationId,
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$AuthorizedAtRaw,
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ExpiresAtRaw,
|
[Parameter(Mandatory)][ValidateRange(0,[long]::MaxValue)][int64]$ExpectedPlanBytes,
|
[Parameter(Mandatory)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedPlanSha256,
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ExpectedOuterExecutablePath,
|
[Parameter(Mandatory)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedOuterCommandLineSha256,
|
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$ExpectedOuterCommandLineBase64,
|
[Parameter(Mandatory)][uint32]$SourcePreflightProcessId,
|
[Parameter(Mandatory)][AllowEmptyCollection()][string[]]$ProhibitedExecutablePaths,
|
[Parameter(Mandatory)][System.Threading.SemaphoreSlim]$InvocationGate,
|
[Parameter(Mandatory)][scriptblock]$NowProvider,
|
[Parameter(Mandatory)][scriptblock]$IdentityProvider,
|
[Parameter(Mandatory)][scriptblock]$PlanHashProvider,
|
[Parameter(Mandatory)][scriptblock]$TargetStateProvider,
|
[Parameter(Mandatory)][scriptblock]$HistoryProvider,
|
[Parameter(Mandatory)][scriptblock]$ProcessSnapshotProvider
|
)
|
$owned = $InvocationGate.Wait(0)
|
if (-not $owned) {
|
return New-AnaSemiPreflightResult @{authorization_id=$AuthorizationId;preflight_stage='START';result_schema_id='ANA.SEMI.PRECONSUMPTION.RESULT.V002';status='STOP';stop_code='STOP_PRECONSUMPTION_ALREADY_INVOKED'}
|
}
|
$v=@{authorization_id=$AuthorizationId;authorized_outer_started=$false;credential_access=$false;database_connection_performed=$false;invocation_count=[int]1;network_access=$false;preflight_stage='START';result_schema_id='ANA.SEMI.PRECONSUMPTION.RESULT.V002';status='STOP'}
|
try {
|
$v.preflight_stage='IDENTITY'
|
try { $identityOut = @(& $IdentityProvider) } catch { $v.exception_type=$_.Exception.GetType().FullName; $v.stop_code='STOP_PRECONSUMPTION_PREFLIGHT_EXCEPTION'; return New-AnaSemiPreflightResult $v }
|
if (-not (Test-AnaSemiProviderObject $identityOut 'ANA.SEMI.PREFLIGHT.IDENTITY.V001' @('cwd','host','role_instance_id','schema_id','source_thread_id','status','uncertain_fields'))) { $v.stop_code='STOP_PRECONSUMPTION_RESULT_CONTRACT_DRIFT'; return New-AnaSemiPreflightResult $v }
|
$identity=$identityOut[0]
|
if ($identity.status -eq 'UNKNOWN') { $v.uncertain_field=(@($identity.uncertain_fields)-join ',');$v.stop_code='STOP_PRECONSUMPTION_STATE_UNCERTAIN';return New-AnaSemiPreflightResult $v }
|
if ($identity.status -ne 'PASS') { $v.stop_code='STOP_PRECONSUMPTION_GATE_MISMATCH';return New-AnaSemiPreflightResult $v }
|
|
$v.preflight_stage='WINDOW_PARSE'
|
$window=ConvertTo-AnaSemiAuthorizationWindow -AuthorizedAtRaw $AuthorizedAtRaw -ExpiresAtRaw $ExpiresAtRaw
|
$v.authorized_at_utc_ticks=$window.authorized_at_utc_ticks;$v.expires_at_utc_ticks=$window.expires_at_utc_ticks
|
if ($window.status -ne 'PASS') {$v.stop_code='STOP_PRECONSUMPTION_GATE_MISMATCH';return New-AnaSemiPreflightResult $v}
|
|
$v.preflight_stage='CLOCK'
|
try { $nowOut=@(& $NowProvider) } catch {$v.exception_type=$_.Exception.GetType().FullName;$v.stop_code='STOP_PRECONSUMPTION_PREFLIGHT_EXCEPTION';return New-AnaSemiPreflightResult $v}
|
if ($nowOut.Count -ne 1 -or $nowOut[0] -isnot [DateTimeOffset]) {$v.stop_code='STOP_PRECONSUMPTION_RESULT_CONTRACT_DRIFT';return New-AnaSemiPreflightResult $v}
|
$now=[DateTimeOffset]$nowOut[0];$stamp=$now.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fffffffK",[Globalization.CultureInfo]::InvariantCulture)
|
$v.now_utc_ticks=[int64]$now.UtcDateTime.Ticks;$v.started_at=$stamp;$v.finished_at=$stamp
|
$v.preflight_stage='WINDOW_TEST';$windowTest=Test-AnaSemiAuthorizationWindow -WindowResult $window -Now $now
|
if ($windowTest.status -ne 'PASS' -or -not $windowTest.inside_window) {$v.stop_code='STOP_PRECONSUMPTION_GATE_MISMATCH';return New-AnaSemiPreflightResult $v}
|
|
$v.preflight_stage='PLAN_HASH'
|
try {$planOut=@(& $PlanHashProvider)} catch {$v.exception_type=$_.Exception.GetType().FullName;$v.stop_code='STOP_PRECONSUMPTION_PREFLIGHT_EXCEPTION';return New-AnaSemiPreflightResult $v}
|
if (-not(Test-AnaSemiProviderObject $planOut 'ANA.SEMI.PREFLIGHT.PLANHASH.V001' @('bytes','schema_id','sha256','status','uncertain_fields'))) {$v.stop_code='STOP_PRECONSUMPTION_RESULT_CONTRACT_DRIFT';return New-AnaSemiPreflightResult $v}
|
$plan=$planOut[0];if($plan.status -eq 'UNKNOWN'){$v.uncertain_field=(@($plan.uncertain_fields)-join ',');$v.stop_code='STOP_PRECONSUMPTION_STATE_UNCERTAIN';return New-AnaSemiPreflightResult $v}
|
if($plan.status -ne 'PASS' -or $plan.bytes -ne $ExpectedPlanBytes -or $plan.sha256 -cne $ExpectedPlanSha256){$v.stop_code='STOP_PRECONSUMPTION_GATE_MISMATCH';return New-AnaSemiPreflightResult $v}
|
|
$v.preflight_stage='TARGET_STATE'
|
try {$targetOut=@(& $TargetStateProvider)} catch {$v.exception_type=$_.Exception.GetType().FullName;$v.stop_code='STOP_PRECONSUMPTION_PREFLIGHT_EXCEPTION';return New-AnaSemiPreflightResult $v}
|
if(-not(Test-AnaSemiProviderObject $targetOut 'ANA.SEMI.PREFLIGHT.TARGETSTATE.V001' @('candidate_run_exists','materialized_target_count','schema_id','status','target_existing_count','uncertain_fields'))){$v.stop_code='STOP_PRECONSUMPTION_RESULT_CONTRACT_DRIFT';return New-AnaSemiPreflightResult $v}
|
$target=$targetOut[0];$v.candidate_run_exists=$target.candidate_run_exists;$v.materialized_target_count=$target.materialized_target_count;$v.target_existing_count=$target.target_existing_count
|
if($target.status -eq 'UNKNOWN'){$v.uncertain_field=(@($target.uncertain_fields)-join ',');$v.stop_code='STOP_PRECONSUMPTION_STATE_UNCERTAIN';return New-AnaSemiPreflightResult $v}
|
if($target.status -ne 'PASS'){$v.stop_code='STOP_PRECONSUMPTION_GATE_MISMATCH';return New-AnaSemiPreflightResult $v}
|
|
$v.preflight_stage='HISTORY'
|
try {$historyOut=@(& $HistoryProvider)} catch {$v.exception_type=$_.Exception.GetType().FullName;$v.stop_code='STOP_PRECONSUMPTION_PREFLIGHT_EXCEPTION';return New-AnaSemiPreflightResult $v}
|
if(-not(Test-AnaSemiProviderObject $historyOut 'ANA.SEMI.PREFLIGHT.HISTORY.V001' @('history_hash_mismatch_count','schema_id','status','uncertain_fields'))){$v.stop_code='STOP_PRECONSUMPTION_RESULT_CONTRACT_DRIFT';return New-AnaSemiPreflightResult $v}
|
$history=$historyOut[0];$v.history_hash_mismatch_count=$history.history_hash_mismatch_count
|
if($history.status -eq 'UNKNOWN'){$v.uncertain_field=(@($history.uncertain_fields)-join ',');$v.stop_code='STOP_PRECONSUMPTION_STATE_UNCERTAIN';return New-AnaSemiPreflightResult $v}
|
if($history.status -ne 'PASS'){$v.stop_code='STOP_PRECONSUMPTION_GATE_MISMATCH';return New-AnaSemiPreflightResult $v}
|
|
$v.preflight_stage='PROCESS_SNAPSHOT'
|
try {$snapOut=@(& $ProcessSnapshotProvider)} catch {$v.exception_type=$_.Exception.GetType().FullName;$v.stop_code='STOP_PRECONSUMPTION_PREFLIGHT_EXCEPTION';return New-AnaSemiPreflightResult $v}
|
if(-not(Test-AnaSemiProviderObject $snapOut 'ANA.SEMI.PREFLIGHT.SNAPSHOT.V001' @('adapter_status','rows','schema_id','snapshot_id','uncertain_fields'))){$v.stop_code='STOP_PRECONSUMPTION_RESULT_CONTRACT_DRIFT';return New-AnaSemiPreflightResult $v}
|
$snap=$snapOut[0];if($snap.adapter_status -ne 'PASS'){$v.uncertain_field=(@($snap.uncertain_fields)-join ',');$v.stop_code='STOP_PRECONSUMPTION_STATE_UNCERTAIN';return New-AnaSemiPreflightResult $v}
|
$v.preflight_stage='PROCESS_SELECT'
|
$selection=Select-AnaSemiExactRelatedProcess -SnapshotRows @($snap.rows) -ExpectedOuterExecutablePath $ExpectedOuterExecutablePath -ExpectedOuterCommandLineSha256 $ExpectedOuterCommandLineSha256 -ExpectedOuterCommandLineBase64 $ExpectedOuterCommandLineBase64 -SourcePreflightProcessId $SourcePreflightProcessId -ProhibitedExecutablePaths $ProhibitedExecutablePaths
|
$v.related_process_count=$selection.related_process_count
|
if($selection.status -ne 'PASS'){$v.uncertain_field=(@($selection.uncertain_fields)-join ',');$v.stop_code=if($selection.uncertain_fields){'STOP_PRECONSUMPTION_STATE_UNCERTAIN'}else{'STOP_PRECONSUMPTION_GATE_MISMATCH'};return New-AnaSemiPreflightResult $v}
|
$v.preflight_stage='PASS';$v.status='PASS';$v.stop_code=$null
|
return New-AnaSemiPreflightResult $v
|
} catch {
|
$v.exception_type=$_.Exception.GetType().FullName;$v.stop_code='STOP_PRECONSUMPTION_PREFLIGHT_EXCEPTION'
|
return New-AnaSemiPreflightResult $v
|
}
|
}
|
|
Export-ModuleMember -Function ConvertTo-AnaSemiAuthorizationWindow,Test-AnaSemiAuthorizationWindow,Select-AnaSemiExactRelatedProcess,Invoke-AnaSemiPreconsumptionPreflight
|