diff --git a/.github/workflows/eventlog-settings.yml b/.github/workflows/eventlog-settings.yml new file mode 100644 index 00000000..37e975bd --- /dev/null +++ b/.github/workflows/eventlog-settings.yml @@ -0,0 +1,25 @@ +name: Event-log settings regressions +on: + push: + branches: ['**'] + pull_request: + workflow_dispatch: +permissions: + contents: read +jobs: + eventlog-settings: + runs-on: windows-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Safe event-log fixtures in Windows PowerShell 5.1 + shell: powershell + run: ./tests/EventLogSettings.Tests.ps1 + - name: Safe event-log fixtures in PowerShell 7 + shell: pwsh + run: ./tests/EventLogSettings.Tests.ps1 + - name: Read-only Windows event-log smoke in Windows PowerShell 5.1 + shell: powershell + run: ./tests/EventLogSettings.Windows.Tests.ps1 + - name: Read-only Windows event-log smoke in PowerShell 7 + shell: pwsh + run: ./tests/EventLogSettings.Windows.Tests.ps1 diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 47edb38e..661e8b98 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -4,6 +4,8 @@ **改善:** +- イベントログのサイズ監査と設定に共通のバイト単位プロファイルを導入し、AppLocker・ファイアウォールログの256 MiB、Setupの32 MiB、ASD推奨のSecurityログ2048 MiBに対応した。`-LogProfile`と`configure-eventlogs`で送信元と収集サーバーのサイズ・保存方式を選択できる。明示的に指定しない限り、既存の大きいバッファと保存方式は維持する。結果には検証した設定を記録し、未測定の保存日数は不明と表示する。 (issue #379) (@Shirofune-Security) + - `audit-settings`、`plan`、`configure`で共有するバージョン付きの詳細監査ポリシープロファイルを追加した。59のサブカテゴリと14のプロファイルで、WELA、文書に基づくWindows既定値、Microsoft、確認済みのCIS v4.0.0、ASDのWindows標準機能向け監査ガイドに対応する。ホストの役割とビルドの検証、オフラインでの設定計画、出典と前提条件を含むJSON出力をサポートする。完全一致、最低限、任意、変更なし、未構成、適用対象外を区別する。Windows既定値は参照専用で、プロファイルの対象はSecurityログの詳細監査ポリシーに限定される。 (#390) (@Shirofune-Security) - WELAのプロファイルにWindows標準の監査サブカテゴリを6つ追加した。Group MembershipとAuthorization Policy Changeは成功、Application Group Management、MPSSVC Rule-Level Policy Change、IPsec Driver、Kernel Objectは成功と失敗を監査する。各ガイドに対応するプロファイルでは、それぞれの監査設定と前提条件を維持する。Kernel Objectのイベント生成には対象オブジェクトに適切なSACLが必要であり、この変更ではそのSACLを作成しない。 (#391) (@Shirofune-Security) - `configure`と`configure -Profile`に`-DryRun`と`-ResultsPath`を追加し、Windows設定を変更せずに変更内容を確認し、設定項目ごとの結果をJSONで出力できるようにした。`-DryRun`に対応していないコマンドは、実行前にエラーで停止する。 (#392) (@Shirofune-Security) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61df55f..9eac821e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ **Improvements:** +- Unified event-log size auditing and configuration with shared byte-based profiles, including 256 MiB AppLocker/firewall logs, 32 MiB Setup and ASD 2048 MiB Security. Added separate source and collector size/mode choices through `-LogProfile` and `configure-eventlogs`; larger buffers and existing retention modes are preserved unless explicitly changed. Results include verified state and unknown retention duration. (issue #379) (@Shirofune-Security) + - Added versioned advanced audit-policy profiles shared by `audit-settings`, `plan` and `configure`: 59 subcategories and 14 profiles covering WELA, documented Windows defaults, Microsoft, reviewed CIS v4.0.0 and ASD native guidance. Profiles support role/build validation, offline planning and JSON exports with sources and prerequisites. Exact, minimum, optional, unchanged, Not Configured and not-applicable settings remain distinct. Windows defaults are reference-only; profile scope is advanced Security audit policy. (#390) (@Shirofune-Security) - Added six native Windows audit subcategories to WELA's profile: Group Membership and Authorization Policy Change (Success), plus Application Group Management, MPSSVC Rule-Level Policy Change, IPsec Driver and Kernel Object (Success and Failure). Source-specific profiles retain their own audit settings and prerequisites; Kernel Object events require matching object SACLs, which this change does not create. (#391) (@Shirofune-Security) - Added `-DryRun` and `-ResultsPath` to `configure` and `configure -Profile` to preview changes without modifying Windows settings and export per-control results as JSON. Commands that do not support `-DryRun` reject it before running. (#392) (@Shirofune-Security) diff --git a/WELA.ps1 b/WELA.ps1 index 2b78f3df..c0a8b9d5 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -4,6 +4,9 @@ [switch]$Debug, [string]$Baseline, [string]$Profile, + [string]$LogProfile, + [switch]$ResizeLogs, + [switch]$ApplyLogMode, [ValidateSet("Client", "MemberServer", "DomainController", "ADCS")][string]$Role, [int]$Build, [string]$PlanPath, @@ -29,6 +32,8 @@ $AuditpolTxtPath = Join-Path $ScriptRoot "auditpol.txt" $SaclTargetsPath = Join-Path $ScriptRoot "config/audit_sacl_targets.json" . (Join-Path $ScriptRoot "scripts/Configuration.ps1") Import-Module (Join-Path $ScriptRoot "modules/AuditProfiles.psm1") -ErrorAction Stop +Import-Module (Join-Path $ScriptRoot "modules/EventLogSettings.psm1") -ErrorAction Stop +. (Join-Path $ScriptRoot "scripts/EventLogConfiguration.ps1") # 64bit の PowerShell と GPO が読むのは Wow6432Node の無いパス。32bit 用に両方を扱う。 $PowerShellPolicyRoots = @( @@ -954,119 +959,15 @@ function Export-MitreHeatmap { function AuditFileSize { - # 推奨サイズはベースラインによらず共通のため、パラメータは取らない - if (-not (TestWindows)) { - Write-Host "[ERROR] 'audit-filesize' reads Windows event logs and can only run on Windows." -ForegroundColor Red - return - } - - # 対象のイベントログ名をハッシュテーブル化 - $logNames = @{ - "Application" = @("20 MB", "128 MB+") - "Microsoft-Windows-AppLocker/EXE and DLL" = @("1 MB", "256 MB+") - "Microsoft-Windows-AppLocker/MSI and Script" = @("1 MB", "256 MB+") - "Microsoft-Windows-AppLocker/Packaged app-Deployment" = @("1 MB", "256 MB+") - "Microsoft-Windows-AppLocker/Packaged app-Execution" = @("1 MB", "256 MB+") - "Microsoft-Windows-Bits-Client/Analytic" = @("1 MB", "128 MB+") - "Microsoft-Windows-Bits-Client/Operational" = @("1 MB", "128 MB+") - "Microsoft-Windows-CodeIntegrity/Operational" = @("1 MB", "128 MB+") - "Microsoft-Windows-Crypto-DPAPI/Debug" = @("1 MB", "128 MB+") - "Microsoft-Windows-DFSN-Server/Admin" = @("1 MB", "128 MB+") - "Microsoft-Windows-DriverFrameworks-UserMode/Operational" = @("1 MB", "128 MB+") - "Microsoft-Windows-NTLM/Operational" = @("1 MB", "128 MB+") - "Microsoft-Windows-PowerShell/Operational" = @("15 MB", "256 MB+") - "Microsoft-Windows-PrintService/Admin" = @("1 MB", "128 MB+") - "Microsoft-Windows-PrintService/Operational" = @("1 MB", "128 MB+") - "Microsoft-Windows-Security-Mitigations/KernelMode" = @("1 MB", "128 MB+") - "Microsoft-Windows-Security-Mitigations/UserMode" = @("1 MB", "128 MB+") - "Microsoft-Windows-SmbClient/Security" = @("8 MB", "128 MB+") - "Microsoft-Windows-TaskScheduler/Operational" = @("1 MB", "128 MB+") - "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational" = @("1 MB", "128 MB+") - "Microsoft-Windows-Windows Defender/Operational" = @("16MB", "128 MB+") - "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall" = @("1 MB", "256 MB+") - "Microsoft-Windows-WMI-Activity/Operational" = @("1 MB", "128 MB+") - "Security" = @("20 MB", "256 MB+") - "System" = @("20 MB", "128 MB+") - "Windows PowerShell" = @("15 MB", "256 MB+") - } - - $results = @() - - $missingLogs = @() - - foreach ($logName in $logNames.Keys | Sort-Object) { - # 存在しないログ(役割やOSエディションによる)で全体を止めない - $logInfo = Get-WinEvent -ListLog $logName -ErrorAction SilentlyContinue - if (-not $logInfo) { - $missingLogs += $logName - continue - } - $maxLogSize = [math]::Floor($logInfo.MaximumSizeInBytes / 1MB) - $recommendedSize = [int]($logNames[$logName][1] -replace " MB\+?", "") - # ローテーション直前までファイルは上限まで育つので、95%以上を「ほぼ満杯」とみなす - $logIsFull = $logInfo.MaximumSizeInBytes -gt 0 -and - $logInfo.FileSize -ge ($logInfo.MaximumSizeInBytes * 0.95) - $logMode = if ($logInfo.LogMode -eq "Retain") { "NoOverwrite" } else { $logInfo.LogMode } - $correctSetting = if ($maxLogSize -ge $recommendedSize -and $logMode -ne "NoOverwrite") { "Y" } else { "N" } - - $results += [PSCustomObject]@{ - LogFile = Split-Path $logInfo.LogFilePath -Leaf - CurrentLogSize = "{0:N2} MB" -f ($logInfo.FileSize / 1MB) - MaxLogSize = "$maxLogSize MB" - Default = $logNames[$logName][0] - Recommended = $logNames[$logName][1] - IsLogFull = $logIsFull - LogMode = $logMode - CorrectSetting = $correctSetting - } - } - - # Format-Tableには色つき出力の機能はないので、Write-Hostで色をつける - $tableLayout = "{0,-75} {1,-15} {2,-10} {3,-10} {4,-15} {5,-10} {6,-15} {7,-10}" - Write-Host ($tableLayout -f ` - "Log File", ` - "Current Size", ` - "Max Size", ` - "Default", ` - "Recommended", ` - "Is Full", ` - "Log Mode", ` - "Correct Setting" ` - ) - Write-Host ($tableLayout -f ` - "--------", ` - "------------", ` - "--------", ` - "------", ` - "-----------", ` - "-------", ` - "--------", ` - "--------------" ` - ) - foreach ($result in $results) { - $color = if ($result.CorrectSetting -eq "Y") { "Green" } else { "Red" } - Write-Host ($tableLayout -f ` - $result.LogFile, ` - $result.CurrentLogSize, ` - $result.MaxLogSize, ` - $result.Default, ` - $result.Recommended, ` - $result.IsLogFull, ` - $result.LogMode, ` - $result.CorrectSetting ` - ) -ForegroundColor $color - } - - if ($missingLogs.Count -gt 0) { - Write-Host "" - Write-Host "Skipped $($missingLogs.Count) log(s) that do not exist on this machine:" -ForegroundColor DarkYellow - $missingLogs | ForEach-Object { Write-Host " - $_" -ForegroundColor DarkYellow } - } - + param([string]$LogProfile = 'wela-source-2.2.0') + if (-not (TestWindows)) { throw "'audit-filesize' reads Windows event logs and can only run on Windows." } + $results = @(Get-WelaEventLogAudit -Profile $LogProfile) + $results | Format-Table Log, ReadStatus, CurrentMaximumMiB, MinimumBytes, SizeStatus, CurrentMode, RecommendedMode, ModeStatus -AutoSize | Out-Host + Write-Host 'Sizes use exact bytes (MiB = 1048576 bytes). Retention days: Unknown; measure event volume and verify collection/archive storage.' + Write-Host 'Mode recommendations are separate from size compliance. configure-eventlogs changes modes only with -ApplyLogMode.' $fileSizeCsv = Join-Path $script:ScriptRoot "WELA-FileSize-Result.csv" - $results | Export-Csv -Path $fileSizeCsv -NoTypeInformation - Write-Host "" - Write-Host "Audit file size result saved to: $fileSizeCsv" + $results | Export-Csv -LiteralPath $fileSizeCsv -NoTypeInformation -Encoding UTF8 -ErrorAction Stop + Write-Host "Event-log audit saved to: $fileSizeCsv" } @@ -1428,37 +1329,8 @@ function ConfigureAuditSettings { $context = New-WelaConfigurationContext -Auto:$Auto -DryRun:$DryRun -BackupPath $BackupPath if (-not $DryRun) { Write-Host "Recovery journal: $($context.BackupPath)" } - foreach ($log in @('Security', 'Microsoft-Windows-PowerShell/Operational', 'Windows PowerShell')) { - Set-WelaEventLogControl -Context $context -Log $log -Property MaximumSizeInBytes -Desired 1073741824 - } - $mediumLogs = @( - "System", - "Application", - "Microsoft-Windows-Windows Defender/Operational", - "Microsoft-Windows-Bits-Client/Operational", - "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall", - "Microsoft-Windows-NTLM/Operational", - "Microsoft-Windows-Security-Mitigations/KernelMode", - "Microsoft-Windows-Security-Mitigations/UserMode", - "Microsoft-Windows-PrintService/Admin", - "Microsoft-Windows-PrintService/Operational", - "Microsoft-Windows-SmbClient/Security", - "Microsoft-Windows-AppLocker/MSI and Script", - "Microsoft-Windows-AppLocker/EXE and DLL", - "Microsoft-Windows-AppLocker/Packaged app-Deployment", - "Microsoft-Windows-AppLocker/Packaged app-Execution", - "Microsoft-Windows-CodeIntegrity/Operational", - "Microsoft-Windows-Crypto-DPAPI/Debug", - "Microsoft-Windows-Diagnosis-Scripted/Operational", - "Microsoft-Windows-DriverFrameworks-UserMode/Operational", - "Microsoft-Windows-WMI-Activity/Operational", - "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational", - "Microsoft-Windows-TaskScheduler/Operational" - ) - - foreach ($log in $mediumLogs) { - Set-WelaEventLogControl -Context $context -Log $log -Property MaximumSizeInBytes -Desired 134217728 - } + # Audit and configure consume the same default size thresholds; modes stay unchanged. + Set-WelaEventLogProfileControls -Context $context -Profile 'wela-source-2.2.0' foreach ($log in @('Microsoft-Windows-TaskScheduler/Operational', 'Microsoft-Windows-DriverFrameworks-UserMode/Operational', 'Microsoft-Windows-Crypto-DPAPI/Debug')) { Set-WelaEventLogControl -Context $context -Log $log -Property IsEnabled -Desired $true } @@ -1758,7 +1630,10 @@ Usage: # -Profile changes advanced audit policy ONLY. Optional controls need -IncludeOptional. ./WELA.ps1 audit-settings -Baseline YamatoSecurity # Audit current setting and show in stdout, save to csv ./WELA.ps1 audit-settings -Baseline ASD -OutType gui # Audit current setting and show in gui, save to csv - ./WELA.ps1 audit-filesize -Baseline YamatoSecurity # Audit current file size and show in stdout, save to csv + ./WELA.ps1 eventlog-profiles # List size/mode profiles (separate from -Profile) + ./WELA.ps1 audit-filesize -LogProfile wela-source-2.2.0 # Audit live sizes/modes, save to CSV + ./WELA.ps1 configure-eventlogs -LogProfile asd-source-2021-10 -DryRun + ./WELA.ps1 configure-eventlogs -LogProfile asd-collector-archive-2021-10 -ApplyLogMode # Explicit archive choice ./WELA.ps1 configure -Baseline YamatoSecurity # Configure audit settings based on the specified baseline ./WELA.ps1 configure -Baseline YamatoSecurity -Auto # Configure audit settings automatically without prompts ./WELA.ps1 configure-sacl # Add targeted File System/Registry audit SACLs (ASEP keys + sensitive files) needed by the rules, without global auditing @@ -1776,8 +1651,17 @@ Write-Host "WELA v$WELAVersion - $WELAReleaseName" Write-Host "" # Reject unsupported dry-run requests before reaching any command's mutation path. -if ($DryRun -and $Cmd -ne 'configure') { - throw "-DryRun is supported only by configure (including configure -Profile). No command was run." +if ($DryRun -and $Cmd -notin @('configure', 'configure-eventlogs')) { + throw "-DryRun is supported only by configure (including configure -Profile) and configure-eventlogs. No command was run." +} +if ($Profile -and $Cmd -in @('eventlog-profiles', 'audit-filesize', 'configure-eventlogs')) { + throw '-Profile selects advanced audit policy only. Use -LogProfile for event-log size/mode settings.' +} +if ($LogProfile -and $Cmd -notin @('audit-filesize', 'configure-eventlogs')) { + throw '-LogProfile is supported only by audit-filesize and configure-eventlogs.' +} +if (($ResizeLogs -or $ApplyLogMode) -and $Cmd -ne 'configure-eventlogs') { + throw '-ResizeLogs and -ApplyLogMode require configure-eventlogs. No command was run.' } if ($Profile -and $Cmd.ToLower() -in @('plan', 'audit', 'audit-settings', 'configure') -and -not $Help) { @@ -1810,21 +1694,38 @@ switch ($Cmd.ToLower()) { } AuditLogSetting -outType $OutType -Baseline $Baseline -debug:$Debug } + "eventlog-profiles" { + (Import-WelaEventLogProfiles).profiles | Select-Object id, kind, scope, note | Format-List + } "audit-filesize" { - if ($Help){ - Write-Host "Audit current Windows Event Log file sizes" - Write-Host "" - Write-Host "Usage: ./WELA.ps1 audit-filesize" - Write-Host "" - Write-Host "Note: the recommended sizes are the same for every baseline, so -Baseline is not required." - Write-Host "" + if ($Help) { + Write-Host 'Usage: ./WELA.ps1 audit-filesize [-LogProfile ]' + Write-Host 'Read live sizes and retention modes; list IDs with eventlog-profiles. Default: wela-source-2.2.0.' return } - if (-not [string]::IsNullOrEmpty($Baseline) -and $Baseline -ne "YamatoSecurity") { - Write-Host "Note: audit-filesize uses the same recommended sizes for every baseline; '-Baseline $Baseline' is ignored." -ForegroundColor DarkYellow - Write-Host "" + if ($Baseline -and $Baseline -ne 'YamatoSecurity') { throw 'Use -LogProfile for source-specific event-log sizes; -Baseline does not select a log profile.' } + if (-not $LogProfile) { $LogProfile = 'wela-source-2.2.0' } + AuditFileSize -LogProfile $LogProfile + } + "configure-eventlogs" { + if ($Help) { + Write-Host 'Usage: ./WELA.ps1 configure-eventlogs [-LogProfile ] [-ApplyLogMode] [-ResizeLogs] [-Auto] [-DryRun] [-BackupPath ] [-ResultsPath ]' + Write-Host 'Default: wela-source-2.2.0, minimum sizes, modes unchanged. -ResizeLogs explicitly permits shrinking; -ApplyLogMode explicitly applies circular source or collector archive behavior.' + Write-Host 'This command changes only event-log size/mode. It does not enable channels, configure forwarding or establish retention days.' + return + } + if ($Baseline) { throw 'configure-eventlogs uses -LogProfile, not -Baseline.' } + if (-not (TestWindows)) { throw 'configure-eventlogs requires Windows.' } + if (-not (TestAdministrator)) { throw 'configure-eventlogs requires Administrator privileges.' } + if (-not $LogProfile) { $LogProfile = 'wela-source-2.2.0' } + try { + $report = Invoke-WelaEventLogConfiguration -Profile $LogProfile -Auto:$Auto -DryRun:$DryRun -ResizeLogs:$ResizeLogs -ApplyLogMode:$ApplyLogMode -BackupPath $BackupPath -ResultsPath $ResultsPath + $report + if ($report.ExitCode -ne 0) { exit $report.ExitCode } + } catch { + Write-Host "[Failed] Event-log configuration aborted: $_" -ForegroundColor Red + exit 1 } - AuditFileSize } "configure" { diff --git a/config/eventlog_profiles.json b/config/eventlog_profiles.json new file mode 100644 index 00000000..eba32f32 --- /dev/null +++ b/config/eventlog_profiles.json @@ -0,0 +1,383 @@ +{ + "schemaVersion": 1, + "sources": { + "wela": { + "title": "WELA 2.2.0 logging choices", + "url": "https://github.com/Yamato-Security/WELA/issues/379" + }, + "cis-v4": { + "title": "CIS Windows 11 Enterprise / Windows Server 2022 v4.0.0 (historical reviewed editions)", + "url": "https://www.cisecurity.org/benchmark/microsoft_windows_desktop", + "note": "Event Log Service size and full-log behavior recommendations only; effective local values do not establish configured GPO or full benchmark compliance." + }, + "asd-2021": { + "title": "ASD Windows event logging and forwarding, October 2021", + "url": "https://www.cyber.gov.au/business-government/detecting-responding-to-threats/event-logging/windows-event-logging-and-forwarding" + }, + "ms-wevtutil": { + "title": "Microsoft wevtutil reference", + "url": "https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/wevtutil" + } + }, + "profiles": [ + { + "id": "wela-source-2.2.0", + "kind": "source", + "scope": "event-log-size-and-mode-only", + "note": "Default configure/audit-filesize model. Circular mode is a recommendation; applying it requires -ApplyLogMode. Sizes are minima unless -ResizeLogs is explicit.", + "controls": [ + { + "log": "Application", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-AppLocker/EXE and DLL", + "minimumBytes": 268435456, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-AppLocker/MSI and Script", + "minimumBytes": 268435456, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-AppLocker/Packaged app-Deployment", + "minimumBytes": 268435456, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-AppLocker/Packaged app-Execution", + "minimumBytes": 268435456, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-Bits-Client/Analytic", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-Bits-Client/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-CodeIntegrity/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-Crypto-DPAPI/Debug", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-DFSN-Server/Admin", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-DriverFrameworks-UserMode/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-NTLM/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-PowerShell/Operational", + "minimumBytes": 1073741824, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-PrintService/Admin", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-PrintService/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-Security-Mitigations/KernelMode", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-Security-Mitigations/UserMode", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-SmbClient/Security", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-TaskScheduler/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-Windows Defender/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall", + "minimumBytes": 268435456, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Microsoft-Windows-WMI-Activity/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Security", + "minimumBytes": 1073741824, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "System", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Windows PowerShell", + "minimumBytes": 1073741824, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "WELA 2.2.0 shared size model; preserves prior configure 1024 MiB thresholds and fixes audit/configure mismatches." + }, + { + "log": "Setup", + "minimumBytes": 33554432, + "mode": "Circular", + "sourceIds": [ + "cis-v4" + ], + "evidence": "CIS v4.0.0 18.10.26.3.2: Setup >=32768 KiB; Windows 11 p.1047, Server 2022 p.849." + }, + { + "log": "Microsoft-Windows-Diagnosis-Scripted/Operational", + "minimumBytes": 134217728, + "mode": "Circular", + "sourceIds": [ + "wela" + ], + "evidence": "Retains the prior WELA configure 128 MiB setting; now shared with size auditing." + } + ] + }, + { + "id": "cis-v4-source", + "kind": "source", + "scope": "event-log-size-and-mode-only", + "note": "Reviewed Windows 11 Enterprise and Server 2022 v4.0.0 effective log settings only; not a GPO compliance assessment.", + "controls": [ + { + "log": "Application", + "minimumBytes": 33554432, + "mode": "Circular", + "sourceIds": [ + "cis-v4" + ], + "evidence": "Client v4.0.0 18.10.26.1.1 p.1035; Client v4.0.0 18.10.26.1.2 p.1037; Server v4.0.0 18.10.26.1.1 p.837; Server v4.0.0 18.10.26.1.2 p.839. Size minimum; full-log retention Disabled (overwrite)." + }, + { + "log": "Security", + "minimumBytes": 201326592, + "mode": "Circular", + "sourceIds": [ + "cis-v4" + ], + "evidence": "Client v4.0.0 18.10.26.2.1 p.1040; Client v4.0.0 18.10.26.2.2 p.1042; Server v4.0.0 18.10.26.2.1 p.842; Server v4.0.0 18.10.26.2.2 p.844. Size minimum; full-log retention Disabled (overwrite)." + }, + { + "log": "Setup", + "minimumBytes": 33554432, + "mode": "Circular", + "sourceIds": [ + "cis-v4" + ], + "evidence": "Client v4.0.0 18.10.26.3.1 p.1045; Client v4.0.0 18.10.26.3.2 p.1047; Server v4.0.0 18.10.26.3.1 p.847; Server v4.0.0 18.10.26.3.2 p.849. Size minimum; full-log retention Disabled (overwrite)." + }, + { + "log": "System", + "minimumBytes": 33554432, + "mode": "Circular", + "sourceIds": [ + "cis-v4" + ], + "evidence": "Client v4.0.0 18.10.26.4.1 p.1050; Client v4.0.0 18.10.26.4.2 p.1052; Server v4.0.0 18.10.26.4.1 p.852; Server v4.0.0 18.10.26.4.2 p.854. Size minimum; full-log retention Disabled (overwrite)." + } + ] + }, + { + "id": "asd-source-2021-10", + "kind": "source", + "scope": "event-log-size-and-mode-only", + "note": "ASD source buffer sizes. Circular mode is an explicit WELA source-host operating choice; ASD collector archival is a separate profile.", + "controls": [ + { + "log": "Application", + "minimumBytes": 67108864, + "mode": "Circular", + "sourceIds": [ + "asd-2021", + "wela" + ], + "evidence": "ASD source size: Security 2097152 KiB; Application/System 65536 KiB. Circular source mode is a WELA operating choice." + }, + { + "log": "Security", + "minimumBytes": 2147483648, + "mode": "Circular", + "sourceIds": [ + "asd-2021", + "wela" + ], + "evidence": "ASD source size: Security 2097152 KiB; Application/System 65536 KiB. Circular source mode is a WELA operating choice." + }, + { + "log": "System", + "minimumBytes": 67108864, + "mode": "Circular", + "sourceIds": [ + "asd-2021", + "wela" + ], + "evidence": "ASD source size: Security 2097152 KiB; Application/System 65536 KiB. Circular source mode is a WELA operating choice." + } + ] + }, + { + "id": "asd-collector-archive-2021-10", + "kind": "collector", + "scope": "event-log-size-and-mode-only", + "note": "ForwardedEvents only. Requires a provisioned collector, secure archive storage, capacity monitoring and backup/move procedures. Does not create WEF subscriptions or guarantee retention duration.", + "controls": [ + { + "log": "ForwardedEvents", + "minimumBytes": 2147483648, + "mode": "AutoBackup", + "sourceIds": [ + "asd-2021" + ], + "evidence": "ASD collection server: 2097152 KiB; archive when full when not forwarding to a secure central logging facility." + } + ] + } + ] +} diff --git a/docs/configuration-results.md b/docs/configuration-results.md index 6f4fc9dc..2fdc720b 100644 --- a/docs/configuration-results.md +++ b/docs/configuration-results.md @@ -53,7 +53,7 @@ was already written. Each line of `before.jsonl` records the computer, timestamp, control identity, requested setting and exact pre-change state. Registry entries include whether the -key/value existed and the previous registry type. Event-log entries capture size or +key/value existed and the previous registry type. Event-log entries capture size, mode or enabled state; audit policies capture the numeric mask; CA entries also capture service state. A journal write failure prevents that control's mutation. The journal is per control, not a full system backup, and can contain records for failed @@ -70,7 +70,10 @@ GPO change and could interrupt certificate services. Before recovery: Do not blindly replay a journal or restore an entire audit policy backup. 3. Restore only the intended controls, normally in reverse application order: - **EventLog:** `wevtutil sl /ms:` or `/e:`. - Review shrinking buffers or disabling a channel before proceeding. + Profile size/mode entries include a complete state object; see + [event-log recovery](eventlog-settings.md#recovery) for mode flags and the + fresh `ImmediatePreWrite` journal record. Review shrinking buffers, changing + retention or disabling a channel before proceeding. - **AuditPolicy:** `auditpol /set /subcategory:{} /success: /failure:`. Previous mask bit 1 means success, bit 2 means failure. Restore that subcategory, not unrelated policy. @@ -146,8 +149,8 @@ selection behavior while adding shared execution and recovery reporting. paths without touching Windows policy, including exact/minimum behavior, concurrent flags, unknown-state preflight, reference-only defaults, metadata and dry runs. -`-DryRun` is supported only by `configure`, including its `-Profile` form. Other -commands reject the flag before dispatch, so `configure-sacl -DryRun` and +`-DryRun` is supported by `configure`, including its `-Profile` form, and by +`configure-eventlogs`. Other commands reject the flag before dispatch, so `configure-sacl -DryRun` and `update-rules -DryRun` cannot silently perform their normal mutations. Outgoing `PreserveOrAudit` checks the shared runner's fresh registry snapshot and diff --git a/docs/eventlog-settings.md b/docs/eventlog-settings.md new file mode 100644 index 00000000..2b5dc129 --- /dev/null +++ b/docs/eventlog-settings.md @@ -0,0 +1,138 @@ +# Event-log sizes and retention modes + +`audit-filesize` and ordinary `configure` now use the same +`config/eventlog_profiles.json` definitions. `-Profile` continues to select +**advanced audit policy only**. Use the separate `-LogProfile` option with +`audit-filesize` or `configure-eventlogs` for channel buffer and retention settings. + +```powershell +.\WELA.ps1 eventlog-profiles +.\WELA.ps1 audit-filesize -LogProfile wela-source-2.2.0 +.\WELA.ps1 configure-eventlogs -LogProfile asd-source-2021-10 -DryRun -ResultsPath .\log-plan.json +# Increase undersized buffers; preserve existing modes and larger buffers. +.\WELA.ps1 configure-eventlogs -LogProfile asd-source-2021-10 -Auto -ResultsPath .\log-results.json +# Explicit source circular overwrite choice. Mode changes require this separate flag. +.\WELA.ps1 configure-eventlogs -LogProfile cis-v4-source -ApplyLogMode +# Explicit collector archive choice, only on an already provisioned collector. +.\WELA.ps1 configure-eventlogs -LogProfile asd-collector-archive-2021-10 -ApplyLogMode +# Deliberately resize to profile values, including shrinking larger buffers. +.\WELA.ps1 configure-eventlogs -LogProfile cis-v4-source -ResizeLogs -DryRun +``` + +`-ResizeLogs` and `-ApplyLogMode` are accepted only by `configure-eventlogs`. +Neither is implied by `-Auto`. The command supports the same `-DryRun`, +`-BackupPath` and `-ResultsPath` semantics as ordinary configuration. It requires +Windows and an elevated shell. It changes only size and the explicitly selected +mode: it does not enable or disable channels, install sensors, configure an audit +subcategory, write policy registry keys, or provision event forwarding. + +| Log profile | Minimum sizes | Mode recommendation, applied only with `-ApplyLogMode` | +| --- | --- | --- | +| `wela-source-2.2.0` | Security and both PowerShell logs 1024 MiB; four AppLocker channels and firewall channel 256 MiB; Setup 32 MiB; remaining listed channels 128 MiB | Circular | +| `cis-v4-source` | Application, Setup, System 32 MiB; Security 192 MiB | Circular | +| `asd-source-2021-10` | Security 2048 MiB; Application and System 64 MiB | Circular (explicit WELA source-host choice, not an additional ASD prescription) | +| `asd-collector-archive-2021-10` | ForwardedEvents 2048 MiB | AutoBackup; collector only | + +The WELA list also includes the previously audit-only BITS Analytic and DFSN Admin +channels. Missing role-specific channels remain explicit failed observations; +they are not silently removed from the result. Debug/analytic channels may impose +provider restrictions on changing their configuration while enabled. WELA reports +such a native failure and does not disable a channel or clear events to work +around it. Profile selection is not an assertion that every channel is installed +on every Windows role or edition. + +The CIS values are from the reviewed **v4.0.0 Windows 11 Enterprise and Windows +Server 2022** editions, not the latest benchmark. This feature compares and changes +effective local channel values; it does not check the CIS requirement to configure +the corresponding Administrative Template policy or establish full baseline +compliance. GPO and MDM may restore different effective settings later. Source +identifiers and setting evidence are retained in JSON and audit CSV output. + +Sizes are compared as integer bytes. One MiB is 1,048,576 bytes; profile targets are +rounded upward to the Windows 65,536-byte unit so rounding cannot undershoot a +minimum. The default comparison is `observed >= target`. Larger buffers are never +shrunk unless `-ResizeLogs` is explicit. Before writing, WELA rechecks state after +operator confirmation, preserving any newly enlarged buffer in minimum mode. +Windows does not provide an atomic compare-and-set for channel settings, so a +later concurrent writer remains outside this guarantee. Changing retention or +shrinking a buffer can affect event availability; review the dry run and storage +requirements before opting in. + +`Circular` overwrites older events as the active buffer fills. `AutoBackup` +archives a full log and starts a new active log. `Retain` keeps existing events +and can discard incoming events when full. Audit output shows observed and +recommended modes separately from size compliance; omitting `-ApplyLogMode` +does not claim that a mismatching existing mode was corrected. + +**Retention days remain Unknown.** A capacity setting does not establish event +rate, archive survival, or a retention period. For collectors, first configure +and verify subscriptions, forwarding, archive ACLs, available disk space, +capacity alerts, and backup/move procedures. Archive files accumulate: this +feature neither deletes them nor manages their age. A disabled channel remains +disabled, and no event-production or Sigma-coverage increase is claimed. + +## Results and failures + +The audit reads `Get-WinEvent -ListLog` live. Missing channels, access denied and +other unreadable state stay explicit; they are never replaced by documentary +Windows defaults. The CSV contains exact observed bytes, minimum and rounded +target bytes, size status, observed/recommended modes, mode status, channel +enabled state, source evidence and any read diagnostic. Access failures are not +misreported as missing channels. Displaying MiB does not round the compliance +decision. + +Configuration uses the shared runner: journal before mutation, native exit-code +checks, immediate read-back, and a final drift check. Missing/unreadable channels, +ineffective changes and drift produce failed/overridden results and a nonzero +overall exit code while other selected channels are assessed. Successful size +configuration leaves no size warning for the same selected profile. Retention +warnings remain when the operator has not requested mode changes. `-DryRun` +performs no native setter calls and creates no recovery journal; an explicitly +requested JSON report may still be written. + +## Recovery + +Use a new protected `-BackupPath` and save the result JSON beside it. Do not use a +journal to overwrite a later intentional administrator or GPO change. For each +channel that needs recovery, review its current state and the latest journal +entry with `Phase: ImmediatePreWrite`: that record stores the fresh byte count and +mode observed immediately before the attempted write. The earlier runner entry +records the initial observation. A journal entry alone does not prove a successful +write; consult the result and live state. `BeforeWrite` is also in each result. + +From an elevated shell, restore only the reviewed settings with `wevtutil sl`: + +| Previous property | Restore arguments | +| --- | --- | +| Maximum size | `/ms:` | +| Circular | `/rt:false /ab:false` | +| AutoBackup | `/rt:true /ab:true` | +| Retain | `/rt:true /ab:false` | + +Check the native exit code and read back the channel afterward. Review shrinking +or changes to event overwrite behavior first. Restoring a size or mode does not +recover overwritten records; WELA does not clear logs or delete archive files. +For a failed write, the original settings may already remain in place. This is a +manual per-channel procedure, not an automated rollback or complete log backup. + +## Sources and validation + +- [Microsoft wevtutil](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/wevtutil) + documents byte sizing, 64 KiB units, retention and auto-backup flags. +- [ASD Windows event logging and forwarding (October 2021)](https://www.cyber.gov.au/business-government/detecting-responding-to-threats/event-logging/windows-event-logging-and-forwarding) + provides the source buffer values and separate collector/archive guidance. +- [CIS Microsoft Windows benchmark catalog](https://www.cisecurity.org/benchmark/microsoft_windows_desktop) + provides the benchmark access point; the values implemented here are the + historical v4.0.0 editions reviewed for issue #379, Event Log Service settings. + +`tests/EventLogSettings.Tests.ps1` uses safe channel/native fixtures and temporary +journals. It checks audit/configure agreement, preservation, explicit resize/mode +choices, rounding, missing/denied reads, concurrent growth, false-success writes, +journal failures and drift. Windows CI also runs +`tests/EventLogSettings.Windows.Tests.ps1`, which reads real channels and verifies +missing-channel handling without changing machine policy. Both run in Windows +PowerShell 5.1 and PowerShell 7. Real mutating behavior and ingestion still require +isolated Windows source/collector validation; these tests do not claim it. + +Release packaging already includes the complete `config`, `modules` and `scripts` +directories. Keep them beside `WELA.ps1`, including the new JSON and module. diff --git a/modules/EventLogSettings.psm1 b/modules/EventLogSettings.psm1 new file mode 100644 index 00000000..28208411 --- /dev/null +++ b/modules/EventLogSettings.psm1 @@ -0,0 +1,87 @@ +# Shared, native event-log size and retention model. Windows PowerShell 5.1 compatible. +function ConvertTo-WelaEventLogBytes { + param([ValidateRange(1048576, 2199023255552)][long]$Bytes) + # wevtutil uses 64 KiB units. Round UP so a minimum never becomes too small. + return [long]([math]::Ceiling($Bytes / 65536.0) * 65536) +} + +function Import-WelaEventLogProfiles { + param([string]$Path = (Join-Path $PSScriptRoot '../config/eventlog_profiles.json')) + $data = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop + if ($data.schemaVersion -ne 1) { throw 'Unsupported event-log profile schema.' } + $ids = @{} + foreach ($profile in $data.profiles) { + if (-not $profile.id -or $ids.ContainsKey($profile.id)) { throw 'Missing or duplicate event-log profile ID.' } + $ids[$profile.id] = $true + if ($profile.kind -notin @('source', 'collector') -or $profile.scope -ne 'event-log-size-and-mode-only') { throw "Invalid event-log profile: $($profile.id)" } + $logs = @{} + foreach ($control in $profile.controls) { + if (-not $control.log -or $control.log -match '[*?\[\]\r\n]' -or $logs.ContainsKey($control.log)) { throw "Invalid/duplicate event log: $($control.log)" } + $logs[$control.log] = $true + if ($control.mode -notin @('Circular', 'AutoBackup') -or -not $control.evidence) { throw "Invalid mode/evidence: $($control.log)" } + if ($control.minimumBytes -isnot [long] -and $control.minimumBytes -isnot [int]) { throw 'Log size must be an integer byte count.' } + $null = ConvertTo-WelaEventLogBytes $control.minimumBytes + if (@($control.sourceIds).Count -eq 0) { throw 'Event-log source evidence is required.' } + foreach ($id in $control.sourceIds) { + if (-not $data.sources.PSObject.Properties[$id]) { throw "Unknown event-log source: $id" } + } + } + if ($logs.Count -eq 0) { throw 'An event-log profile must contain controls.' } + } + return $data +} + +function Get-WelaEventLogProfile { + param([string]$Id = 'wela-source-2.2.0') + $data = Import-WelaEventLogProfiles + $profile = @($data.profiles | Where-Object { $_.id -eq $Id }) + if ($profile.Count -ne 1) { throw "Unknown log profile '$Id'. Use eventlog-profiles to list IDs." } + return $profile[0] +} + +function Get-WelaEventLogState { + param([string]$Log) + $state = [ordered]@{ + Log = $Log; ReadStatus = 'Unreadable'; MaximumSizeInBytes = $null + LogMode = $null; FileSize = $null; IsEnabled = $null; Diagnostic = '' + } + try { + $info = @(Get-WinEvent -ListLog $Log -ErrorAction Stop) + if ($info.Count -ne 1 -or $null -eq $info[0].MaximumSizeInBytes -or [long]$info[0].MaximumSizeInBytes -le 0) { throw 'A unique channel with a readable positive maximum size was not returned.' } + if ([string]$info[0].LogMode -notin @('Circular', 'AutoBackup', 'Retain')) { throw 'Channel retention mode is unknown.' } + $state.MaximumSizeInBytes = [long]$info[0].MaximumSizeInBytes + $state.LogMode = [string]$info[0].LogMode + $state.FileSize = $info[0].FileSize + $state.IsEnabled = $info[0].IsEnabled + $state.ReadStatus = 'Available' + } catch { + # Do not turn permission/provider failures into a claim that a channel is absent. + if ($_.FullyQualifiedErrorId -like 'NoMatchingLogsFound*' -or $_.Exception.GetType().FullName -eq 'System.Diagnostics.Eventing.Reader.EventLogNotFoundException') { $state.ReadStatus = 'Missing' } + $state.Diagnostic = $_.ToString() + } + return [pscustomobject]$state +} + +function Get-WelaEventLogAudit { + param([string]$Profile = 'wela-source-2.2.0', [scriptblock]$Read = { param($log) Get-WelaEventLogState -Log $log }) + $selected = Get-WelaEventLogProfile -Id $Profile + foreach ($control in $selected.controls) { + $current = & $Read $control.log + $available = $current.ReadStatus -eq 'Available' + $target = ConvertTo-WelaEventLogBytes $control.minimumBytes + [pscustomobject][ordered]@{ + Profile = $selected.id; Log = $control.log; ReadStatus = $current.ReadStatus + CurrentMaximumBytes = $current.MaximumSizeInBytes + CurrentMaximumMiB = $(if ($available) { $current.MaximumSizeInBytes / 1048576.0 } else { $null }) + MinimumBytes = [long]$control.minimumBytes; RoundedTargetBytes = $target + SizeStatus = $(if (-not $available) { 'Unknown' } elseif ($current.MaximumSizeInBytes -ge $target) { 'Compliant' } else { 'BelowMinimum' }) + CurrentMode = $current.LogMode; RecommendedMode = $control.mode + ModeStatus = $(if (-not $available) { 'Unknown' } elseif ($current.LogMode -eq $control.mode) { 'Compliant' } else { 'Different' }) + RetentionDays = 'Unknown'; IsEnabled = $current.IsEnabled + IsNearlyFull = $(if ($available -and $null -ne $current.FileSize) { $current.FileSize -ge ($current.MaximumSizeInBytes * 0.95) } else { $null }) + SourceIds = ($control.sourceIds -join ','); Evidence = $control.evidence; Diagnostic = $current.Diagnostic + } + } +} + +Export-ModuleMember -Function ConvertTo-WelaEventLogBytes, Import-WelaEventLogProfiles, Get-WelaEventLogProfile, Get-WelaEventLogState, Get-WelaEventLogAudit diff --git a/scripts/Configuration.ps1 b/scripts/Configuration.ps1 index d8baddde..fe94b23b 100644 --- a/scripts/Configuration.ps1 +++ b/scripts/Configuration.ps1 @@ -94,7 +94,7 @@ function Invoke-WelaConfigurationControl { function Complete-WelaConfiguration { param($Context, [string]$ResultsPath, $Plan, - [ValidateSet("native-windows-configuration", "advanced-audit-policy-only")] + [ValidateSet("native-windows-configuration", "advanced-audit-policy-only", "event-log-size-and-mode-only")] [string]$Scope = "native-windows-configuration") # A second read detects a value that was compliant earlier but changed during # this run. It does not establish whether GPO or another writer caused drift. diff --git a/scripts/EventLogConfiguration.ps1 b/scripts/EventLogConfiguration.ps1 new file mode 100644 index 00000000..153d3a98 --- /dev/null +++ b/scripts/EventLogConfiguration.ps1 @@ -0,0 +1,82 @@ +# Uses the same configuration runner as other native WELA settings. No closures. +function Set-WelaEventLogProfileControls { + param($Context, [string]$Profile = 'wela-source-2.2.0', [switch]$ResizeLogs, [switch]$ApplyLogMode) + $selected = Get-WelaEventLogProfile -Id $Profile + foreach ($control in $selected.controls) { + $state = @{ + Log = $control.log; Bytes = (ConvertTo-WelaEventLogBytes $control.minimumBytes) + ExactSize = [bool]$ResizeLogs; Mode = $(if ($ApplyLogMode) { $control.mode } else { $null }) + Context = $Context; BeforeWrite = $null + } + $read = { + param($state) + $observed = Get-WelaEventLogState -Log $state.Log + if ($observed.ReadStatus -ne 'Available') { throw "$($observed.ReadStatus) channel '$($state.Log)': $($observed.Diagnostic)" } + return $observed + } + $test = { + param($value, $state) + $sizeOK = if ($state.ExactSize) { $value.MaximumSizeInBytes -eq $state.Bytes } else { $value.MaximumSizeInBytes -ge $state.Bytes } + return $sizeOK -and (-not $state.Mode -or $value.LogMode -eq $state.Mode) + } + $apply = { + param($state) + # Recheck after prompting/journaling. A newly increased buffer must not + # be shrunk in minimum mode. Windows has no atomic compare-and-set here. + $fresh = Get-WelaEventLogState -Log $state.Log + if ($fresh.ReadStatus -ne 'Available') { throw 'Event-log state became unreadable before write.' } + $arguments = @('sl', $state.Log) + if (($state.ExactSize -and $fresh.MaximumSizeInBytes -ne $state.Bytes) -or $fresh.MaximumSizeInBytes -lt $state.Bytes) { + $arguments += "/ms:$($state.Bytes)" + } + if ($state.Mode -and $fresh.LogMode -ne $state.Mode) { + if ($state.Mode -eq 'Circular') { $arguments += @('/rt:false', '/ab:false') } + elseif ($state.Mode -eq 'AutoBackup') { $arguments += @('/rt:true', '/ab:true') } + else { throw 'Unsupported event-log mode.' } + } + if ($arguments.Count -gt 2) { + # The prompt can outlive another administrator's change. Keep the + # fresh snapshot as well as the runner's original observation. + $state.BeforeWrite = $fresh + [ordered]@{ + Version = 1; ComputerName = $env:COMPUTERNAME; RecordedUtc = [DateTime]::UtcNow.ToString('o') + Id = "EventLog/$($state.Log)/ProfileSettings"; Kind = 'EventLog'; Phase = 'ImmediatePreWrite' + Target = @{ Log = $state.Log }; Before = $fresh + Desired = @{ MaximumSizeInBytes = $state.Bytes; SizeMode = $(if ($state.ExactSize) { 'Exact' } else { 'Minimum' }); LogMode = $state.Mode } + } | ConvertTo-Json -Depth 12 -Compress | Add-Content -LiteralPath (Join-Path $state.Context.BackupPath 'before.jsonl') -Encoding UTF8 -ErrorAction Stop + Invoke-WelaNative -FilePath 'wevtutil.exe' -Arguments $arguments + } + } + $description = if ($ResizeLogs) { "Set exact size $($state.Bytes) bytes (shrinking can discard events)." } else { "Set minimum size $($state.Bytes) bytes; preserve larger buffers." } + $description += if ($ApplyLogMode) { " Apply $($state.Mode) mode." } else { ' Preserve the current retention mode.' } + Invoke-WelaConfigurationControl -Context $Context -Id "EventLog/$($control.log)/ProfileSettings" -Kind EventLog ` + -Target @{ Log = $control.log; Profile = $selected.id } ` + -Desired @{ MaximumSizeInBytes = $state.Bytes; SizeMode = $(if ($ResizeLogs) { 'Exact' } else { 'Minimum' }); LogMode = $state.Mode } ` + -Description $description ` + -Read $read -Compliant $test -Apply $apply -CallbackState $state + $result = $Context.Results[$Context.Results.Count - 1] + $result | Add-Member NoteProperty SourceIds @($control.sourceIds) + $result | Add-Member NoteProperty Evidence $control.evidence + $result | Add-Member NoteProperty RecommendedMode $control.mode + $result | Add-Member NoteProperty RetentionDays 'Unknown' + $result | Add-Member NoteProperty BeforeWrite $state.BeforeWrite + } +} + +function Invoke-WelaEventLogConfiguration { + param([string]$Profile = 'wela-source-2.2.0', [switch]$Auto, [switch]$DryRun, + [switch]$ResizeLogs, [switch]$ApplyLogMode, [string]$BackupPath, [string]$ResultsPath) + # Validate before a journal is created. The CLI checks Windows/elevation. + $selected = Get-WelaEventLogProfile -Id $Profile + $context = New-WelaConfigurationContext -Auto:$Auto -DryRun:$DryRun -BackupPath $BackupPath + Set-WelaEventLogProfileControls -Context $context -Profile $Profile -ResizeLogs:$ResizeLogs -ApplyLogMode:$ApplyLogMode + $report = Complete-WelaConfiguration -Context $context -Scope 'event-log-size-and-mode-only' + $report | Add-Member NoteProperty LogProfile $selected.id + $report | Add-Member NoteProperty ProfileKind $selected.kind + $report | Add-Member NoteProperty RetentionDays 'Unknown' + if ($ResultsPath) { + try { $report | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $ResultsPath -Encoding UTF8 -ErrorAction Stop } + catch { $report.ExitCode = 1; Write-Host "[Failed] Writing event-log results: $_" -ForegroundColor Red } + } + return $report +} diff --git a/tests/EventLogSettings.Tests.ps1 b/tests/EventLogSettings.Tests.ps1 new file mode 100644 index 00000000..a01d1075 --- /dev/null +++ b/tests/EventLogSettings.Tests.ps1 @@ -0,0 +1,206 @@ +# Safe fixtures only: never invokes real wevtutil or changes a Windows channel. +$ErrorActionPreference = 'Stop' +$repo = Split-Path $PSScriptRoot -Parent +Import-Module (Join-Path $repo 'modules/EventLogSettings.psm1') -Force +. (Join-Path $repo 'scripts/Configuration.ps1') +. (Join-Path $repo 'scripts/EventLogConfiguration.ps1') +$script:ScriptRoot = $repo +$script:assertions = 0 +$script:cleanup = New-Object 'System.Collections.Generic.List[string]' +function Assert($Condition, [string]$Message) { + if (-not $Condition) { throw "FAIL: $Message" } + $script:assertions++ +} +function New-TestContext([switch]$DryRun, [switch]$Prompt) { + $path = Join-Path ([IO.Path]::GetTempPath()) ('wela-eventlog-' + [guid]::NewGuid().ToString('N')) + if (-not $DryRun) { $script:cleanup.Add($path) } + New-WelaConfigurationContext -Auto:(-not $Prompt) -DryRun:$DryRun -BackupPath $path +} +function New-State([string]$Log, [long]$Bytes = 1048576, [string]$Mode = 'Retain') { + [pscustomobject]@{ Log = $Log; ReadStatus = 'Available'; MaximumSizeInBytes = $Bytes; LogMode = $Mode; FileSize = 100; IsEnabled = $true; Diagnostic = '' } +} +$data = Import-WelaEventLogProfiles +$wela = Get-WelaEventLogProfile +Assert ($data.profiles.Count -eq 4 -and $wela.controls.Count -eq 28) 'Catalog includes four separate source/collector choices and all WELA channels' +foreach ($log in @('Microsoft-Windows-AppLocker/EXE and DLL', 'Microsoft-Windows-AppLocker/MSI and Script', 'Microsoft-Windows-AppLocker/Packaged app-Deployment', 'Microsoft-Windows-AppLocker/Packaged app-Execution', 'Microsoft-Windows-Windows Firewall With Advanced Security/Firewall')) { + Assert (($wela.controls | Where-Object log -eq $log).minimumBytes -eq 268435456) "$log uses the same 256 MiB audit/apply threshold" +} +Assert (($wela.controls | Where-Object log -eq 'Setup').minimumBytes -eq 33554432) 'WELA includes Setup at CIS 32 MiB minimum' +Assert (($wela.controls | Where-Object log -eq 'Security').minimumBytes -eq 1073741824) 'Default preserves WELA configure 1024 MiB Security choice' +Assert (((Get-WelaEventLogProfile 'asd-source-2021-10').controls | Where-Object log -eq 'Security').minimumBytes -eq 2147483648) 'ASD Security size remains a 64-bit 2048 MiB byte count' +$collector = Get-WelaEventLogProfile 'asd-collector-archive-2021-10' +Assert ($collector.kind -eq 'collector' -and $collector.controls.Count -eq 1 -and $collector.controls[0].log -eq 'ForwardedEvents' -and $collector.controls[0].mode -eq 'AutoBackup') 'Collector archive does not leak into source logs' +Assert ((ConvertTo-WelaEventLogBytes 1048577) -eq 1114112) 'Fractional 64 KiB request rounds upward, never below the requested minimum' +Assert ((ConvertTo-WelaEventLogBytes 2147483648) -eq 2147483648) 'Aligned 2 GiB remains exact without integer overflow' +$caught = $false; try { Get-WelaEventLogProfile 'unrecognized' } catch { $caught = $true } +Assert $caught 'Unknown profile fails rather than silently using default values' + +# Exercise the real reader inside its module, safely replacing only Get-WinEvent. +$module = Get-Module EventLogSettings +& $module { + $script:case = 'normal' + function script:Get-WinEvent { + param($ListLog, $ErrorAction) + if ($script:case -eq 'denied') { throw [UnauthorizedAccessException]::new('fixture access denied') } + if ($script:case -eq 'missing') { throw [Management.Automation.ErrorRecord]::new([Exception]::new('fixture missing'), 'NoMatchingLogsFound', [Management.Automation.ErrorCategory]::ObjectNotFound, $ListLog) } + [pscustomobject]@{ MaximumSizeInBytes = $(if ($script:case -eq 'null') { $null } else { 1048577 }); LogMode = 'Circular'; FileSize = 42; IsEnabled = $false } + } +} +$observed = Get-WelaEventLogState 'Fixture' +Assert ($observed.ReadStatus -eq 'Available' -and $observed.MaximumSizeInBytes -eq 1048577 -and -not $observed.IsEnabled) 'Reader preserves exact bytes and does not claim a disabled channel is enabled' +& $module { $script:case = 'denied' } +Assert ((Get-WelaEventLogState 'Fixture').ReadStatus -eq 'Unreadable') 'Access denial is not classified as a missing channel' +& $module { $script:case = 'missing' } +Assert ((Get-WelaEventLogState 'Fixture').ReadStatus -eq 'Missing') 'Known missing-channel error remains explicit' +& $module { $script:case = 'null' } +Assert ((Get-WelaEventLogState 'Fixture').ReadStatus -eq 'Unreadable') 'Null maximum does not become a static/default size' +& $module { Remove-Item Function:script:Get-WinEvent } + +# The audit compares bytes, not rounded display values, and never invents duration. +$audit = @(Get-WelaEventLogAudit -Profile 'cis-v4-source' -Read { param($log) New-State $log 33554431 }) +Assert (($audit | Where-Object Log -eq 'Setup').SizeStatus -eq 'BelowMinimum') 'One byte under the minimum is not rounded to compliant' +Assert (@($audit | Where-Object RetentionDays -ne 'Unknown').Count -eq 0) 'Buffer size is not converted into an unmeasured retention age' +$audit = @(Get-WelaEventLogAudit -Profile 'cis-v4-source' -Read { param($log) [pscustomobject]@{ ReadStatus = 'Unreadable'; MaximumSizeInBytes = $null; LogMode = $null; IsEnabled = $null; Diagnostic = 'denied' } }) +Assert ($audit[0].SizeStatus -eq 'Unknown' -and $null -eq $audit[0].CurrentMaximumBytes -and $audit[0].Diagnostic -eq 'denied') 'Unreadable audit rows retain unknown state and failure evidence' + +# Script-scoped fixtures override exported commands at the same scope as helpers. +function Reset-Fixture([string]$Profile = 'cis-v4-source', [long]$Bytes = 1048576, [string]$Mode = 'Retain') { + $script:states = @{}; $script:writes = New-Object 'System.Collections.Generic.List[object]' + foreach ($control in (Get-WelaEventLogProfile $Profile).controls) { $script:states[$control.log] = New-State $control.log $Bytes $Mode } + $script:failWrite = $false; $script:falseSuccess = $false; $script:growOnPrompt = $false; $script:unreadableOnPrompt = $false +} +function Get-WelaEventLogState { + param($Log) + if (-not $script:states.ContainsKey($Log)) { return [pscustomobject]@{ ReadStatus = 'Missing'; Diagnostic = 'Fixture channel absent' } } + # Return a snapshot, not the mutable fixture reference, so journals are realistic. + return $script:states[$Log].PSObject.Copy() +} +function Read-Host { + param($Prompt) + if ($script:growOnPrompt) { foreach ($key in @($script:states.Keys)) { $script:states[$key].MaximumSizeInBytes = 4294967296 } } + if ($script:unreadableOnPrompt) { foreach ($key in @($script:states.Keys)) { $script:states[$key].ReadStatus = 'Unreadable' } } + return 'Y' +} +function Invoke-WelaNative { + param($FilePath, $Arguments) + Assert ($FilePath -eq 'wevtutil.exe' -and $Arguments[0] -eq 'sl') 'Only the intended native channel-setting command is issued' + Assert (Test-Path -LiteralPath (Join-Path $script:activeContext.BackupPath 'before.jsonl')) 'Pre-change journal is durable before each native write' + $script:writes.Add(@($Arguments)) + if ($script:failWrite) { throw 'fixture native nonzero exit' } + if (-not $script:falseSuccess) { + $current = $script:states[$Arguments[1]] + foreach ($argument in $Arguments) { + if ($argument -like '/ms:*') { $current.MaximumSizeInBytes = [long]$argument.Substring(4) } + } + if ($Arguments -contains '/ab:true' -and $Arguments -contains '/rt:true') { $current.LogMode = 'AutoBackup' } + if ($Arguments -contains '/ab:false' -and $Arguments -contains '/rt:false') { $current.LogMode = 'Circular' } + } + [pscustomobject]@{ Diagnostic = 'Safe fixture write' } +} +try { + Reset-Fixture 'wela-source-2.2.0' + $script:activeContext = New-TestContext + Set-WelaEventLogProfileControls -Context $script:activeContext + $audit = @(Get-WelaEventLogAudit -Read { param($log) Get-WelaEventLogState $log }) + Assert (@($audit | Where-Object SizeStatus -ne 'Compliant').Count -eq 0) 'Successful default configuration leaves no default-profile size warning' + Assert (@($script:writes | Where-Object { ($_ -join ' ') -match '/[ar][bt]:' }).Count -eq 0) 'Ordinary configuration never changes retention modes' + Assert ($script:states.Security.LogMode -eq 'Retain') 'Existing retain policy is preserved without explicit mode opt-in' + $result = Complete-WelaConfiguration -Context $script:activeContext + Assert ($result.ExitCode -eq 0) 'All default sizes pass final verification' + + Reset-Fixture 'cis-v4-source' 4294967296 + $script:activeContext = New-TestContext + Set-WelaEventLogProfileControls -Context $script:activeContext -Profile 'cis-v4-source' + Assert ($script:writes.Count -eq 0 -and $script:activeContext.Checks.Count -eq 4) 'Minimum mode preserves larger buffers and still registers final verification' + $script:states.Setup.MaximumSizeInBytes = 1048576 + $result = Complete-WelaConfiguration -Context $script:activeContext + Assert ($result.ExitCode -eq 1 -and @($result.Results | Where-Object Status -eq 'Overridden').Count -eq 1) 'Final drift is not reported as success' + + Reset-Fixture 'cis-v4-source' 4294967296 + $script:activeContext = New-TestContext + Set-WelaEventLogProfileControls -Context $script:activeContext -Profile 'cis-v4-source' -ResizeLogs -ApplyLogMode + Assert ($script:states.Setup.MaximumSizeInBytes -eq 33554432 -and $script:states.Setup.LogMode -eq 'Circular') 'Explicit resize and mode flags allow reviewed shrink/circular choices' + Assert ($script:writes[0] -contains '/rt:false' -and $script:writes[0] -contains '/ab:false') 'Source circular uses both retention and autobackup false' + $firstJournal = (Get-Content -LiteralPath (Join-Path $script:activeContext.BackupPath 'before.jsonl'))[0] | ConvertFrom-Json + Assert ($firstJournal.Before.MaximumSizeInBytes -eq 4294967296 -and $firstJournal.Before.LogMode -eq 'Retain') 'Journal records exact previous bytes and mode for manual recovery' + + Reset-Fixture 'asd-collector-archive-2021-10' + $script:activeContext = New-TestContext + Set-WelaEventLogProfileControls -Context $script:activeContext -Profile 'asd-collector-archive-2021-10' -ApplyLogMode + Assert ($script:writes.Count -eq 1 -and $script:writes[0] -contains '/rt:true' -and $script:writes[0] -contains '/ab:true') 'Collector archive explicitly applies retention plus autobackup' + Assert ($script:states.ForwardedEvents.MaximumSizeInBytes -eq 2147483648 -and $script:states.ForwardedEvents.LogMode -eq 'AutoBackup') 'Collector exact 2 GiB and mode are read back' + + Reset-Fixture + $script:activeContext = New-TestContext -DryRun + Set-WelaEventLogProfileControls -Context $script:activeContext -Profile 'cis-v4-source' -ResizeLogs -ApplyLogMode + Assert ($script:writes.Count -eq 0 -and -not (Test-Path -LiteralPath $script:activeContext.BackupPath)) 'Dry run performs no native writes and creates no journal' + + Reset-Fixture + $script:activeContext = New-TestContext -Prompt; $script:growOnPrompt = $true + Set-WelaEventLogProfileControls -Context $script:activeContext -Profile 'cis-v4-source' + Assert ($script:writes.Count -eq 0 -and $script:states.Setup.MaximumSizeInBytes -eq 4294967296) 'Fresh prewrite state preserves buffer enlarged during operator confirmation' + + Reset-Fixture + $script:activeContext = New-TestContext -Prompt; $script:unreadableOnPrompt = $true + Set-WelaEventLogProfileControls -Context $script:activeContext -Profile 'cis-v4-source' + Assert ($script:writes.Count -eq 0 -and $script:activeContext.Results[0].Status -eq 'Failed') 'Unknown fresh state refuses writes' + + foreach ($case in @('missing', 'denied', 'native', 'false-success', 'journal')) { + Reset-Fixture 'asd-collector-archive-2021-10' + $script:activeContext = New-TestContext + if ($case -eq 'missing') { $script:states.Clear() } + if ($case -eq 'denied') { $script:states.ForwardedEvents.ReadStatus = 'Unreadable'; $script:states.ForwardedEvents.Diagnostic = 'access denied' } + if ($case -eq 'native') { $script:failWrite = $true } + if ($case -eq 'false-success') { $script:falseSuccess = $true } + if ($case -eq 'journal') { $script:activeContext.BackupPath = Join-Path $script:activeContext.BackupPath 'absent-parent' } + Set-WelaEventLogProfileControls -Context $script:activeContext -Profile 'asd-collector-archive-2021-10' -ApplyLogMode + $result = Complete-WelaConfiguration -Context $script:activeContext + Assert ($result.ExitCode -eq 1 -and $result.Results[0].Status -eq 'Failed') "$case is surfaced as failed configuration" + if ($case -in @('missing', 'denied', 'journal')) { Assert ($script:writes.Count -eq 0) "$case prevents the native write" } + } + + # Parse actual production entry points; same profile is selected in both paths. + $tokens = $null; $errors = $null + $ast = [Management.Automation.Language.Parser]::ParseFile((Join-Path $repo 'WELA.ps1'), [ref]$tokens, [ref]$errors) + Assert ($errors.Count -eq 0) 'WELA remains parseable' + $configure = $ast.Find({ param($n) $n -is [Management.Automation.Language.FunctionDefinitionAst] -and $n.Name -eq 'ConfigureAuditSettings' }, $true).Extent.Text + Assert ($configure.Contains("Set-WelaEventLogProfileControls -Context `$context -Profile 'wela-source-2.2.0'") -and $configure -notmatch '-Property MaximumSizeInBytes') 'Default configure consumes the shared model rather than another hardcoded size list' + $release = Get-Content (Join-Path $repo '.github/workflows/release.yml') -Raw + foreach ($directory in @('config', 'scripts', 'modules')) { Assert ($release -match "Copy-Item -Recurse -Path ./($directory) ") "Release packages the new $directory dependency" } + + Reset-Fixture + $output = Join-Path ([IO.Path]::GetTempPath()) ('wela-eventlog-results-' + [guid]::NewGuid().ToString('N') + '.json') + $script:cleanup.Add($output) + $result = Invoke-WelaEventLogConfiguration -Profile 'cis-v4-source' -DryRun -ResultsPath $output + $saved = Get-Content -LiteralPath $output -Raw | ConvertFrom-Json + Assert ($result.ExitCode -eq 0 -and $saved.Scope -eq 'event-log-size-and-mode-only' -and $saved.LogProfile -eq 'cis-v4-source') 'Dedicated configuration exports its narrow scope and selected log profile' + Assert ($saved.Results.Count -eq 4 -and $saved.Results[0].SourceIds -contains 'cis-v4' -and $saved.RetentionDays -eq 'Unknown') 'Structured result preserves source provenance and unknown retention duration' + + # Execute only actual option guards, then the dispatcher with harmless stubs. + $guards = @($ast.EndBlock.Statements | Where-Object { $_ -is [Management.Automation.Language.IfStatementAst] -and $_.Extent.Text -match 'No command was run|selects advanced audit policy only|LogProfile is supported only' }) + $guardBlock = [scriptblock]::Create(($guards | ForEach-Object { $_.Extent.Text }) -join "`n") + $Cmd = 'configure'; $Profile = 'wela-2.2.0'; $LogProfile = 'cis-v4-source'; $ResizeLogs = $false; $ApplyLogMode = $false; $DryRun = $true + $caught = $false; try { & $guardBlock } catch { $caught = $true } + Assert $caught 'Advanced audit profile configure rejects log options instead of silently ignoring them' + $Cmd = 'configure-eventlogs'; $Profile = 'wela-2.2.0' + $caught = $false; try { & $guardBlock } catch { $caught = $true } + Assert $caught 'Event-log command rejects the advanced -Profile option' + $Profile = $null; $ResizeLogs = $true; $ApplyLogMode = $true + & $guardBlock + Assert $true 'Dedicated event-log dry run accepts explicit size/mode options' + $Help = $false; $Baseline = $null; $Auto = $true; $BackupPath = $null; $ResultsPath = $null + function TestWindows { return $true } + function TestAdministrator { return $true } + function Invoke-WelaEventLogConfiguration { + param($Profile, [switch]$Auto, [switch]$DryRun, [switch]$ResizeLogs, [switch]$ApplyLogMode, $BackupPath, $ResultsPath) + $script:dispatched = @{ Profile = $Profile; DryRun = [bool]$DryRun; ResizeLogs = [bool]$ResizeLogs; ApplyLogMode = [bool]$ApplyLogMode } + [pscustomobject]@{ ExitCode = 0 } + } + $dispatch = $ast.Find({ param($n) $n -is [Management.Automation.Language.SwitchStatementAst] -and $n.Condition.Extent.Text -eq '$Cmd.ToLower()' }, $false) + & ([scriptblock]::Create($dispatch.Extent.Text)) | Out-Null + Assert ($script:dispatched.Profile -eq 'cis-v4-source' -and $script:dispatched.DryRun -and $script:dispatched.ResizeLogs -and $script:dispatched.ApplyLogMode) 'CLI passes every explicit event-log choice to the dedicated runner' +} finally { + foreach ($path in $script:cleanup) { if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force } } +} +$global:LASTEXITCODE = 0 +Write-Host "PASS: $script:assertions event-log assertions; no machine policies changed." diff --git a/tests/EventLogSettings.Windows.Tests.ps1 b/tests/EventLogSettings.Windows.Tests.ps1 new file mode 100644 index 00000000..d76c534e --- /dev/null +++ b/tests/EventLogSettings.Windows.Tests.ps1 @@ -0,0 +1,14 @@ +# Real Windows reader smoke only. No native setter, SaveChanges or policy mutation. +$ErrorActionPreference = 'Stop' +Import-Module (Join-Path $PSScriptRoot '../modules/EventLogSettings.psm1') -Force +foreach ($log in @('Application', 'System', 'Setup', 'Security')) { + $before = Get-WelaEventLogState -Log $log + if ($before.ReadStatus -ne 'Available') { throw "Cannot inspect $log : $($before.Diagnostic)" } + $row = @(Get-WelaEventLogAudit -Profile 'cis-v4-source' | Where-Object Log -eq $log) + $after = Get-WelaEventLogState -Log $log + if ($row.Count -ne 1 -or $row[0].CurrentMaximumBytes -ne $before.MaximumSizeInBytes -or $row[0].RetentionDays -ne 'Unknown') { throw "Audit did not represent the live $log configuration." } + if ($before.MaximumSizeInBytes -ne $after.MaximumSizeInBytes -or $before.LogMode -ne $after.LogMode) { throw "Observed concurrent change to $log during read-only smoke." } +} +$missing = Get-WelaEventLogState -Log ('WELA-Unregistered-' + [guid]::NewGuid().ToString('N')) +if ($missing.ReadStatus -ne 'Missing') { throw "Missing Windows channel was misclassified: $($missing.ReadStatus) $($missing.Diagnostic)" } +Write-Host 'PASS: live read-only event-log audit; exact bytes, modes and missing-channel reporting verified.' diff --git a/website/docs/commands/usage.md b/website/docs/commands/usage.md index 159faa82..db1df969 100644 --- a/website/docs/commands/usage.md +++ b/website/docs/commands/usage.md @@ -24,15 +24,25 @@ Check with Microsoft's recommended Client OS settings and display results in tab ./WELA.ps1 audit-settings -Baseline Microsoft_Client -OutType table ``` -## audit-filesize -The `audit-filesize` command checks the Windows event logs' file size and compares them with the recommended settings from Yamato Security's recommendations. +## audit-filesize and configure-eventlogs -### `audit-filesize` command examples -Check the Windows event log file size with Yamato Security's recommendations and save results to CSV: -``` -./WELA.ps1 audit-filesize -Baseline YamatoSecurity +`audit-filesize` reads live event-log sizes and retention modes using the same +profile as configuration, preserving exact byte counts in its CSV output. +Use `eventlog-profiles` to list the separate `-LogProfile` choices. The existing +`-Profile` option selects advanced audit policy only. + +```powershell +./WELA.ps1 audit-filesize -LogProfile wela-source-2.2.0 +./WELA.ps1 configure-eventlogs -LogProfile asd-source-2021-10 -DryRun +./WELA.ps1 configure-eventlogs -LogProfile asd-collector-archive-2021-10 -ApplyLogMode ``` +`configure-eventlogs` preserves larger buffers and current modes by default. +`-ResizeLogs` explicitly permits shrinking; `-ApplyLogMode` explicitly applies +source circular or collector archive behavior. Retention days remain unknown +until event volume and archive retention are measured. See the +[event-log profiles and recovery guide](https://github.com/Yamato-Security/WELA/blob/dev/docs/eventlog-settings.md). + ## configure The `configure` command sets the recommended Windows event log audit policy and file size. diff --git a/website/docs/resources/changelog.ja.md b/website/docs/resources/changelog.ja.md index 4f073d1f..b6c2464d 100644 --- a/website/docs/resources/changelog.ja.md +++ b/website/docs/resources/changelog.ja.md @@ -7,6 +7,8 @@ **改善:** +- イベントログのサイズ監査と設定に共通のバイト単位プロファイルを導入し、AppLocker・ファイアウォールログの256 MiB、Setupの32 MiB、ASD推奨のSecurityログ2048 MiBに対応した。`-LogProfile`と`configure-eventlogs`で送信元と収集サーバーのサイズ・保存方式を選択できる。明示的に指定しない限り、既存の大きいバッファと保存方式は維持する。結果には検証した設定を記録し、未測定の保存日数は不明と表示する。 (issue #379) (@Shirofune-Security) + - `audit-settings`、`plan`、`configure`で共有するバージョン付きの詳細監査ポリシープロファイルを追加した。59のサブカテゴリと14のプロファイルで、WELA、文書に基づくWindows既定値、Microsoft、確認済みのCIS v4.0.0、ASDのWindows標準機能向け監査ガイドに対応する。ホストの役割とビルドの検証、オフラインでの設定計画、出典と前提条件を含むJSON出力をサポートする。完全一致、最低限、任意、変更なし、未構成、適用対象外を区別する。Windows既定値は参照専用で、プロファイルの対象はSecurityログの詳細監査ポリシーに限定される。 (#390) (@Shirofune-Security) - WELAのプロファイルにWindows標準の監査サブカテゴリを6つ追加した。Group MembershipとAuthorization Policy Changeは成功、Application Group Management、MPSSVC Rule-Level Policy Change、IPsec Driver、Kernel Objectは成功と失敗を監査する。各ガイドに対応するプロファイルでは、それぞれの監査設定と前提条件を維持する。Kernel Objectのイベント生成には対象オブジェクトに適切なSACLが必要であり、この変更ではそのSACLを作成しない。 (#391) (@Shirofune-Security) - `configure`と`configure -Profile`に`-DryRun`と`-ResultsPath`を追加し、Windows設定を変更せずに変更内容を確認し、設定項目ごとの結果をJSONで出力できるようにした。`-DryRun`に対応していないコマンドは、実行前にエラーで停止する。 (#392) (@Shirofune-Security) diff --git a/website/docs/resources/changelog.md b/website/docs/resources/changelog.md index 9e7520f5..27f99e5b 100644 --- a/website/docs/resources/changelog.md +++ b/website/docs/resources/changelog.md @@ -7,6 +7,8 @@ **Improvements:** +- Unified event-log size auditing and configuration with shared byte-based profiles, including 256 MiB AppLocker/firewall logs, 32 MiB Setup and ASD 2048 MiB Security. Added separate source and collector size/mode choices through `-LogProfile` and `configure-eventlogs`; larger buffers and existing retention modes are preserved unless explicitly changed. Results include verified state and unknown retention duration. (issue #379) (@Shirofune-Security) + - Added versioned advanced audit-policy profiles shared by `audit-settings`, `plan` and `configure`: 59 subcategories and 14 profiles covering WELA, documented Windows defaults, Microsoft, reviewed CIS v4.0.0 and ASD native guidance. Profiles support role/build validation, offline planning and JSON exports with sources and prerequisites. Exact, minimum, optional, unchanged, Not Configured and not-applicable settings remain distinct. Windows defaults are reference-only; profile scope is advanced Security audit policy. (#390) (@Shirofune-Security) - Added six native Windows audit subcategories to WELA's profile: Group Membership and Authorization Policy Change (Success), plus Application Group Management, MPSSVC Rule-Level Policy Change, IPsec Driver and Kernel Object (Success and Failure). Source-specific profiles retain their own audit settings and prerequisites; Kernel Object events require matching object SACLs, which this change does not create. (#391) (@Shirofune-Security) - Added `-DryRun` and `-ResultsPath` to `configure` and `configure -Profile` to preview changes without modifying Windows settings and export per-control results as JSON. Commands that do not support `-DryRun` reject it before running. (#392) (@Shirofune-Security)