Add windows/iso-builder.md: reproducible custom-packed-ISO pipeline design for
SilverMetal Enhanced - Windows on IoT Enterprise LTSC. Covers the licensing
frame (IoT = blessed channel for preinstalled custom images; self-apply stays a
builder), 7 build stages (verify/extract/DISM-service/inject-unattend/brand/
oscdimg-repack/attest), the offline-vs-first-boot-vs-firmware control split, an
honest reproducibility scope (pinned inputs + SBOM + attestation, NOT bit-
identical on Windows), and M0-M4 milestones.
Scaffold windows/ per the planned layout:
- installer/ build.ps1 (7-stage orchestrator, stages stubbed to M2),
inputs.manifest.json (pinned-input schema), autounattend.xml
(local-account OOBE), oem/SetupComplete.cmd (first-boot runner)
- hardening/ shared §A-H PowerShell modules + Verify-SilverMetalWindows.ps1
(used by BOTH the ISO first-boot path and the self-apply track).
BitLocker module enforces TPM+PIN and blocks TPM-only.
- policies/ wdac/ debloat/ stack-installer/ drivers/ tests/ scaffolded with
READMEs; wdac/ documents audit->enforce; debloat/ flags Tiny11/NTLite as an
anti-pattern; rename applocker/ -> wdac/ realised.
All 11 PowerShell scripts parse clean; manifest JSON + autounattend XML valid.
Module bodies are M1 scaffold (safe: log + policy-set; interactive/firmware
steps documented, not faked).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
3.0 KiB
PowerShell
60 lines
3.0 KiB
PowerShell
#Requires -Version 5.1
|
|
<# SilverMetal Enhanced - Windows | Verification
|
|
Asserts the hardening-spec.md verification gates and emits a report. Evidence
|
|
before assertions: each check reports the observed value, not a claim.
|
|
Spec: ../hardening-spec.md (§6) | SCAFFOLD (M1): fill remaining gates.
|
|
Exit code 0 = all pass; non-zero = count of failed gates.
|
|
#>
|
|
[CmdletBinding()] param([string]$ReportPath = "$env:ProgramData\SilverMetal\verify-report.json")
|
|
Set-StrictMode -Version Latest; $ErrorActionPreference = 'Continue'
|
|
|
|
$results = [ordered]@{}
|
|
function Test-Gate { param([string]$Name,[scriptblock]$Check)
|
|
try { $v = & $Check; $results[$Name] = @{ pass = [bool]$v.pass; detail = $v.detail } }
|
|
catch { $results[$Name] = @{ pass = $false; detail = "error: $_" } }
|
|
}
|
|
|
|
Test-Gate 'Telemetry=Security(0)' {
|
|
$v = (Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection' -Name AllowTelemetry -EA Stop).AllowTelemetry
|
|
@{ pass = ($v -eq 0); detail = "AllowTelemetry=$v" }
|
|
}
|
|
Test-Gate 'BitLocker XtsAes256 + TpmPin' {
|
|
$bl = Get-BitLockerVolume -MountPoint $env:SystemDrive -EA Stop
|
|
$hasPin = ($bl.KeyProtector.KeyProtectorType -contains 'TpmPin')
|
|
@{ pass = ($bl.ProtectionStatus -eq 'On' -and $bl.EncryptionMethod -eq 'XtsAes256' -and $hasPin)
|
|
detail = "$($bl.ProtectionStatus)/$($bl.EncryptionMethod)/TpmPin=$hasPin" }
|
|
}
|
|
Test-Gate 'VBS + HVCI + CredentialGuard running' {
|
|
$dg = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard -EA Stop
|
|
$svc = $dg.SecurityServicesRunning
|
|
@{ pass = (($svc -contains 1) -and ($svc -contains 2)); detail = "running=$($svc -join ',')" }
|
|
}
|
|
Test-Gate 'WDAC enforced' {
|
|
$dg = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard -EA Stop
|
|
@{ pass = ($dg.CodeIntegrityPolicyEnforcementStatus -eq 2)
|
|
detail = "enforcement=$($dg.CodeIntegrityPolicyEnforcementStatus) (2=enforced,1=audit)" }
|
|
}
|
|
Test-Gate 'Secure Boot enabled' {
|
|
@{ pass = (Confirm-SecureBootUEFI); detail = 'Confirm-SecureBootUEFI' }
|
|
}
|
|
Test-Gate 'Firewall default-deny inbound' {
|
|
$p = Get-NetFirewallProfile -EA Stop
|
|
@{ pass = -not ($p.DefaultInboundAction -contains 'Allow'); detail = ($p.DefaultInboundAction -join ',') }
|
|
}
|
|
Test-Gate 'Windows Update healthy' {
|
|
$s = Get-Service wuauserv -EA Stop
|
|
@{ pass = ($s.StartType -ne 'Disabled'); detail = "$($s.Status)/$($s.StartType)" }
|
|
}
|
|
# TODO-M1: VPN kill-switch egress test; telemetry-leak capture; Stack functional check.
|
|
|
|
$pass = ($results.Values | Where-Object { $_.pass }).Count
|
|
$fail = ($results.Values | Where-Object { -not $_.pass }).Count
|
|
New-Item (Split-Path $ReportPath) -ItemType Directory -Force -EA SilentlyContinue | Out-Null
|
|
$results | ConvertTo-Json -Depth 4 | Set-Content $ReportPath
|
|
$results.GetEnumerator() | ForEach-Object {
|
|
$c = if ($_.Value.pass) { 'Green' } else { 'Red' }
|
|
Write-Host ("[{0}] {1} - {2}" -f ($(if($_.Value.pass){'PASS'}else{'FAIL'}), $_.Key, $_.Value.detail)) -ForegroundColor $c
|
|
}
|
|
Write-Host "`n$pass passed, $fail failed. Report: $ReportPath"
|
|
exit $fail
|