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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user