security(sprint1): §1.1/1.3/1.4 — WinRM HTTPS/5986, SHA256 pinning infra, IP regex per-octet

§1.4 — Replace loose IP ValidatePattern with per-octet regex in 4 scripts
  (Invoke-CIJob, Invoke-RemoteBuild, Get-BuildArtifacts, Wait-VMReady)

§1.1 — Migrate all WinRM connections from HTTP/5985 to HTTPS/5986
  - Deploy post-install.ps1: remove AllowUnencrypted=true; self-signed cert + HTTPS listener
    already present; firewall rule restricted to 192.168.79.0/24
  - Setup-WinBuild2025.ps1: assert AllowUnencrypted=false
  - Prepare-WinBuild2025.ps1: preflight uses TCP/5986; Test-WSMan -UseSSL -Port 5986;
    New-PSSession -UseSSL -Port 5986; host AllowUnencrypted save/restore removed (not needed for HTTPS)
  - Invoke-RemoteBuild, Get-BuildArtifacts: New-PSSession -UseSSL -Port 5986 -Authentication Basic
  - Wait-VMReady: New-WSManSessionOption + Test-WSMan -Port 5986 -UseSSL
  - Validate-DeployState, Validate-SetupState: Invoke-Command -UseSSL -Port 5986,
    AllowUnencrypted check → false; AllowUnencrypted host-side removed from both
  - Docs: PLAN, README, ARCHITECTURE, BEST-PRACTICES, HOST-SETUP, WINDOWS-TEMPLATE-SETUP,
    LINUX-TEMPLATE-SETUP, TODO updated

§1.3 — SHA256 hash pinning infrastructure
  - Assert-Hash function + $script:Hashes hashtable added to Setup-WinBuild2025.ps1
  - Assert-Hash called after dotnet-install.ps1, python installer, vs_buildtools.exe downloads
  - Hashes initialized to '' (warn-skip); must be filled before next snapshot refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simone
2026-05-10 14:46:41 +02:00
parent 41611d46a7
commit 68cde01c9d
17 changed files with 193 additions and 183 deletions
+30 -37
View File
@@ -65,7 +65,7 @@
```powershell
Enable-PSRemoting -Force -SkipNetworkProfileCheck
Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force
Set-Item WSMan:\localhost\Service\AllowUnencrypted $true -Force
# AllowUnencrypted left false — Deploy post-install.ps1 configures HTTPS/5986 listener
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
```
- [x] **Attiva Windows Server 2025** (prima dello snapshot — i linked clone ereditano l'attivazione):
@@ -87,7 +87,7 @@
```
Installa: WinRM config, utente `ci_build`, .NET SDK 10.0, VS Build Tools 2026, Python 3.13.3.
Lo script esegue validazione automatica ad ogni passaggio (`Assert-Step`):
- Pre-flight: IP ottetti 0-255, TCP/5985 raggiungibile, Setup-WinBuild2025.ps1 presente
- Pre-flight: IP ottetti 0-255, TCP/5986 raggiungibile, Setup-WinBuild2025.ps1 presente
- Host WinRM: AllowUnencrypted, TrustedHosts
- Guest prep: `C:\CI` esiste, file copiato, size match
- Post-setup remoto (9 check): WinRM, user+admin, UAC, firewall, .NET, Python, MSBuild, CI dirs
@@ -148,25 +148,22 @@
## 1. Sicurezza & hardening
### 1.1 [P0] Migrare WinRM da HTTP/Basic a HTTPS/5986
File: [template/Setup-WinBuild2025.ps1:204-241](template/Setup-WinBuild2025.ps1) (Step 3 WinRM), [template/Deploy-WinBuild2025.ps1:524-534](template/Deploy-WinBuild2025.ps1) (Enable-PSRemoting + AllowUnencrypted/Basic), [scripts/Invoke-RemoteBuild.ps1:83-91](scripts/Invoke-RemoteBuild.ps1), [scripts/Get-BuildArtifacts.ps1:66-75](scripts/Get-BuildArtifacts.ps1).
**Motivazione**: la combo `AllowUnencrypted=true` + `Auth/Basic=true` invia credenziali del
`ci_build` (admin) in chiaro su VMnet8. Anche in lab isolato, l'host può essere compromesso
via altri vettori e scrapare la rete VMware. Il piano è già in `docs/BEST-PRACTICES.md §2`;
manca solo l'esecuzione.
### 1.1 [P0] Migrare WinRM da HTTP/Basic a HTTPS/5986 — COMPLETATO 2026-05-10
**Azioni**:
- [ ] Generare cert self-signed nel template *prima* dello snapshot.
- [ ] Sostituire `New-PSSession -ComputerName $ip` con `-UseSSL -Port 5986` in `Invoke-RemoteBuild.ps1` e `Get-BuildArtifacts.ps1`.
- [ ] Rimuovere `AllowUnencrypted=true` dal `Setup-WinBuild2025.ps1`.
- [ ] Tenere `-SkipCACheck` lato host: per cert lab self-signed è accettabile, ma documentare la motivazione inline.
- [ ] Regole firewall — limitare WinRM alla subnet build VM (`192.168.79.0/24` — VMnet8 NAT).
- [x] Generare cert self-signed nel template *prima* dello snapshot (già in Deploy post-install.ps1).
- [x] Sostituire `New-PSSession -ComputerName $ip` con `-UseSSL -Port 5986 -Authentication Basic` in `Invoke-RemoteBuild.ps1`, `Get-BuildArtifacts.ps1`, `Prepare-WinBuild2025.ps1`, `Validate-*.ps1`.
- [x] Rimuovere `AllowUnencrypted=true` da Deploy post-install.ps1 e Setup-WinBuild2025.ps1 (assertion → false).
- [x] `-SkipCACheck`/`-SkipCNCheck`/`-SkipRevocationCheck` mantenuti nei `New-PSSessionOption` e `New-WSManSessionOption` (cert lab self-signed — documentato inline).
- [x] Regola firewall `WinRM-HTTPS` in Deploy ristretta a `RemoteAddress '192.168.79.0/24'`.
- [x] `Wait-VMReady.ps1` usa `New-WSManSessionOption` + `Test-WSMan -Port 5986 -UseSSL`.
- [x] `Prepare-WinBuild2025.ps1`: preflight TCP/5986, host non imposta più `AllowUnencrypted`; solo `TrustedHosts` viene salvato/ripristinato.
- [x] `Validate-DeployState.ps1`, `Validate-SetupState.ps1`: connessione HTTPS/5986, check `AllowUnencrypted=false`.
### 1.2 [P0] Restringere `TrustedHosts` lato host (audit-only — già coperto)
File: nessuno script imposta `*` sull'host. Stato corrente:
- `Setup-Host.ps1` non tocca TrustedHosts (verificato 2026-05-10)
- [template/Prepare-WinBuild2025.ps1:270-294](template/Prepare-WinBuild2025.ps1:270) appende `$VMIPAddress` e ripristina nel `finally` (riga 617)
- [template/Prepare-WinBuild2025.ps1](template/Prepare-WinBuild2025.ps1) appende `$VMIPAddress` ai `TrustedHosts` e ripristina nel `finally` (post-§1.1: solo TrustedHosts, no AllowUnencrypted)
- [docs/HOST-SETUP.md:121-126](docs/HOST-SETUP.md:121) raccomanda `'192.168.79.*'` in setup manuale
**Azione residua**: audit eventuali host già configurati con `*` (eredità manuale) e
@@ -175,32 +172,28 @@ sostituire con la subnet build:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '192.168.79.*' -Force -Concatenate
```
### 1.3 [P0] Pinning hash SHA256 degli installer
File: [template/Setup-WinBuild2025.ps1:707-755](template/Setup-WinBuild2025.ps1) (Step 8 Python), [template/Setup-WinBuild2025.ps1:757-885](template/Setup-WinBuild2025.ps1) (Step 9 VS Build Tools), [template/Setup-WinBuild2025.ps1:660-705](template/Setup-WinBuild2025.ps1) (Step 7 dotnet-install.ps1).
### 1.3 [P0] Pinning hash SHA256 degli installer — STRUTTURA AGGIUNTA 2026-05-10
**Motivazione**: Python, `dotnet-install.ps1`, `vs_buildtools.exe` vengono scaricati senza
verifica integrità. Un MITM nella rete dell'host (anche temporaneo) può iniettare binari
compromessi nel template, che poi viene snapshottato e usato da *ogni* build.
`$script:Hashes` + `Assert-Hash` aggiunti a [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
`Assert-Hash` viene chiamato dopo ogni download (dotnet-install.ps1, python installer, vs_buildtools.exe).
Se l'hash è `''` viene emesso un warning ma l'esecuzione continua (non-breaking).
Pattern minimale:
```powershell
$expected = '6F25A7DF...' # SHA256 pinnato per la versione esatta
$actual = (Get-FileHash $pyInstallerPath -Algorithm SHA256).Hash.ToLower()
if ($actual -ne $expected.ToLower()) { throw "Hash mismatch: $actual" }
```
Aggiungere un blocco hash per ogni download e bloccarli in costanti `$script:Hashes`.
Aggiornare al cambio versione (è raro).
**Azione residua — OBBLIGATORIA prima del prossimo refresh snapshot**:
- [ ] Scaricare i tre file, calcolare hash, riempire `$script:Hashes` in Setup-WinBuild2025.ps1:
```powershell
# Inside or outside VM — qualsiasi PowerShell:
(Get-FileHash 'python-3.13.3-amd64.exe' -Algorithm SHA256).Hash # → $script:Hashes['Python']
(Get-FileHash 'dotnet-install.ps1' -Algorithm SHA256).Hash # → $script:Hashes['DotNetInstallScript']
(Get-FileHash 'vs_buildtools.exe' -Algorithm SHA256).Hash # → $script:Hashes['VSBuildToolsBootstrapper']
```
- [ ] Verificare che `Assert-Hash` throwi correttamente su hash errato (test con hash fake).
### 1.4 [P1] Validazione IP per-ottetto
File: [scripts/Invoke-CIJob.ps1:106](scripts/Invoke-CIJob.ps1), [scripts/Invoke-RemoteBuild.ps1:52](scripts/Invoke-RemoteBuild.ps1), [scripts/Get-BuildArtifacts.ps1:42](scripts/Get-BuildArtifacts.ps1), [scripts/Wait-VMReady.ps1:43](scripts/Wait-VMReady.ps1).
### 1.4 [P1] Validazione IP per-ottetto — COMPLETATO 2026-05-10
**Motivazione**: `'^(\d{1,3}\.){3}\d{1,3}$'` accetta `999.999.999.999`. Sostituire con regex
per-ottetto:
```powershell
$ipv4 = '^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$'
[ValidatePattern($ipv4)]
```
Estrarre la costante in un modulo condiviso `scripts/_Common.psm1` per evitare drift fra i 4 file (vedi §5.2).
Regex per-ottetto sostituita in tutti e 4 i file:
`'^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$'`
Estrazione in `scripts/_Common.psm1` — vedi §5.2 (P2, backlog).
### 1.5 [P1] PAT mai persistito quando si abilita `-UseGitClone`
Requisiti di sicurezza per l'implementazione del clone in-VM. La sequenza operativa