82 lines
6.4 KiB
PowerShell
82 lines
6.4 KiB
PowerShell
$ErrorActionPreference='Stop'
|
|
. (Join-Path $PSScriptRoot 'Catalog.ps1')
|
|
$script:Checks=0
|
|
function Assert-Panel($Condition,[string]$Message) { if (-not $Condition) { throw $Message }; $script:Checks++ }
|
|
function Expect-Rejection($Action,[string]$Message) {
|
|
$rejected=$false
|
|
try { & $Action | Out-Null } catch { $rejected=$true }
|
|
Assert-Panel $rejected $Message
|
|
}
|
|
function New-Fixture([string]$Role,[int]$Port,[string]$Address='192.0.2.10') {
|
|
return [pscustomobject]@{Role=$Role; Label=('Server '+$Role); HostName=$Address; UserName='operator'; PortNumber=64646; LocalPort=$Port; RemotePort=2053; Fingerprint=('SHA256:'+('A'*43)); PanelPath='/private-panel/'}
|
|
}
|
|
$ru=New-Fixture 'RU' 18053
|
|
$de=New-Fixture 'DE' 18054 'server.example.org'
|
|
$dk=New-Fixture 'DK' 18055 '2001:db8::10'
|
|
$catalog=ConvertTo-PanelCatalog ([pscustomobject]@{Sessions=@($ru,$de,$dk)})
|
|
Assert-Panel ($catalog.Sessions.Count -eq 3) 'Three initial servers must load.'
|
|
$chosen=@(Select-PanelSessions $catalog @('KAgent-DK-panel'))
|
|
Assert-Panel ($chosen.Count -eq 1 -and $chosen[0].Role -ceq 'DK') 'Installing DK must select only DK.'
|
|
$chosen=@(Select-PanelSessions $catalog @('KAgent-DE-panel','KAgent-RU-panel'))
|
|
Assert-Panel (($chosen.Role -join ',') -ceq 'DE,RU') 'Multiple selected servers must be preserved in requested order.'
|
|
Expect-Rejection { Select-PanelSessions $catalog @() } 'Empty selection must not install anything.'
|
|
Expect-Rejection { Select-PanelSessions $catalog @('KAgent-FI-panel') } 'Unknown selection must be rejected.'
|
|
Expect-Rejection { Select-PanelSessions $catalog @('KAgent-DK-panel','KAgent-DK-panel') } 'Duplicate selection must be rejected.'
|
|
$fi=New-Fixture 'FI2' 18056
|
|
$larger=ConvertTo-PanelCatalog ([pscustomobject]@{Sessions=@($catalog.Sessions)+@($fi)})
|
|
Assert-Panel ($larger.Sessions.Count -eq 4 -and $larger.Sessions[3].Role -ceq 'FI2') 'A new country must work without changing installer code.'
|
|
$legacy=[pscustomobject]@{Sessions=@([pscustomobject]@{Role='DE';Name='KAgent-DE-panel';HostName='198.51.100.20';UserName='operator';PortNumber=22;LocalPort=18054;RemotePort=2053;Fingerprint=('SHA256:'+('A'*43))})}
|
|
$normalized=ConvertTo-PanelCatalog $legacy
|
|
Assert-Panel ($normalized.Sessions[0].PanelPath -ceq '/' -and $normalized.Sessions[0].Label -ceq 'DE') 'Legacy RU/DE catalog compatibility failed.'
|
|
$empty=ConvertTo-PanelCatalog ([pscustomobject]@{Sessions=@()})
|
|
Assert-Panel ($empty.Sessions.Count -eq 0) 'Clean install must support an empty catalog.'
|
|
Expect-Rejection { ConvertTo-PanelCatalog ([pscustomobject]@{Sessions=@($ru,$ru)}) } 'Duplicate session must be rejected.'
|
|
$collision=New-Fixture 'OTHER' 18053
|
|
Expect-Rejection { ConvertTo-PanelCatalog ([pscustomobject]@{Sessions=@($ru,$collision)}) } 'Loopback port collision must be rejected.'
|
|
$caseCollision=New-Fixture 'ru' 18056
|
|
Expect-Rejection { ConvertTo-PanelCatalog ([pscustomobject]@{Sessions=@($ru,$caseCollision)}) } 'Case-insensitive Windows session collision must be rejected.'
|
|
Expect-Rejection { ConvertTo-PanelCatalog ([pscustomobject]@{SchemaVersion=2;Sessions=@($ru)}) } 'Unknown schema must be rejected.'
|
|
foreach ($pair in @(
|
|
@('Role','../../owned'), @('Role','DK; Start-Process cmd'), @('Name','Other-session'),
|
|
@('HostName','https://example.org/path'), @('HostName','server -proxycmd cmd'), @('HostName',''),
|
|
@('HostName',"server`r`nPort=1"), @('UserName','user;cmd'), @('PortNumber','0'), @('PortNumber','65536'),
|
|
@('LocalPort','12.5'), @('RemotePort','1;2'), @('Fingerprint',''), @('Fingerprint',('SHA256:'+('A'*43)+' -pw injected')),
|
|
@('PanelPath','//example.org/'), @('PanelPath','/../secret'), @('PanelPath',"/x`r`nInjected: yes"),
|
|
@('PanelPath','/panel?password=secret'), @('Label',"line`nnewline")
|
|
)) {
|
|
$copy=$ru | ConvertTo-Json | ConvertFrom-Json
|
|
$copy | Add-Member -NotePropertyName $pair[0] -NotePropertyValue $pair[1] -Force
|
|
Expect-Rejection { ConvertTo-PanelSession $copy } ('Unsafe input accepted: '+$pair[0])
|
|
}
|
|
$key='C:\Keys & files\my owner.ppk'
|
|
$plan=Get-PanelRegistryPlan $catalog.Sessions[2] $key
|
|
Assert-Panel ($plan.Strings.PublicKeyFile -ceq $key) 'Key path with spaces must remain data.'
|
|
Assert-Panel ($plan.Strings.PortForwardings -ceq 'L127.0.0.1:18055=127.0.0.1:2053') 'Tunnel must bind loopback and target remote loopback.'
|
|
Assert-Panel ($plan.Numbers.LocalPortAcceptAll -eq 0 -and $plan.Numbers.AgentFwd -eq 0 -and $plan.Numbers.ProxyMethod -eq 0) 'Unsafe forwarding/proxy settings.'
|
|
Assert-Panel ($plan.Arguments -ceq ('-load KAgent-DK-panel -N -hostkey SHA256:'+('A'*43))) 'Shortcut must pin the exact host key.'
|
|
Assert-Panel ($plan.PanelUrl -ceq 'http://127.0.0.1:18055/private-panel/') 'Browser URL must stay loopback.'
|
|
Expect-Rejection { Get-PanelRegistryPlan $ru '' } 'Missing key path must be rejected.'
|
|
$temp=Join-Path ([IO.Path]::GetTempPath()) ('panel-catalog-test-'+[Guid]::NewGuid().ToString('N'))
|
|
[IO.Directory]::CreateDirectory($temp) | Out-Null
|
|
try {
|
|
$file=Join-Path $temp 'connections.json'
|
|
Write-PanelCatalog $file $larger
|
|
$read=Read-PanelCatalog $file
|
|
Assert-Panel (($read.Sessions.Role -join ',') -ceq 'RU,DE,DK,FI2') 'Export/import roundtrip lost a server.'
|
|
Write-PanelCatalog $file $catalog
|
|
Assert-Panel ((Read-PanelCatalog $file).Sessions.Count -eq 3) 'Atomic replacement failed.'
|
|
$before=[IO.File]::ReadAllText($file)
|
|
Expect-Rejection { Write-PanelCatalog $file ([pscustomobject]@{Sessions=@($collision,$ru)}) } 'Invalid replacement must fail.'
|
|
Assert-Panel ([IO.File]::ReadAllText($file) -ceq $before) 'Invalid replacement modified the existing catalog.'
|
|
Assert-Panel (@(Get-ChildItem -LiteralPath $temp -Filter '*.tmp').Count -eq 0) 'Temporary catalog leaked.'
|
|
$withSecret=$ru | ConvertTo-Json | ConvertFrom-Json
|
|
$withSecret | Add-Member -NotePropertyName Password -NotePropertyValue 'must-not-be-saved'
|
|
Write-PanelCatalog $file ([pscustomobject]@{Sessions=@($withSecret)})
|
|
Assert-Panel (-not [IO.File]::ReadAllText($file).Contains('must-not-be-saved')) 'Unknown secret field must not be persisted.'
|
|
} finally { Remove-Item -LiteralPath $temp -Recurse -Force }
|
|
foreach ($file in Get-ChildItem -LiteralPath $PSScriptRoot -Filter '*.ps1') {
|
|
$tokens=$null; $parseErrors=$null
|
|
[System.Management.Automation.Language.Parser]::ParseFile($file.FullName,[ref]$tokens,[ref]$parseErrors) | Out-Null
|
|
Assert-Panel ($parseErrors.Count -eq 0) ('PowerShell syntax errors: '+$file.Name)
|
|
}
|
|
Write-Output ('PASS: '+$script:Checks+' catalog, selection, safe-forwarding, host-key and persistence checks.')
|