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
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
#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
|
||||
}
|
||||
Reference in New Issue
Block a user