Files
local-ci-cd-system/scripts/Validate-HostState.ps1
T
Simone 8ca3e530c5 feat(low): sprint 4 Low items tutti 5 completati
codice:
- template/Validate-SetupState.ps1: docblock HTTP/Basic -> HTTPS/Basic, port 5986
- scripts/Validate-HostState.ps1 (nuovo): check ACL SSH key ci_linux, Credential Manager
  target BuildVMGuest, struttura dir F:\CI\, vmrun.exe raggiungibile

docs:
- docs/TEST-PLAN-v1.3-to-HEAD.md sezione 3.6: campi JSONL corretti (cloneSec/startSec/
  ipSec/winrmSec/destroySec/totalBootSec) per riflettere output reale di Measure-CIBenchmark.ps1
- docs/CI-FLOW.md tabella failure: 'partial artifacts' -> Get-GuestDiagnostics a diagnostics/
- docs/CHANGELOG.md (nuovo): log reverse-cronologico di tutti gli sprint completati
- plans/final-master-plan.md: tutti i Low marcati done
2026-05-13 10:26:08 +02:00

191 lines
6.3 KiB
PowerShell

#Requires -Version 5.1
<#
.SYNOPSIS
Validates host-side security prerequisites for the CI system.
.DESCRIPTION
Checks that:
1. The SSH private key for Linux guest access exists and has restrictive ACLs
(only the current user and SYSTEM/Administrators may read it).
2. The Windows Credential Manager target used by CI guest sessions is accessible.
3. The CI key directories exist with expected permissions.
Run after `Setup-Host.ps1` and any time credentials or keys are rotated.
Exits with code 0 on all-pass, non-zero if any check fails.
.PARAMETER SshKeyPath
Path to the SSH private key used for Linux guest access.
Default: F:\CI\keys\ci_linux
.PARAMETER GuestCredTarget
Windows Credential Manager target name for Windows guest credentials.
Default: BuildVMGuest
.PARAMETER CIRootDir
Root CI directory to verify existence.
Default: F:\CI
.EXAMPLE
.\Validate-HostState.ps1
.EXAMPLE
.\Validate-HostState.ps1 -SshKeyPath 'F:\CI\keys\ci_linux' -Verbose
#>
[CmdletBinding()]
param(
[string] $SshKeyPath = 'F:\CI\keys\ci_linux',
[string] $GuestCredTarget = 'BuildVMGuest',
[string] $CIRootDir = 'F:\CI'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# ── Helpers ───────────────────────────────────────────────────────────────────
$script:passCount = 0
$script:failCount = 0
function Write-Check {
param([string]$Label, [scriptblock]$Test)
try {
$result = & $Test
if ($result) {
Write-Host " [OK] $Label"
$script:passCount++
} else {
Write-Host " [FAIL] $Label"
$script:failCount++
}
} catch {
Write-Host " [FAIL] $Label$_"
$script:failCount++
}
}
# ── Check 1: SSH key file exists ──────────────────────────────────────────────
Write-Host "`n=== SSH Key ($SshKeyPath) ==="
Write-Check "SSH private key file exists" {
Test-Path $SshKeyPath -PathType Leaf
}
Write-Check "SSH public key file exists" {
Test-Path "${SshKeyPath}.pub" -PathType Leaf
}
# Check ACLs: only SYSTEM, Administrators, and the current user should have access
Write-Check "SSH key ACL — no unexpected identities" {
if (-not (Test-Path $SshKeyPath -PathType Leaf)) { return $false }
$acl = Get-Acl -Path $SshKeyPath
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
# Allowed identities (case-insensitive prefix matching)
$allowedPatterns = @(
'NT AUTHORITY\SYSTEM',
'BUILTIN\Administrators',
$currentUser
)
$unexpected = @()
foreach ($ace in $acl.Access) {
$identity = $ace.IdentityReference.Value
$matched = $false
foreach ($pattern in $allowedPatterns) {
if ($identity -ieq $pattern) { $matched = $true; break }
}
if (-not $matched) {
$unexpected += $identity
}
}
if ($unexpected.Count -gt 0) {
Write-Verbose "[ACL] Unexpected identities on SSH key: $($unexpected -join ', ')"
return $false
}
return $true
}
Write-Check "SSH key not world-readable (Everyone/Users absent)" {
if (-not (Test-Path $SshKeyPath -PathType Leaf)) { return $false }
$acl = Get-Acl -Path $SshKeyPath
$broad = $acl.Access | Where-Object {
$id = $_.IdentityReference.Value
$id -ilike '*Everyone*' -or $id -ilike 'BUILTIN\Users'
}
return ($null -eq $broad -or @($broad).Count -eq 0)
}
# ── Check 2: Credential Manager target ────────────────────────────────────────
Write-Host "`n=== Credential Manager (target: $GuestCredTarget) ==="
Write-Check "CredentialManager module available" {
$null -ne (Get-Module -Name CredentialManager -ListAvailable -ErrorAction SilentlyContinue)
}
Write-Check "Credential target '$GuestCredTarget' accessible" {
# Use cmdkey.exe as a fallback — avoids requiring CredentialManager module
$cmdkeyOut = & cmdkey.exe /list:$GuestCredTarget 2>&1 | Out-String
if ($cmdkeyOut -imatch [regex]::Escape($GuestCredTarget)) {
return $true
}
# Try via CredentialManager module if available
if ($null -ne (Get-Module -Name CredentialManager -ListAvailable -ErrorAction SilentlyContinue)) {
Import-Module CredentialManager -ErrorAction SilentlyContinue
$cred = Get-StoredCredential -Target $GuestCredTarget -ErrorAction SilentlyContinue
return ($null -ne $cred)
}
return $false
}
# ── Check 3: CI directory structure ───────────────────────────────────────────
Write-Host "`n=== CI Directory Structure ($CIRootDir) ==="
$requiredDirs = @(
'BuildVMs',
'Artifacts',
'Logs',
'State',
'keys',
'Cache\NuGet',
'Cache\pip',
'Templates'
)
foreach ($rel in $requiredDirs) {
$full = Join-Path $CIRootDir $rel
Write-Check "Directory exists: $rel" { Test-Path $full -PathType Container }
}
# ── Check 4: vmrun.exe reachable ──────────────────────────────────────────────
Write-Host "`n=== VMware ==="
Write-Check "vmrun.exe found in PATH or default location" {
$vmrunDefault = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe'
$inPath = $null -ne (Get-Command 'vmrun.exe' -ErrorAction SilentlyContinue)
$atDefault = Test-Path $vmrunDefault -PathType Leaf
$inPath -or $atDefault
}
# ── Summary ───────────────────────────────────────────────────────────────────
Write-Host "`n=== Summary ==="
Write-Host " Passed : $($script:passCount)"
Write-Host " Failed : $($script:failCount)"
if ($script:failCount -gt 0) {
Write-Host "`n[Validate-HostState] $($script:failCount) check(s) failed. Review output above." -ForegroundColor Red
exit 1
} else {
Write-Host "`n[Validate-HostState] All checks passed." -ForegroundColor Green
exit 0
}