diff --git a/Changelog.md b/Changelog.md index a6ef5e2..6d1096a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,18 @@ # Changelog +## v1.116.0 + +NTP clock-tamper protection + time-authentication audit — a new **NTP Configuration → [8] Clock-Tamper Protection** item plus a read-only CLI action. + +Windows Time (W32Time) bounds how far a single sync may move the clock with `MaxPosPhaseCorrection` / `MaxNegPhaseCorrection`. On domain members these default to `0xFFFFFFFF` (**unbounded**) — so a wrong or hostile time source can jump the clock arbitrarily, which breaks Kerberos (auth fails past ~5 min skew) and can enable ticket/replay and certificate-validity attacks. + +- **`NtpHardeningAudit`** (read-only) — reports the sync type (NT5DS / NTP / NoSync), whether the time source is authenticated (domain hierarchy = MS-SNTP) or unauthenticated (manual external NTP), the current phase-correction limits (decoding `0xFFFFFFFF` as "Unbounded"), and `RequireSecureTimeSyncRequests` when the NTP server is enabled. JSON-aware; makes no changes. +- **Clock-Tamper Protection** (reversible) — bounds `MaxPos/MaxNegPhaseCorrection` to a chosen cap (default 48 h, matching the domain-controller default; never trips on a normally-running server, only closes the unbounded default). Prior values are captured for the session undo and the change is Dry-Run aware. Corrections beyond the cap are logged instead of applied — run **Force Time Sync** to recover after a legitimate large jump. + +A note on terminology: Windows has **no standalone symmetric-key/`ntp.keys` file** like Unix `ntpd`. Authenticated NTP on Windows is **MS-SNTP**, keyed automatically from the machine account through the domain hierarchy (legacy MD5-derived crypto). The audit surfaces whether that authenticated path is in use rather than exposing a keys file that does not exist on the platform. + +Addition to 19-NTPConfiguration (no new module). CLI actions: 197 → 198. + ## v1.115.0 Network throughput benchmark — a new interactive diagnostic under **Network Diagnostics → [14] Network Throughput Benchmark (file copy)**. diff --git a/Header.ps1 b/Header.ps1 index ca6938f..f48d385 100644 --- a/Header.ps1 +++ b/Header.ps1 @@ -30,7 +30,7 @@ 7h3 4b1d3r .VERSION - 1.115.0 + 1.116.0 .LAST UPDATED 05/23/2026 @@ -1391,7 +1391,7 @@ param( # CLI headless mode: run a specific action without interactive menus # Usage: RackStack.exe -Action Cleanup [-Tier Standard] [-Silent] [-OutputFormat JSON] - [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus', 'VHDXEncryptionAudit', 'ADRecycleBin', 'ClusterValidationReport', 'SmbEnforce', 'SmbSecurityCheck', 'PrintServerAudit')] + [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus', 'VHDXEncryptionAudit', 'ADRecycleBin', 'ClusterValidationReport', 'SmbEnforce', 'SmbSecurityCheck', 'PrintServerAudit', 'NtpHardeningAudit')] [string]$Action, [ValidateSet('Light', 'Standard', 'Aggressive')] diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1 index 9098c2e..2fdffe4 100644 --- a/Modules/00-Initialization.ps1 +++ b/Modules/00-Initialization.ps1 @@ -225,7 +225,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) { if (-not $script:ModuleRoot -and $script:ScriptPath) { $script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath) } -$script:ScriptVersion = "1.115.0" +$script:ScriptVersion = "1.116.0" $script:ScriptStartTime = Get-Date # Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe. diff --git a/Modules/19-NTPConfiguration.ps1 b/Modules/19-NTPConfiguration.ps1 index 8a2fecb..dfa9dce 100644 --- a/Modules/19-NTPConfiguration.ps1 +++ b/Modules/19-NTPConfiguration.ps1 @@ -67,6 +67,7 @@ function Set-NTPConfiguration { Write-MenuItem "[5] Force Time Sync Now" Write-MenuItem "[6] Show Detailed Time Status" Write-MenuItem "[7] View NTP Status" + Write-MenuItem "[8] Clock-Tamper Protection (phase-correction limits)" Write-OutputColor " └────────────────────────────────────────────────────────────────────────┘" -color "Info" Write-OutputColor "" -color "Info" Write-OutputColor " [B] ◄ Back" -color "Info" @@ -117,9 +118,12 @@ function Set-NTPConfiguration { "7" { Show-NTPStatus } + "8" { + Show-NtpClockHardening + } "b" { return } "B" { return } - default { Write-OutputColor " Invalid choice. Enter 1-7 or B." -color "Error"; Start-Sleep -Seconds 1 } + default { Write-OutputColor " Invalid choice. Enter 1-8 or B." -color "Error"; Start-Sleep -Seconds 1 } } Write-PressEnter @@ -331,4 +335,164 @@ function Show-NTPStatus { Write-OutputColor " Could not test time accuracy" -color "Warning" } } + +# Read-only: report W32Time tamper-protection posture. MaxPos/MaxNegPhaseCorrection +# bound how far a single sync may move the clock; on domain members they default to +# 0xFFFFFFFF (UNBOUNDED) — any time source can jump the clock arbitrarily. SyncType +# NT5DS means time comes authenticated from the domain hierarchy (MS-SNTP); NTP +# (manual peers) is UNauthenticated. Windows has no standalone symmetric-key file — +# NTP authentication is domain-based (machine account / MS-SNTP), not a keys file. +function Get-NtpHardeningStatus { + $cfgPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config' + $parPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters' + $srvPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer' + $result = [PSCustomObject]@{ + Available = $false + SyncType = 'Unknown' + AuthenticatedSource = $false + MaxPosPhaseCorrection = $null + MaxNegPhaseCorrection = $null + NtpServerEnabled = $false + RequireSecureSyncReqs = $null + } + try { + $cfg = Get-ItemProperty -Path $cfgPath -ErrorAction Stop + $result.Available = $true + $mp = $cfg.MaxPosPhaseCorrection + $mn = $cfg.MaxNegPhaseCorrection + $result.MaxPosPhaseCorrection = if ($null -eq $mp) { $null } elseif ([uint32]$mp -eq [uint32]::MaxValue) { 'Unbounded' } else { [int64][uint32]$mp } + $result.MaxNegPhaseCorrection = if ($null -eq $mn) { $null } elseif ([uint32]$mn -eq [uint32]::MaxValue) { 'Unbounded' } else { [int64][uint32]$mn } + } catch { return $result } + try { + $par = Get-ItemProperty -Path $parPath -ErrorAction SilentlyContinue + if ($par -and $par.Type) { $result.SyncType = "$($par.Type)"; $result.AuthenticatedSource = ($par.Type -eq 'NT5DS') } + } catch {} + try { + $srv = Get-ItemProperty -Path $srvPath -ErrorAction SilentlyContinue + if ($srv) { + $result.NtpServerEnabled = ([int]$srv.Enabled -eq 1) + if ($null -ne $srv.RequireSecureTimeSyncRequests) { $result.RequireSecureSyncReqs = [int]$srv.RequireSecureTimeSyncRequests } + } + } catch {} + return $result +} + +# Reversible: bound the clock phase-correction limits so a wrong or hostile time +# source cannot apply an arbitrarily large clock jump. Beyond the cap W32Time logs +# an event instead of moving the clock. The default cap (48h) matches the domain +# controller default and never trips on a normally-running server; it only closes +# the UNBOUNDED (0xFFFFFFFF) default that domain members ship with. +function Set-NtpClockTamperProtection { + param([int]$CapSeconds = 172800) + $cfgPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config' + if (-not (Test-Path -Path $cfgPath)) { + Write-OutputColor " W32Time Config registry key not found — is the Windows Time service installed?" -color "Error"; return + } + $priorPos = (Get-ItemProperty -Path $cfgPath -Name MaxPosPhaseCorrection -ErrorAction SilentlyContinue).MaxPosPhaseCorrection + $priorNeg = (Get-ItemProperty -Path $cfgPath -Name MaxNegPhaseCorrection -ErrorAction SilentlyContinue).MaxNegPhaseCorrection + + if ($script:DryRunMode -and -not $script:ApplyingDryRunQueue) { + $cp = $priorPos; $cn = $priorNeg; $cap = $CapSeconds; $path = $cfgPath + Push-DryRunStep -Label "Bound NTP clock phase-correction to ${CapSeconds}s" -Category "Security" -OneWay $false ` + -Params @{ CapSeconds = $CapSeconds } ` + -Preflight { Test-Path -Path $path }.GetNewClosure() ` + -Apply { + Set-ItemProperty -Path $path -Name MaxPosPhaseCorrection -Value $cap -Type DWord -ErrorAction SilentlyContinue + Set-ItemProperty -Path $path -Name MaxNegPhaseCorrection -Value $cap -Type DWord -ErrorAction SilentlyContinue + $null = w32tm /config /update 2>&1 + Restart-Service w32time -Force -ErrorAction SilentlyContinue + }.GetNewClosure() ` + -Undo { + if ($null -ne $cp) { Set-ItemProperty -Path $path -Name MaxPosPhaseCorrection -Value $cp -Type DWord -ErrorAction SilentlyContinue } + if ($null -ne $cn) { Set-ItemProperty -Path $path -Name MaxNegPhaseCorrection -Value $cn -Type DWord -ErrorAction SilentlyContinue } + $null = w32tm /config /update 2>&1 + Restart-Service w32time -Force -ErrorAction SilentlyContinue + }.GetNewClosure() + Write-OutputColor " Queued (Dry-Run): bound clock phase-correction to ${CapSeconds}s." -color "Warning" + Add-SessionChange -Category "DryRun" -Description "Queued NTP clock-tamper protection (${CapSeconds}s cap)" + return + } + + try { + Set-ItemProperty -Path $cfgPath -Name MaxPosPhaseCorrection -Value $CapSeconds -Type DWord -ErrorAction Stop + Set-ItemProperty -Path $cfgPath -Name MaxNegPhaseCorrection -Value $CapSeconds -Type DWord -ErrorAction Stop + $null = w32tm /config /update 2>&1 + Restart-Service w32time -Force -ErrorAction SilentlyContinue + Write-OutputColor " Clock phase-correction bounded to ${CapSeconds}s (was Pos=$priorPos / Neg=$priorNeg)." -color "Success" + Write-OutputColor " Corrections larger than the cap are now logged instead of applied; run Force Time Sync after a legitimate large jump." -color "Info" + Add-SessionChange -Category "Security" -Description "Bounded NTP clock phase-correction to ${CapSeconds}s" + Clear-MenuCache + Add-UndoAction -Category "Security" -Description "Bounded NTP clock phase-correction to ${CapSeconds}s" -UndoScript { + param($Path, $Pos, $Neg) + if ($null -ne $Pos) { Set-ItemProperty -Path $Path -Name MaxPosPhaseCorrection -Value $Pos -Type DWord -ErrorAction SilentlyContinue } + if ($null -ne $Neg) { Set-ItemProperty -Path $Path -Name MaxNegPhaseCorrection -Value $Neg -Type DWord -ErrorAction SilentlyContinue } + $null = w32tm /config /update 2>&1 + Restart-Service w32time -Force -ErrorAction SilentlyContinue + } -UndoParams @{ Path = $cfgPath; Pos = $priorPos; Neg = $priorNeg } + } + catch { + Write-OutputColor " Failed to apply clock-tamper protection: $($_.Exception.Message)" -color "Error" + } +} + +# Interactive: clock-tamper protection audit + offer to bound phase-correction. +function Show-NtpClockHardening { + Clear-Host + Write-CenteredOutput "Clock-Tamper Protection" -color "Info" + $s = Get-NtpHardeningStatus + if (-not $s.Available) { Write-OutputColor " W32Time configuration is not available on this host." -color "Error"; return } + + $posStr = if ($s.MaxPosPhaseCorrection -eq 'Unbounded') { "Unbounded (0xFFFFFFFF)" } else { "$($s.MaxPosPhaseCorrection)s" } + $negStr = if ($s.MaxNegPhaseCorrection -eq 'Unbounded') { "Unbounded (0xFFFFFFFF)" } else { "$($s.MaxNegPhaseCorrection)s" } + $posColor = if ($s.MaxPosPhaseCorrection -eq 'Unbounded') { "Warning" } else { "Success" } + $negColor = if ($s.MaxNegPhaseCorrection -eq 'Unbounded') { "Warning" } else { "Success" } + + Write-OutputColor " Sync type : $($s.SyncType)" -color "Info" + if ($s.AuthenticatedSource) { + Write-OutputColor " Time authentication : Domain hierarchy (MS-SNTP, authenticated)" -color "Success" + } else { + Write-OutputColor " Time authentication : Manual / external NTP (UNauthenticated)" -color "Warning" + } + Write-OutputColor " Max +correction : $posStr" -color $posColor + Write-OutputColor " Max -correction : $negStr" -color $negColor + if ($s.NtpServerEnabled -and $null -ne $s.RequireSecureSyncReqs) { + $rsColor = if ($s.RequireSecureSyncReqs -eq 1) { "Success" } else { "Warning" } + Write-OutputColor " Secure sync requests : $($s.RequireSecureSyncReqs) (NTP server)" -color $rsColor + } + Write-OutputColor "" -color "Info" + Write-OutputColor " NOTE: Windows authenticated NTP (MS-SNTP) is domain-based and uses legacy" -color "Info" + Write-OutputColor " MD5-derived crypto. There is no standalone symmetric-key file on Windows;" -color "Info" + Write-OutputColor " authentication comes from the domain hierarchy (NT5DS), not a keys file." -color "Info" + Write-OutputColor "" -color "Info" + + if ($s.MaxPosPhaseCorrection -eq 'Unbounded' -or $s.MaxNegPhaseCorrection -eq 'Unbounded') { + Write-OutputColor " This host accepts UNBOUNDED clock corrections — a wrong or hostile time" -color "Warning" + Write-OutputColor " source could jump the clock arbitrarily (breaking Kerberos / enabling replay)." -color "Warning" + } + if (-not (Confirm-UserAction -Message "Bound the clock phase-correction limits now?")) { Write-OutputColor " No changes made." -color "Info"; return } + + $capSeconds = 172800 + $capInput = Read-Host " Cap in seconds (default 172800 = 48h; e.g. 3600 = 1h)" + if (-not [string]::IsNullOrWhiteSpace($capInput)) { + $parsed = 0 + if ([int]::TryParse($capInput.Trim(), [ref]$parsed) -and $parsed -gt 0) { $capSeconds = $parsed } + } + Set-NtpClockTamperProtection -CapSeconds $capSeconds +} + +# CLI: NtpHardeningAudit — read-only clock-tamper / auth posture (JSON-aware). +function Start-NtpHardeningAudit { + $s = Get-NtpHardeningStatus + if ($script:CLIOutputFormat -eq 'JSON') { + Write-Output (@{ + Tool = $script:ToolFullName; Version = $script:ScriptVersion; Action = 'NtpHardeningAudit' + Timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss"); Hostname = $env:COMPUTERNAME + Available = $s.Available; SyncType = $s.SyncType; AuthenticatedSource = $s.AuthenticatedSource + MaxPosPhaseCorrection = "$($s.MaxPosPhaseCorrection)"; MaxNegPhaseCorrection = "$($s.MaxNegPhaseCorrection)" + NtpServerEnabled = $s.NtpServerEnabled; RequireSecureSyncRequests = $s.RequireSecureSyncReqs + } | ConvertTo-Json) + } + else { Show-NtpClockHardening } + return $true +} #endregion \ No newline at end of file diff --git a/Modules/34-Help.ps1 b/Modules/34-Help.ps1 index 5f0f786..52b5294 100644 --- a/Modules/34-Help.ps1 +++ b/Modules/34-Help.ps1 @@ -253,7 +253,7 @@ function Search-HelpTopics { @{ Title = "Performance"; Keywords = @("performance", "cpu", "memory", "disk", "io", "bandwidth", "dashboard", "process"); Description = "Live performance dashboard with CPU, memory, disk I/O, and network bandwidth monitoring" } @{ Title = "Licensing & NTP"; Keywords = @("license", "activation", "kms", "avma", "ntp", "time", "timezone", "clock"); Description = "Windows licensing status (KMS/AVMA/Retail), NTP configuration, time sync, and timezone setup" } @{ Title = "VM Management"; Keywords = @("checkpoint", "snapshot", "export", "import", "migration", "vhd", "iso"); Description = "VM checkpoints, export/import, migration readiness, VHD health, and ISO inventory" } - @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "197 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." } + @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "198 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." } @{ Title = "SelfTest Action"; Keywords = @("selftest", "self-test", "diagnose", "diagnostic", "verify", "healthcheck", "sanity"); Description = "Internal diagnostic. -Action SelfTest checks PS version, elevation, module count, version consistency, defaults.json validity, temp path writability, FileServer reachability, and agent installer config. Exit 1 on any failure. Use -OutputFormat JSON for structured output." } @{ Title = "Security Audits"; Keywords = @("security", "audit", "hardening", "compliance", "tls", "smb", "kerberos", "credguard", "applocker", "bitlockeraudit", "defenderexclusionaudit", "audit-policy", "secureboot", "tpm"); Description = "Security-focused CLI audits: TLSAudit, SMBAudit, KerberosAudit, CredGuardAudit, AppLockerAudit, BitLockerAudit, DefenderExclusionAudit, AuditPolicyAudit, SecureBootAudit, TPMAudit, UserAudit, LogonAudit, InsecureServiceAudit, RegistryAudit. All support -OutputFormat JSON." } @{ Title = "Network Audits"; Keywords = @("netaudit", "dns", "firewall-audit", "firewalllog", "arp", "route", "tcp", "netstat", "dhcp", "netprofile", "winrm", "qos", "nicoffload"); Description = "Network audits: DNSAudit, DNSCacheAudit, FirewallAudit, FirewallRuleAudit, FirewallLogAudit, ARPTableAudit, RouteTableAudit, TcpSettingsAudit, NetStatAudit, DHCPAudit, NetworkProfileAudit, WinRMAudit, QoSPolicyAudit, NICOffloadAudit, NICErrorAudit, HostsFileAudit, VPNAudit, ProxyAudit." } diff --git a/Modules/50-EntryPoint.ps1 b/Modules/50-EntryPoint.ps1 index f2c13c1..2994a5c 100644 --- a/Modules/50-EntryPoint.ps1 +++ b/Modules/50-EntryPoint.ps1 @@ -415,6 +415,7 @@ function Assert-Elevation { @{ Action = 'SmbSecurityCheck'; Description = 'Read-only: report SMB server signing/encryption posture (JSON-aware)' } @{ Action = 'SmbEnforce'; Description = 'Enforce SMB server signing + encryption (reversible)' } @{ Action = 'PrintServerAudit'; Description = 'Read-only: report print-spooler posture, queue depth, orphaned ports + unused drivers (JSON-aware)' } + @{ Action = 'NtpHardeningAudit'; Description = 'Read-only: report W32Time clock-tamper posture (phase-correction limits, auth mode) (JSON-aware)' } @{ Action = 'Batch'; Description = 'JSON-driven full configuration' } ) if ($script:CLIOutputFormat -eq 'JSON') { @@ -2077,6 +2078,11 @@ footer{text-align:center;color:#999;font-size:12px;padding:16px} $psAuditOk = Start-PrintServerAudit [Environment]::Exit([int](-not $psAuditOk)) } + 'NtpHardeningAudit' { + # Read-only W32Time clock-tamper / auth posture (JSON-aware). + $ntpAuditOk = Start-NtpHardeningAudit + [Environment]::Exit([int](-not $ntpAuditOk)) + } 'Batch' { if (-not $script:CLIConfig) { Write-OutputColor " ERROR: -Action Batch requires -Config " -color "Error" diff --git a/README.md b/README.md index 8b8423b..d8c3d42 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ OpenSSF Best Practices codecov PSScriptAnalyzer 0 errors - 5085 structural tests + 5099 structural tests Pester 312 tests SLSA Level 3

@@ -37,7 +37,7 @@ --- -RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." Where sconfig gives you 15 options, RackStack gives you 197 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, cloud onboarding, and batch automation, all with undo, transaction rollback, and audit logging. +RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." Where sconfig gives you 15 options, RackStack gives you 198 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, cloud onboarding, and batch automation, all with undo, transaction rollback, and audit logging. Built for MSPs, sysadmins, and infrastructure teams who build servers repeatedly and want it done right every time. @@ -61,7 +61,7 @@ Built for MSPs, sysadmins, and infrastructure teams who build servers repeatedly **Automation** -- JSON-driven batch mode (24 idempotent steps with transaction rollback), Quick Setup Wizard, configuration export/import, HTML reports, JSON audit logging with rotation -**Monitoring** -- 197 CLI actions with JSON output for fleet automation, `ServerScore` (unified 0-100 health grade), `HealthDashboard` (all-in-one monitoring endpoint), `ClusterHealthScore`, `StorageHealthScore`, System Center (SCCM/SCOM/WAC) + Azure AD/Intune integration +**Monitoring** -- 198 CLI actions with JSON output for fleet automation, `ServerScore` (unified 0-100 health grade), `HealthDashboard` (all-in-one monitoring endpoint), `ClusterHealthScore`, `StorageHealthScore`, System Center (SCCM/SCOM/WAC) + Azure AD/Intune integration **Cloud & Security** -- Azure Arc server onboarding (install the Connected Machine Agent, connect the host to Azure's hybrid management plane via service-principal auth); Microsoft Defender for Endpoint onboarding (activate the built-in EDR sensor against your tenant, with a built-in detection test) @@ -439,7 +439,7 @@ $report.Issues **Tiers:** `Light` (minimal, safe for prod), `Standard` (recommended), `Aggressive` (maximum cleanup/debloat). -### 197 CLI Actions +### 198 CLI Actions | Category | Actions | |----------|---------| diff --git a/RackStack.ps1 b/RackStack.ps1 index b4d7376..ea5b979 100644 --- a/RackStack.ps1 +++ b/RackStack.ps1 @@ -13,7 +13,7 @@ Environment-specific settings are configured via defaults.json. .VERSION - 1.115.0 + 1.116.0 .NOTES - Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing) diff --git a/RackStack.psd1 b/RackStack.psd1 index 5148451..69f8856 100644 --- a/RackStack.psd1 +++ b/RackStack.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'RackStack.psm1' - ModuleVersion = '1.115.0' + ModuleVersion = '1.116.0' GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91' Author = 'TheAbider' CompanyName = 'TheAbider' diff --git a/Tests/Run-Tests.ps1 b/Tests/Run-Tests.ps1 index 07679ef..3c5c296 100644 --- a/Tests/Run-Tests.ps1 +++ b/Tests/Run-Tests.ps1 @@ -9198,6 +9198,41 @@ catch { Write-TestResult "Network Throughput Benchmark Tests" $false $_.Exception.Message } +# ============================================================================ +# SECTION 183: NTP CLOCK-TAMPER PROTECTION (v1.116.0, 19-NTPConfiguration) +# ============================================================================ +Write-SectionHeader "SECTION 183: NTP CLOCK-TAMPER PROTECTION (19-NTPConfiguration)" + +try { + $ntpHC = Get-Content "$modulesPath\19-NTPConfiguration.ps1" -Raw + Write-TestResult "19-NTP: Get-NtpHardeningStatus exists" ($ntpHC -match 'function\s+Get-NtpHardeningStatus\b') + Write-TestResult "19-NTP: Set-NtpClockTamperProtection exists" ($ntpHC -match 'function\s+Set-NtpClockTamperProtection\b') + Write-TestResult "19-NTP: Show-NtpClockHardening exists" ($ntpHC -match 'function\s+Show-NtpClockHardening\b') + Write-TestResult "19-NTP: Start-NtpHardeningAudit exists" ($ntpHC -match 'function\s+Start-NtpHardeningAudit\b') + # Targets the real W32Time phase-correction knobs. + Write-TestResult "19-NTP: bounds MaxPos/MaxNegPhaseCorrection" ($ntpHC -match 'MaxPosPhaseCorrection' -and $ntpHC -match 'MaxNegPhaseCorrection') + # Decodes the unbounded 0xFFFFFFFF default. + Write-TestResult "19-NTP: decodes unbounded (0xFFFFFFFF) limit" ($ntpHC -match '\[uint32\]::MaxValue' -and $ntpHC -match 'Unbounded') + # Reversible: captures prior values and registers an undo. + Write-TestResult "19-NTP: clock-tamper protection is reversible (undo)" ($ntpHC -match 'function\s+Set-NtpClockTamperProtection[\s\S]{0,3500}Add-UndoAction') + # Dry-Run aware, not one-way (it is reversible). + Write-TestResult "19-NTP: clock-tamper protection is Dry-Run aware (reversible)" ($ntpHC -match 'Set-NtpClockTamperProtection[\s\S]{0,1500}Push-DryRunStep[\s\S]{0,400}-OneWay \$false') + # Honest platform note: no standalone symmetric-key file on Windows. + Write-TestResult "19-NTP: notes no standalone keys file (MS-SNTP/domain)" ($ntpHC -match 'no standalone symmetric-key file' -or $ntpHC -match 'MS-SNTP') + # Audit CLI is JSON-aware. + Write-TestResult "19-NTP: NtpHardeningAudit JSON-aware" ($ntpHC -match "Start-NtpHardeningAudit[\s\S]{0,300}CLIOutputFormat -eq 'JSON'") + # Menu wiring. + Write-TestResult "19-NTP: menu item [8] present" ($ntpHC -match '\[8\]\s*Clock-Tamper Protection') + Write-TestResult "19-NTP: dispatch case 8 wired" ($ntpHC -match '"8"\s*\{\s*Show-NtpClockHardening') + $ntpEntry = Get-Content "$modulesPath\50-EntryPoint.ps1" -Raw + Write-TestResult "50-EntryPoint: NtpHardeningAudit dispatch case" ($ntpEntry -match "'NtpHardeningAudit'\s*\{") + $ntpHeader = Get-Content (Join-Path $script:ModuleRoot "Header.ps1") -Raw + Write-TestResult "Header.ps1: NtpHardeningAudit in -Action ValidateSet" ($ntpHeader -match "'NtpHardeningAudit'") +} +catch { + Write-TestResult "NTP Clock-Tamper Protection Tests" $false $_.Exception.Message +} + # ============================================================================ # SECTION 174: DOCUMENTATION FRESHNESS (counts must match the codebase) # ============================================================================ @@ -9491,7 +9526,7 @@ Write-TestResult "32-Dedup: specific invalid msg" ($ddContent -match 'Enter 1-5 # NTP nav check and error message $ntpContent = Get-Content -LiteralPath "$modulesPath\19-NTPConfiguration.ps1" -Raw Write-TestResult "19-NTP: custom server nav check" ($ntpContent -match 'NTP server address[\s\S]{0,80}Test-NavigationCommand') -Write-TestResult "19-NTP: specific invalid msg" ($ntpContent -match 'Enter 1-7 or B') +Write-TestResult "19-NTP: specific invalid msg" ($ntpContent -match 'Enter 1-8 or B') # StorageReplica nav checks $srContent = Get-Content -LiteralPath "$modulesPath\33-StorageReplica.ps1" -Raw @@ -13209,7 +13244,7 @@ try { # Action list in -ListActions block has 160 entries $listBlock = [regex]::Match($ep5, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $listActionCount = @([regex]::Matches($listBlock, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 197 entries" ($listActionCount -eq 197) "Found $listActionCount" + Write-TestResult "50-EntryPoint: action list has 198 entries" ($listActionCount -eq 198) "Found $listActionCount" } catch { Write-TestResult "v1.91.0 Tests" $false $_.Exception.Message } @@ -13242,7 +13277,7 @@ try { # Action list count (should be 167 now) $listBlock2 = [regex]::Match($ep6, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $actionCount2 = @([regex]::Matches($listBlock2, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 197 entries" ($actionCount2 -eq 197) "Found $actionCount2" + Write-TestResult "50-EntryPoint: action list has 198 entries" ($actionCount2 -eq 198) "Found $actionCount2" } catch { Write-TestResult "v1.92.0 Tests" $false $_.Exception.Message } @@ -13268,7 +13303,7 @@ try { # Action count updated $listBlock3 = [regex]::Match($ep7, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $actionCount3 = @([regex]::Matches($listBlock3, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 197 entries" ($actionCount3 -eq 197) "Found $actionCount3" + Write-TestResult "50-EntryPoint: action list has 198 entries" ($actionCount3 -eq 198) "Found $actionCount3" } catch { Write-TestResult "v1.93.0 Tests" $false $_.Exception.Message } @@ -13306,7 +13341,7 @@ try { # Action list count $listBlock4 = [regex]::Match($ep8, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $actionCount4 = @([regex]::Matches($listBlock4, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 197 entries" ($actionCount4 -eq 197) "Found $actionCount4" + Write-TestResult "50-EntryPoint: action list has 198 entries" ($actionCount4 -eq 198) "Found $actionCount4" } catch { Write-TestResult "v1.94.1 Tests" $false $_.Exception.Message } diff --git a/dist/chocolatey/rackstack.nuspec b/dist/chocolatey/rackstack.nuspec index bcc9cc0..3099311 100644 --- a/dist/chocolatey/rackstack.nuspec +++ b/dist/chocolatey/rackstack.nuspec @@ -18,7 +18,7 @@ windows-server hyper-v iscsi clustering powershell sysadmin automation msp admin-tools PowerShell automation toolkit for configuring Windows Server hosts -RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." 197 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, and batch automation, all with undo, transaction rollback, and audit logging. +RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." 198 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, and batch automation, all with undo, transaction rollback, and audit logging. Built for MSPs, sysadmins, and infrastructure teams who build servers repeatedly and want it done right every time. diff --git a/dist/scoop/rackstack.json b/dist/scoop/rackstack.json index fb40954..71c087a 100644 --- a/dist/scoop/rackstack.json +++ b/dist/scoop/rackstack.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", "version": "0.0.0", - "description": "PowerShell automation toolkit for configuring Windows Server hosts — Hyper-V virtualization hosts, failover cluster nodes, iSCSI storage clients, Active Directory members, and standalone servers. Ships as a code-signed EXE plus a PowerShell Gallery wrapper module exposing 197 structured CLI actions.", + "description": "PowerShell automation toolkit for configuring Windows Server hosts — Hyper-V virtualization hosts, failover cluster nodes, iSCSI storage clients, Active Directory members, and standalone servers. Ships as a code-signed EXE plus a PowerShell Gallery wrapper module exposing 198 structured CLI actions.", "homepage": "https://github.com/TheAbider/RackStack", "license": "MIT", "url": "https://github.com/TheAbider/RackStack/releases/download/v0.0.0/RackStack.exe", diff --git a/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml b/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml index d855395..5342242 100644 --- a/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml +++ b/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml @@ -13,7 +13,7 @@ Copyright: Copyright (c) 2026 TheAbider ShortDescription: PowerShell automation toolkit for configuring Windows Server hosts. Description: |- RackStack is a menu-driven PowerShell tool that automates everything between - "Windows is installed" and "server is in production." It provides 197 CLI + "Windows is installed" and "server is in production." It provides 198 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, cloud onboarding, and batch automation, all with undo, transaction rollback, and audit logging. Built for MSPs, sysadmins,