From 2ea509700b60fe2f5c5284e80c30b0bef68e719e Mon Sep 17 00:00:00 2001 From: Shirofune-Security <43838376+Shirofune-Security@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:31:44 +0900 Subject: [PATCH] Add version-aware opt-in SMB audit policy controls --- .github/workflows/smb-auditing.yml | 31 +++++ CHANGELOG-Japanese.md | 1 + CHANGELOG.md | 1 + WELA.ps1 | 23 +++- docs/smb-auditing.md | 68 ++++++++++ scripts/Configuration.ps1 | 2 +- scripts/SmbAuditing.ps1 | 167 ++++++++++++++++++++++++ tests/SmbAuditing.Tests.ps1 | 171 +++++++++++++++++++++++++ tests/SmbAuditing.Windows.Tests.ps1 | 31 +++++ website/docs/resources/changelog.ja.md | 1 + website/docs/resources/changelog.md | 1 + 11 files changed, 494 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/smb-auditing.yml create mode 100644 docs/smb-auditing.md create mode 100644 scripts/SmbAuditing.ps1 create mode 100644 tests/SmbAuditing.Tests.ps1 create mode 100644 tests/SmbAuditing.Windows.Tests.ps1 diff --git a/.github/workflows/smb-auditing.yml b/.github/workflows/smb-auditing.yml new file mode 100644 index 00000000..6077684a --- /dev/null +++ b/.github/workflows/smb-auditing.yml @@ -0,0 +1,31 @@ +name: SMB audit policy regressions +on: + push: + branches: ['**'] + paths: + - 'WELA.ps1' + - 'scripts/Configuration.ps1' + - 'scripts/SmbAuditing.ps1' + - 'tests/SmbAuditing*' + - '.github/workflows/smb-auditing.yml' + pull_request: + workflow_dispatch: +permissions: + contents: read +jobs: + smb-auditing: + runs-on: windows-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Mocked policy/runtime and real ADMX parsing in Windows PowerShell 5.1 + shell: powershell + run: ./tests/SmbAuditing.Tests.ps1 + - name: Actual Windows read-only smoke in Windows PowerShell 5.1 + shell: powershell + run: ./tests/SmbAuditing.Windows.Tests.ps1 + - name: Mocked policy/runtime and real ADMX parsing in PowerShell 7 + shell: pwsh + run: ./tests/SmbAuditing.Tests.ps1 + - name: Actual Windows read-only smoke in PowerShell 7 + shell: pwsh + run: ./tests/SmbAuditing.Windows.Tests.ps1 diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 47edb38e..c365bbb0 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -4,6 +4,7 @@ **改善:** +- 6つのネイティブSMB監査ポリシーを監査・計画・設定する任意実行の`smb-auditing`を追加しました。OSビルドとローカルADMXの正確な定義を確認してから書き込み、ポリシーのDWORD値と取得可能な実行時設定を分けて表示します。Dry-run、復旧用記録、最終検証に対応し、署名・暗号化要件やゲストアクセスは変更しません。実イベント生成と収集の検証は別途必要です。 (issue #377) (@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..db5be3cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ **Improvements:** +- Added opt-in `smb-auditing` audit, plan and configure actions for six native SMB audit policies. Host builds and exact local ADMX mappings gate writes; policy DWORDs and available runtime values are reported separately, with dry-run, recovery journaling and final verification. Signing/encryption requirements and guest access are not changed; runtime event/ingestion validation remains required. (issue #377) (@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..52afe6c4 100644 --- a/WELA.ps1 +++ b/WELA.ps1 @@ -14,6 +14,7 @@ [switch]$DryRun, [string]$BackupPath, [string]$ResultsPath, + [ValidateSet('Audit', 'Plan', 'Configure')][string]$SmbAction = 'Audit', [switch]$Help ) @@ -28,6 +29,7 @@ $EidMappingPath = Join-Path $ScriptRoot "config/eid_subcategory_mapping.csv" $AuditpolTxtPath = Join-Path $ScriptRoot "auditpol.txt" $SaclTargetsPath = Join-Path $ScriptRoot "config/audit_sacl_targets.json" . (Join-Path $ScriptRoot "scripts/Configuration.ps1") +. (Join-Path $ScriptRoot "scripts/SmbAuditing.ps1") Import-Module (Join-Path $ScriptRoot "modules/AuditProfiles.psm1") -ErrorAction Stop # 64bit の PowerShell と GPO が読むのは Wow6432Node の無いパス。32bit 用に両方を扱う。 @@ -1751,6 +1753,10 @@ function Get-WelaUserProfiles { $usage = @" Usage: + ./WELA.ps1 smb-auditing -SmbAction Audit -ResultsPath smb-audit.json + ./WELA.ps1 smb-auditing -SmbAction Plan + ./WELA.ps1 smb-auditing -SmbAction Configure -DryRun + # SMB auditing is opt-in and never changes signing/encryption requirements or guest access. ./WELA.ps1 profiles # List versioned advanced audit-policy profiles ./WELA.ps1 plan -Profile wela-2.2.0 -Role Client -Build 26100 -PlanPath plan.json ./WELA.ps1 audit-settings -Profile microsoft-sct-win11-24h2 -PlanPath audit.json @@ -1776,8 +1782,8 @@ 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 -ne 'configure' -and -not ($Cmd -eq 'smb-auditing' -and $SmbAction -eq 'Configure')) { + throw "-DryRun is supported only by configure (including configure -Profile) and smb-auditing -SmbAction Configure. No command was run." } if ($Profile -and $Cmd.ToLower() -in @('plan', 'audit', 'audit-settings', 'configure') -and -not $Help) { @@ -1786,6 +1792,19 @@ if ($Profile -and $Cmd.ToLower() -in @('plan', 'audit', 'audit-settings', 'confi } switch ($Cmd.ToLower()) { + 'smb-auditing' { + if ($Help) { + Write-Host 'Usage: ./WELA.ps1 smb-auditing [-SmbAction Audit|Plan|Configure] [-Auto] [-DryRun] [-BackupPath new-directory] [-ResultsPath file.json]' + Write-Host 'Checks six version-aware SMB audit policies against local ADMX and available runtime properties. Configure writes supported audit DWORDs only. See docs/smb-auditing.md.' + return + } + if ($Profile -or $Baseline) { throw 'smb-auditing uses -SmbAction; -Profile and -Baseline apply to Security audit settings.' } + try { + $report = Invoke-WelaSmbAuditCommand -Action $SmbAction -Auto:$Auto -DryRun:$DryRun -BackupPath $BackupPath -ResultsPath $ResultsPath + $report + if ($report.ExitCode) { exit $report.ExitCode } + } catch { Write-Host "[Failed] SMB auditing: $_" -ForegroundColor Red; exit 1 } + } "profiles" { (Import-WelaAuditProfiles).profiles | Select-Object id, version, scope, appliesTo | Format-List } diff --git a/docs/smb-auditing.md b/docs/smb-auditing.md new file mode 100644 index 00000000..384a01f1 --- /dev/null +++ b/docs/smb-auditing.md @@ -0,0 +1,68 @@ +# Version-aware native SMB audit policies + +The opt-in `smb-auditing` command audits, plans and configures six built-in Windows audit policies. It does not enable insecure guest access, weaken signing/encryption, change SMB dialects or shares, restart services, or install Sysmon. It does not configure event forwarding, change channel settings or claim a Sigma coverage increase. + +Run in elevated **64-bit** Windows PowerShell 5.1 or PowerShell 7: + +```powershell +.\WELA.ps1 smb-auditing -SmbAction Audit -ResultsPath smb-audit.json +.\WELA.ps1 smb-auditing -SmbAction Plan -ResultsPath smb-plan.json +.\WELA.ps1 smb-auditing -SmbAction Configure -DryRun -ResultsPath smb-preview.json +.\WELA.ps1 smb-auditing -SmbAction Configure -Auto -BackupPath .\smb-before -ResultsPath smb-results.json +``` + +`-Profile` and `-Baseline` are rejected for this command: their advanced Security audit-policy semantics do not include these SMB policies. Audit and Plan both read the current host and show desired DWORD values; the plan is evidence, not an offline authorization file. They write only the explicitly requested results JSON. Configure dry run performs no policy/key writes and creates no recovery directory. Unknown observations fail with exit code 1; known unsupported controls are skipped explicitly. A readable assessment with `ChangeRequired` has exit code 0 because the assessment completed. + +## Exact controls and capability gates + +Each value below is enabled as **REG_DWORD 1**. Numeric strings are neither compliant nor accepted during read-back. + +| Registry key under HKLM | Values | +| --- | --- | +| `SOFTWARE\Policies\Microsoft\Windows\LanmanServer` | `AuditClientDoesNotSupportEncryption`, `AuditClientDoesNotSupportSigning`, `AuditInsecureGuestLogon` | +| `SOFTWARE\Policies\Microsoft\Windows\LanmanWorkstation` | `AuditServerDoesNotSupportEncryption`, `AuditServerDoesNotSupportSigning`, `AuditInsecureGuestLogon` | + +The reviewed build families are Windows 11 24H2 (26100), Windows 11 25H2 (26200), and Windows Server 2025 including domain controllers (26100). Host role/build is read from Win32_OperatingSystem. Older releases, including Server 2022 (20348), are `NotApplicable` even if someone has copied newer ADMX files onto them. Unreviewed future builds or unknown host information are `Unknown`; they receive no policy writes. + +A qualifying build is only a candidate. For **each** control WELA must also read the local `%windir%\PolicyDefinitions\LanmanServer.admx` or `LanmanWorkstation.admx` and find exactly one matching Machine policy, official `Pol_...` name, exact registry key and value name, and enabled decimal DWORD value 1. Missing, inaccessible, malformed, mismatched or ambiguous definitions are `Unknown`. DTD/external entities are prohibited. The report records the local ADMX path, SHA-256 hash and supportedOn reference. WELA does not download templates or assume that a Central Store proves local capability. + +Microsoft's Policy CSP pages list **26100.3613** as the availability floor for that CSP delivery surface. This tool writes the documented registry policy, not the CSP. It does not treat every 26100 host as supported based on its build alone or use the CSP minor build as a substitute for local policy/capability evidence. Local templates can still be replaced independently of the OS; available native runtime properties and lab events provide additional evidence. + +## Policy registry versus effective runtime + +Reports keep `Policy` (the actual policy-registry value/type) separate from `Runtime` (the corresponding property of `Get-SmbServerConfiguration` or `Get-SmbClientConfiguration`). WELA never substitutes the policy DWORD for a runtime observation: + +- `Observed`: the getter exposes an actual Boolean. True supports effective configuration; False means the requested auditing is not yet observed. A write that leaves an exposed property False fails verification even when the DWORD was written successfully. Review policy application and repeat the audit; WELA does not restart a service or weaken security to make verification pass. +- `NotExposed`: the getter or property is unavailable. With the exact local ADMX mapping, WELA can verify the registry policy only. The snapshot explicitly says **effective auditing not established**. A successful registry result is not proof of runtime activation or event generation. +- `Unknown`: a runtime read fails or returns an unexpected type. Configuration fails closed without treating the state as a default. + +Configure uses the common recovery journal and result runner. It rechecks capabilities/current values before writing, verifies the DWORD and available runtime property afterward, and reads them again at completion. Changed previously compliant controls become `Overridden`. Read/write failures are reported per control and give a nonzero exit code while other controls continue. A prompt-time policy change is refused so recovery evidence does not silently describe a stale value. + +The registry policy is a current observation, not proof of GPO/MDM ownership or long-term persistence. Future policy refresh can replace it. A direct local policy write also is not an edit to the domain GPO or its authoritative registry.pol source. + +## Manual recovery + +There is no automatic rollback. Review results and `before.jsonl` before selecting an entry. Its `Before.Policy` contains the original value existence, data and registry kind; `Before.Runtime` is observation only and must not be blindly passed to SMB setters. Example for one reviewed entry: + +```powershell +$entry = Get-Content -LiteralPath .\smb-before\before.jsonl | ConvertFrom-Json | + Where-Object { $_.Kind -eq 'SmbAudit' -and $_.Target.Name -eq 'AuditClientDoesNotSupportSigning' } | + Select-Object -First 1 +if (-not $entry) { throw 'Recovery entry not found' } +$old = $entry.Before.Policy +if ($old.ValueExists) { + Set-ItemProperty -LiteralPath $entry.Target.Path -Name $entry.Target.Name -Value $old.Value -Type $old.Type -ErrorAction Stop +} else { + Remove-ItemProperty -LiteralPath $entry.Target.Path -Name $entry.Target.Name -ErrorAction Stop +} +``` + +Review concurrent administrator changes and GPO/MDM ownership first; a failed write may have left the original state unchanged. Restore controls individually and rerun Audit. Keep newly created parent policy keys unless separately reviewed as empty and safe to remove; never delete the entire LanmanServer/Workstation policy key. Service runtime can lag a registry change, so recovery also requires a later runtime check. This command has not changed guest access, signing/encryption requirements, shares or service state. + +## Evidence still needed before closing issue #377 + +Mocked tests exercise OS/build and per-policy ADMX gating, all six exact policy targets, DWORD types, supported/unsupported/unknown states, runtime read errors and unavailable properties, dry run, recovery ordering, idempotence, ineffective runtime state and final drift. Windows CI adds read-only registry/local-ADMX/native-runtime observations and dry-run checks under PowerShell 5.1 and 7. It does not generate SMB traffic or alter runner policy. + +On isolated supported client/server snapshots, retain OS build/revision, PowerShell version, WELA commit, ADMX hashes and before/after reports. Confirm a policy refresh does not unexpectedly override the requested setting. Capture representative native SMB audit events under the existing security requirements and verify collector delivery. Microsoft's signing/encryption guide identifies SMBClient/Audit 31998/31999 and SMBServer/Audit 3021/3022; validate the event actually corresponds to the test condition. Inspect guest-audit behavior without enabling guest access or weakening signing/encryption. If the existing secure configuration prevents a guest-session event, record that limitation rather than changing the security posture merely to obtain a test event. Also verify manual recovery. These traffic and ingestion tests remain pending; keep the issue open until the required evidence exists. + +Sources: [LanmanServer Policy CSP mappings](https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-lanmanserver), [LanmanWorkstation Policy CSP mappings](https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-lanmanworkstation), [SMB signing and encryption auditing](https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-signing-overview), [SMB feature availability](https://learn.microsoft.com/en-us/windows-server/storage/file-server/file-server-smb-overview), [SMB server audit parameters](https://learn.microsoft.com/en-us/powershell/module/smbshare/set-smbserverconfiguration?view=windowsserver2025-ps), and [issue #377](https://github.com/Yamato-Security/WELA/issues/377). diff --git a/scripts/Configuration.ps1 b/scripts/Configuration.ps1 index d8baddde..118f91f1 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", "smb-audit-policies-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/SmbAuditing.ps1 b/scripts/SmbAuditing.ps1 new file mode 100644 index 00000000..d3069bcd --- /dev/null +++ b/scripts/SmbAuditing.ps1 @@ -0,0 +1,167 @@ +# Optional audit-only SMB policies. Requires Configuration.ps1; compatible with PowerShell 5.1. +function Get-WelaSmbAuditDefinitions { + foreach ($component in @('LanmanServer', 'LanmanWorkstation')) { + $peer = if ($component -eq 'LanmanServer') { 'Client' } else { 'Server' } + foreach ($name in @("Audit${peer}DoesNotSupportEncryption", "Audit${peer}DoesNotSupportSigning", 'AuditInsecureGuestLogon')) { + [pscustomobject]@{ Component = $component; Name = $name; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\$component"; Admx = "$component.admx"; PolicyName = "Pol_$name"; DesiredValue = 1; DesiredType = 'DWord' } + } + } +} + +function Get-WelaSmbAuditHost { + try { + if (-not [Environment]::Is64BitProcess) { throw 'Use 64-bit PowerShell to read and write the native policy registry view.' } + $os = Get-CimInstance -ClassName Win32_OperatingSystem -Property ProductType, BuildNumber, Version, Caption -ErrorAction Stop + if (-not $os -or [string]$os.BuildNumber -notmatch '^\d+$' -or $os.ProductType -notin @(1, 2, 3)) { throw 'OS build or product type is unknown.' } + $build = [int]$os.BuildNumber + $state = if ($build -lt 26100) { 'NotApplicable' } + elseif (($os.ProductType -eq 1 -and $build -in @(26100, 26200)) -or ($os.ProductType -in @(2, 3) -and $build -eq 26100)) { 'Candidate' } + else { 'Unknown' } + [pscustomobject]@{ Status = $state; Build = $build; ProductType = [int]$os.ProductType; Caption = [string]$os.Caption; Version = [string]$os.Version; Diagnostic = $(if ($state -eq 'NotApplicable') { 'These six audit switches require Windows 11 24H2/25H2 or Server 2025; older releases, including Server 2022, are not configured.' } elseif ($state -eq 'Unknown') { 'This OS build has not been reviewed; no policies will be created.' } else { 'Build is eligible; each local ADMX mapping is checked separately.' }) } + } catch { [pscustomobject]@{ Status = 'Unknown'; Build = $null; ProductType = $null; Caption = $null; Version = $null; Diagnostic = $_.Exception.Message } } +} + +function Read-WelaSmbAuditAdmx { + param([string]$Path) + $settings = New-Object Xml.XmlReaderSettings + $settings.DtdProcessing = [Xml.DtdProcessing]::Prohibit + $settings.XmlResolver = $null + $reader = [Xml.XmlReader]::Create($Path, $settings) + try { + $document = New-Object Xml.XmlDocument + $document.XmlResolver = $null + $document.Load($reader) + return $document + } finally { $reader.Dispose() } +} + +function Get-WelaSmbAuditCapability { + param($Definition, $HostState) + $path = Join-Path (Join-Path $env:windir 'PolicyDefinitions') $Definition.Admx + $result = [pscustomobject]@{ Status = $HostState.Status; Host = $HostState; AdmxPath = $path; AdmxSha256 = $null; SupportedOn = $null; Diagnostic = $HostState.Diagnostic } + if ($HostState.Status -ne 'Candidate') { return $result } + try { + if (-not (Test-Path -LiteralPath $path -PathType Leaf -ErrorAction Stop)) { throw "Local policy definition is missing: $path" } + $document = Read-WelaSmbAuditAdmx -Path $path + $policies = @($document.SelectNodes("//*[local-name()='policy']") | Where-Object { + $_.GetAttribute('name') -eq $Definition.PolicyName -and $_.GetAttribute('class') -eq 'Machine' -and + $_.GetAttribute('key') -eq ($Definition.Path -replace '^HKLM:\\', '') -and $_.GetAttribute('valueName') -eq $Definition.Name + }) + if ($policies.Count -ne 1) { throw 'Exact machine ADMX policy/key/value mapping is missing or ambiguous.' } + $enabled = $policies[0].SelectSingleNode("./*[local-name()='enabledValue']/*[local-name()='decimal']") + if (-not $enabled -or $enabled.GetAttribute('value') -ne '1') { throw 'ADMX does not define the requested enabled DWORD value 1.' } + $supported = $policies[0].SelectSingleNode("./*[local-name()='supportedOn']") + if ($supported) { $result.SupportedOn = $supported.GetAttribute('ref') } + $result.AdmxSha256 = (Get-FileHash -LiteralPath $path -Algorithm SHA256 -ErrorAction Stop).Hash + $result.Status = 'Supported' + $result.Diagnostic = 'Reviewed host build and exact local machine ADMX mapping found. Runtime observation and event validation are separate.' + } catch { $result.Status = 'Unknown'; $result.Diagnostic = $_.Exception.Message } + return $result +} + +function Get-WelaSmbAuditRuntime { + param($Definition) + $command = if ($Definition.Component -eq 'LanmanServer') { 'Get-SmbServerConfiguration' } else { 'Get-SmbClientConfiguration' } + $result = [pscustomobject]@{ Command = $command; Property = $Definition.Name; Status = 'NotExposed'; Value = $null; Diagnostic = '' } + try { + if (-not (Get-Command -Name $command -ErrorAction SilentlyContinue)) { + $result.Diagnostic = 'Runtime cmdlet is unavailable; registry-only verification cannot establish effective auditing.' + return $result + } + $configuration = & $command -ErrorAction Stop + if (-not $configuration) { throw 'Runtime cmdlet returned no configuration.' } + $property = $configuration.PSObject.Properties[$Definition.Name] + if ($null -eq $property) { + $result.Diagnostic = 'This runtime object does not expose the audit property; registry-only verification cannot establish effective auditing.' + return $result + } + if ($property.Value -isnot [bool]) { throw 'Runtime audit property is not a Boolean.' } + $result.Status = 'Observed'; $result.Value = $property.Value + $result.Diagnostic = 'Observed runtime configuration, not proof of generated or collected events.' + } catch { $result.Status = 'Unknown'; $result.Diagnostic = $_.Exception.Message } + return $result +} + +function Get-WelaSmbAuditState { + param($Definition) + $capability = Get-WelaSmbAuditCapability -Definition $Definition -HostState (Get-WelaSmbAuditHost) + $policy = $null; $runtime = $null + if ($capability.Status -eq 'Supported') { + $policy = Get-WelaRegistryState -Path $Definition.Path -Name $Definition.Name + $runtime = Get-WelaSmbAuditRuntime -Definition $Definition + } + [pscustomobject]@{ Capability = $capability; Policy = $policy; Runtime = $runtime; VerificationScope = $(if ($runtime -and $runtime.Status -eq 'Observed') { 'Policy registry and observed runtime' } else { 'Policy registry only; effective auditing not established' }) } +} + +function Test-WelaSmbAuditCompliance { + param($Snapshot) + return $Snapshot.Capability.Status -eq 'Supported' -and $Snapshot.Policy.ValueExists -and + $Snapshot.Policy.Type -eq 'DWord' -and $Snapshot.Policy.Value -eq 1 -and + ($Snapshot.Runtime.Status -eq 'NotExposed' -or ($Snapshot.Runtime.Status -eq 'Observed' -and $Snapshot.Runtime.Value)) +} + +function Get-WelaSmbAuditPlan { + foreach ($definition in Get-WelaSmbAuditDefinitions) { + $state = $null + try { + $state = Get-WelaSmbAuditState -Definition $definition + $status = if ($state.Capability.Status -ne 'Supported') { $state.Capability.Status } + elseif ($state.Runtime.Status -eq 'Unknown') { 'Unknown' } + elseif (Test-WelaSmbAuditCompliance $state) { 'Compliant' } else { 'ChangeRequired' } + $diagnostic = if ($state.Capability.Status -ne 'Supported') { $state.Capability.Diagnostic } else { $state.Runtime.Diagnostic } + [pscustomobject]@{ Definition = $definition; Status = $status; Before = $state; Diagnostic = $diagnostic } + } catch { [pscustomobject]@{ Definition = $definition; Status = 'Unknown'; Before = $state; Diagnostic = $_.Exception.Message } } + } +} + +function Set-WelaSmbAuditControls { + param($Context, [array]$Plan) + foreach ($entry in $Plan) { + $definition = $entry.Definition + $id = "SmbAudit/$($definition.Component)/$($definition.Name)" + if ($entry.Status -in @('NotApplicable', 'Unknown')) { + $Context.Results.Add([pscustomobject]@{ Id = $id; Kind = 'SmbAudit'; Target = @{ Path = $definition.Path; Name = $definition.Name }; Desired = @{ Value = 1; Type = 'DWord' }; Before = $entry.Before; After = $entry.Before; Status = $(if ($entry.Status -eq 'NotApplicable') { 'Skipped' } else { 'Failed' }); Diagnostic = "$($entry.Status): $($entry.Diagnostic)" }) + continue + } + $callback = @{ Definition = $definition; Observed = $null } + $read = { + param($state) + $snapshot = Get-WelaSmbAuditState -Definition $state.Definition + if ($snapshot.Capability.Status -ne 'Supported') { throw "SMB policy capability changed: $($snapshot.Capability.Diagnostic)" } + if ($snapshot.Runtime.Status -eq 'Unknown') { throw "Runtime observation failed: $($snapshot.Runtime.Diagnostic)" } + $state.Observed = $snapshot + return $snapshot + } + $test = { param($snapshot) Test-WelaSmbAuditCompliance $snapshot } + $apply = { + param($state) + $fresh = Get-WelaSmbAuditState -Definition $state.Definition + if ($fresh.Capability.Status -ne 'Supported' -or $fresh.Runtime.Status -eq 'Unknown') { throw 'Capability/runtime could no longer be read; no policy was written.' } + foreach ($field in @('KeyExists', 'ValueExists', 'Value', 'Type')) { + if ($fresh.Policy.$field -ne $state.Observed.Policy.$field) { throw 'Policy changed after the recovery snapshot; review policy and retry.' } + } + New-WelaRegistryKey -Path $state.Definition.Path + Set-ItemProperty -LiteralPath $state.Definition.Path -Name $state.Definition.Name -Type DWord -Value 1 -ErrorAction Stop + 'Audit policy DWORD written. Runtime may require policy refresh; event generation/collection and policy persistence remain unverified.' + } + Invoke-WelaConfigurationControl -Context $Context -Id $id -Kind SmbAudit -Target @{ Path = $definition.Path; Name = $definition.Name } ` + -Desired @{ Value = 1; Type = 'DWord' } -Read $read -Compliant $test -Apply $apply -CallbackState $callback ` + -Description 'Set this supported SMB audit policy to DWORD 1 without changing security requirements.' + } +} + +function Invoke-WelaSmbAuditCommand { + param([ValidateSet('Audit', 'Plan', 'Configure')][string]$Action = 'Audit', [switch]$Auto, [switch]$DryRun, [string]$BackupPath, [string]$ResultsPath) + if ($env:OS -ne 'Windows_NT') { throw 'SMB auditing requires Windows.' } + if ($DryRun -and $Action -ne 'Configure') { throw '-DryRun applies only to SmbAction Configure; Audit and Plan are read-only.' } + $plan = @(Get-WelaSmbAuditPlan) + if ($Action -eq 'Configure') { + $context = New-WelaConfigurationContext -Auto:$Auto -DryRun:$DryRun -BackupPath $BackupPath + Set-WelaSmbAuditControls -Context $context -Plan $plan + Write-Host 'SMB verification covers the policy registry and available runtime properties only. Event generation, collection and persistence after policy refresh are not established.' -ForegroundColor Yellow + return Complete-WelaConfiguration -Context $context -ResultsPath $ResultsPath -Scope 'smb-audit-policies-only' + } + $report = [pscustomobject]@{ Scope = 'smb-audit-policies-only'; Action = $Action; Controls = $plan; ExitCode = $(if (@($plan | Where-Object Status -eq Unknown).Count) { 1 } else { 0 }) } + if ($ResultsPath) { $report | ConvertTo-Json -Depth 14 | Set-Content -LiteralPath $ResultsPath -Encoding UTF8 -ErrorAction Stop } + return $report +} diff --git a/tests/SmbAuditing.Tests.ps1 b/tests/SmbAuditing.Tests.ps1 new file mode 100644 index 00000000..a71d4469 --- /dev/null +++ b/tests/SmbAuditing.Tests.ps1 @@ -0,0 +1,171 @@ +$ErrorActionPreference = 'Stop' +$repo = Split-Path $PSScriptRoot -Parent +$script:ScriptRoot = $repo +. (Join-Path $repo 'scripts/Configuration.ps1') +. (Join-Path $repo 'scripts/SmbAuditing.ps1') +$script:assertions = 0; $script:cleanup = @() +$root = Join-Path ([IO.Path]::GetTempPath()) ('wela-smb-' + [guid]::NewGuid().ToString('N')) +$script:cleanup += $root +$null = New-Item -ItemType Directory -Path (Join-Path $root 'PolicyDefinitions') -Force +$savedWindir = $env:windir; $savedOS = $env:OS +$env:windir = $root +function Assert($Condition, [string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:assertions++ } +function Write-Admx([string]$Omit = '', [string]$WrongType = '') { + foreach ($component in @('LanmanServer', 'LanmanWorkstation')) { + $policies = @() + foreach ($definition in @(Get-WelaSmbAuditDefinitions | Where-Object Component -eq $component)) { + if ($definition.Name -eq $Omit) { continue } + $enabled = if ($definition.Name -eq $WrongType) { '1' } else { '' } + $policies += '' + $enabled + '' + } + '' + ($policies -join '') + '' | Set-Content -LiteralPath (Join-Path (Join-Path $root 'PolicyDefinitions') "$component.admx") -Encoding UTF8 + } +} +function Reset-Mocks { + $script:build = 26100; $script:productType = 1 + $script:hostFails = $false; $script:registryFails = $false; $script:runtimeFails = $false; $script:writeFails = $false + $script:runtimeMissing = $false; $script:runtimeFollows = $true; $script:wrongTypeWrite = $false + $script:writes = 0; $script:keysCreated = 0; $script:registry = @{}; $script:runtime = @{}; $script:onPrompt = $null + foreach ($definition in Get-WelaSmbAuditDefinitions) { + $id = "$($definition.Component)/$($definition.Name)" + $script:registry[$id] = [pscustomobject]@{ KeyExists = $false; ValueExists = $false; Value = $null; Type = $null } + $script:runtime[$id] = $false + } + Write-Admx +} +function Get-CimInstance { + param($ClassName, $Property, $ErrorAction) + if ($script:hostFails) { throw 'OS query denied' } + [pscustomobject]@{ ProductType = $script:productType; BuildNumber = [string]$script:build; Version = "10.0.$script:build"; Caption = 'Mock Windows' } +} +function Get-WelaRegistryState { + param($Path, $Name) + if ($script:registryFails) { throw 'Policy read denied' } + $component = ($Path -split '\\')[-1] + $source = $script:registry["$component/$Name"] + if (-not $source) { throw "Unexpected policy path $Path/$Name" } + [pscustomobject]@{ KeyExists = $source.KeyExists; ValueExists = $source.ValueExists; Value = $source.Value; Type = $source.Type } +} +function New-WelaRegistryKey { param($Path) $script:keysCreated++ } +function Set-ItemProperty { + param($LiteralPath, $Name, $Value, $Type, $ErrorAction) + $definition = @(Get-WelaSmbAuditDefinitions | Where-Object { $_.Path -eq $LiteralPath -and $_.Name -eq $Name }) + Assert ($definition.Count -eq 1 -and $Value -eq 1 -and $Type -eq 'DWord') 'Only six exact audit DWORD paths can be written' + $entries = @(Get-Content -LiteralPath (Join-Path $script:context.BackupPath 'before.jsonl') | ConvertFrom-Json) + Assert ($entries[-1].Target.Name -eq $Name -and $entries[-1].Target.Path -eq $LiteralPath) 'Matching recovery evidence precedes the write' + Assert ($entries[-1].Before.Capability.AdmxSha256 -and $entries[-1].Before.Policy) 'Journal retains registry state and ADMX evidence' + if ($script:writeFails) { throw 'Policy write denied' } + $script:writes++ + $id = "$($definition[0].Component)/$Name" + $script:registry[$id] = [pscustomobject]@{ KeyExists = $true; ValueExists = $true; Value = 1; Type = $(if ($script:wrongTypeWrite) { 'String' } else { 'DWord' }) } + if ($script:runtimeFollows) { $script:runtime[$id] = $true } +} +function Get-Runtime($Component) { + if ($script:runtimeFails) { throw 'Runtime query denied' } + $values = @{ RequireSecuritySignature = $true; EnableInsecureGuestLogons = $false } + if (-not $script:runtimeMissing) { + foreach ($definition in @(Get-WelaSmbAuditDefinitions | Where-Object Component -eq $Component)) { $values[$definition.Name] = $script:runtime["$Component/$($definition.Name)"] } + } + [pscustomobject]$values +} +function Get-SmbClientConfiguration { param($ErrorAction) Get-Runtime 'LanmanWorkstation' } +function Get-SmbServerConfiguration { param($ErrorAction) Get-Runtime 'LanmanServer' } +function Read-Host { param($Prompt) if ($script:onPrompt) { & $script:onPrompt }; 'Y' } +function New-TestContext([switch]$DryRun, [switch]$Prompt) { + $path = Join-Path $root ([guid]::NewGuid().ToString('N')) + $script:context = New-WelaConfigurationContext -DryRun:$DryRun -Auto:(-not $Prompt) -BackupPath $path + return $script:context +} +try { + Reset-Mocks + foreach ($case in @(@(26100, 1), @(26200, 1), @(26100, 2), @(26100, 3))) { + $script:build = $case[0]; $script:productType = $case[1] + $plan = @(Get-WelaSmbAuditPlan) + Assert ($plan.Count -eq 6 -and @($plan | Where-Object Status -eq ChangeRequired).Count -eq 6) 'Reviewed client/server/DC builds require the six policies when exact ADMX exists' + } + Reset-Mocks + $script:build = 20348; $script:productType = 3 + $plan = @(Get-WelaSmbAuditPlan) + Assert (@($plan | Where-Object Status -eq NotApplicable).Count -eq 6) 'Server 2022 stays unsupported even with copied newer ADMX' + $context = New-TestContext + Set-WelaSmbAuditControls $context $plan + Assert ($script:writes -eq 0 -and $script:keysCreated -eq 0 -and @($context.Results | Where-Object Status -eq Skipped).Count -eq 6) 'Unsupported host gets explicit skips and no created policy keys' + $script:build = 30000 + Assert (@(Get-WelaSmbAuditPlan | Where-Object Status -eq Unknown).Count -eq 6) 'Unreviewed future builds are Unknown' + + Reset-Mocks + Write-Admx -Omit AuditInsecureGuestLogon + Assert (@(Get-WelaSmbAuditPlan | Where-Object Status -eq Unknown).Count -eq 2) 'Missing local ADMX controls are individually gated' + Write-Admx -WrongType AuditInsecureGuestLogon + Assert (@(Get-WelaSmbAuditPlan | Where-Object Status -eq Unknown).Count -eq 2) 'An ADMX string value 1 does not authorize a DWORD policy write' + Remove-Item -LiteralPath (Join-Path $root 'PolicyDefinitions/LanmanServer.admx') + Assert (@(Get-WelaSmbAuditPlan | Where-Object Status -eq Unknown).Count -eq 4) 'Missing ADMX file blocks its component while preserving other controls' + ']>&external;' | Set-Content -LiteralPath (Join-Path $root 'PolicyDefinitions/LanmanServer.admx') + Assert (@(Get-WelaSmbAuditPlan | Where-Object { $_.Definition.Component -eq 'LanmanServer' -and $_.Status -eq 'Unknown' }).Count -eq 3) 'ADMX external entities are rejected' + + Reset-Mocks + $plan = @(Get-WelaSmbAuditPlan) + $context = New-TestContext -DryRun + Set-WelaSmbAuditControls $context $plan + Assert ($script:writes -eq 0 -and $script:keysCreated -eq 0 -and -not (Test-Path $context.BackupPath)) 'Dry run creates no registry policy or recovery directory' + $context = New-TestContext + Set-WelaSmbAuditControls $context $plan + Assert ($script:writes -eq 6 -and (Complete-WelaConfiguration $context -Scope smb-audit-policies-only).ExitCode -eq 0) 'All six exact policies are applied and runtime observations confirm them' + $context = New-TestContext + Set-WelaSmbAuditControls $context @(Get-WelaSmbAuditPlan) + Assert ($script:writes -eq 6 -and @($context.Results | Where-Object Status -eq AlreadyCompliant).Count -eq 6) 'Confirmed policy/runtime state is idempotent' + $script:registry['LanmanServer/AuditInsecureGuestLogon'].Value = 0 + Assert ((Complete-WelaConfiguration $context).ExitCode -eq 1 -and $context.Results[2].Status -eq 'Overridden') 'Final policy refresh drift changes status and exit code' + + Reset-Mocks + $script:runtimeFollows = $false + $context = New-TestContext + Set-WelaSmbAuditControls $context @(Get-WelaSmbAuditPlan) + Assert ($script:writes -eq 6 -and (Complete-WelaConfiguration $context).Failed -eq 6) 'Policy DWORD alone cannot claim effective success when runtime is observably false' + Assert ($context.Results[0].After.Policy.Value -eq 1 -and $context.Results[0].After.Runtime.Value -eq $false) 'Failed effective read-back keeps policy and runtime separate' + + Reset-Mocks + $script:runtimeMissing = $true + $context = New-TestContext + Set-WelaSmbAuditControls $context @(Get-WelaSmbAuditPlan) + Assert ((Complete-WelaConfiguration $context).ExitCode -eq 0) 'Exact ADMX permits registry-only configuration when runtime property is absent' + Assert ($context.Results[0].After.Runtime.Status -eq 'NotExposed' -and $context.Results[0].After.VerificationScope -like '*effective auditing not established*') 'Registry-only outcome never claims runtime confirmation' + + Reset-Mocks + $script:wrongTypeWrite = $true + $context = New-TestContext + Set-WelaSmbAuditControls $context @(Get-WelaSmbAuditPlan) + Assert ((Complete-WelaConfiguration $context).Failed -eq 6) 'Read-back rejects wrong registry type despite a numeric match' + foreach ($errorKind in @('hostFails', 'registryFails', 'runtimeFails')) { + Reset-Mocks + Set-Variable -Name $errorKind -Scope Script -Value $true + $context = New-TestContext + Set-WelaSmbAuditControls $context @(Get-WelaSmbAuditPlan) + Assert ($script:writes -eq 0 -and (Complete-WelaConfiguration $context).Failed -eq 6) "$errorKind becomes an explicit failure without writes" + } + Reset-Mocks + $script:runtime['LanmanServer/AuditInsecureGuestLogon'] = 'False' + Assert (@(Get-WelaSmbAuditPlan | Where-Object Status -eq Unknown).Count -eq 1) 'String False is not coerced into a true runtime Boolean' + Reset-Mocks + $script:writeFails = $true + $context = New-TestContext + Set-WelaSmbAuditControls $context @((Get-WelaSmbAuditPlan)[0]) + Assert ((Complete-WelaConfiguration $context).Failed -eq 1) 'Write errors propagate to results' + Reset-Mocks + $script:onPrompt = { $script:registry['LanmanServer/AuditClientDoesNotSupportEncryption'].Value = 42 } + $context = New-TestContext -Prompt + Set-WelaSmbAuditControls $context @((Get-WelaSmbAuditPlan)[0]) + Assert ($script:writes -eq 0 -and $context.Results[0].Status -eq 'Failed') 'Policy changes during confirmation are not overwritten with stale recovery data' + + Reset-Mocks + $env:OS = 'Windows_NT' + $json = Join-Path $root 'plan.json' + $report = Invoke-WelaSmbAuditCommand -Action Plan -ResultsPath $json + Assert ($report.ExitCode -eq 0 -and (Get-Content $json -Raw | ConvertFrom-Json).Controls.Count -eq 6) 'Public plan writes six complete controls to JSON' + $report = Invoke-WelaSmbAuditCommand -Action Configure -DryRun -BackupPath (Join-Path $root 'dry') + Assert ($report.DryRun -and $script:writes -eq 0 -and $report.Scope -eq 'smb-audit-policies-only') 'Public entrypoint keeps SMB scope and dry-run semantics' + Write-Host "PASS: $script:assertions SMB audit assertions (mocked policies/runtime; real temporary ADMX parsing)." +} finally { + $env:windir = $savedWindir; $env:OS = $savedOS + foreach ($path in $script:cleanup) { if (Test-Path $path) { Remove-Item -LiteralPath $path -Recurse -Force } } +} diff --git a/tests/SmbAuditing.Windows.Tests.ps1 b/tests/SmbAuditing.Windows.Tests.ps1 new file mode 100644 index 00000000..9d1143a7 --- /dev/null +++ b/tests/SmbAuditing.Windows.Tests.ps1 @@ -0,0 +1,31 @@ +# Native Windows read-only evidence: policy registry, local ADMX and SMB getters only. +$ErrorActionPreference = 'Stop' +if ($env:OS -ne 'Windows_NT') { Write-Host 'SKIP: Windows only'; return } +$repo = Split-Path $PSScriptRoot -Parent +. (Join-Path $repo 'scripts/Configuration.ps1') +. (Join-Path $repo 'scripts/SmbAuditing.ps1') +function Read-PolicySnapshot { + foreach ($definition in Get-WelaSmbAuditDefinitions) { + [pscustomobject]@{ Component = $definition.Component; Name = $definition.Name; State = Get-WelaRegistryState -Path $definition.Path -Name $definition.Name } + } +} +$before = @(Read-PolicySnapshot) | ConvertTo-Json -Depth 8 +$serverBefore = Get-SmbServerConfiguration | Select-Object RequireSecuritySignature, EncryptData, RejectUnencryptedAccess | ConvertTo-Json +$clientBefore = Get-SmbClientConfiguration | Select-Object RequireSecuritySignature, RequireEncryption, EnableInsecureGuestLogons | ConvertTo-Json +$plan = @(Get-WelaSmbAuditPlan) +if ($plan.Count -ne 6) { throw 'Expected all six SMB audit controls.' } +foreach ($entry in $plan) { + if ($entry.Status -notin @('NotApplicable', 'Unknown', 'ChangeRequired', 'Compliant')) { throw 'Unexpected assessment status.' } + Write-Host "$($entry.Definition.Component)/$($entry.Definition.Name): $($entry.Status); $($entry.Diagnostic)" + if ($entry.Before.Runtime) { Write-Host "Runtime: $($entry.Before.Runtime.Status) / $($entry.Before.Runtime.Value)" } +} +$path = Join-Path ([IO.Path]::GetTempPath()) ('wela-smb-readonly-' + [guid]::NewGuid().ToString('N')) +$context = New-WelaConfigurationContext -DryRun -Auto -BackupPath $path +Set-WelaSmbAuditControls -Context $context -Plan $plan +if (Test-Path -LiteralPath $path) { throw 'Dry run created an unexpected recovery directory.' } +if (@($context.Results | Where-Object Status -eq Applied).Count) { throw 'Dry run applied a control.' } +$after = @(Read-PolicySnapshot) | ConvertTo-Json -Depth 8 +$serverAfter = Get-SmbServerConfiguration | Select-Object RequireSecuritySignature, EncryptData, RejectUnencryptedAccess | ConvertTo-Json +$clientAfter = Get-SmbClientConfiguration | Select-Object RequireSecuritySignature, RequireEncryption, EnableInsecureGuestLogons | ConvertTo-Json +if ($before -ne $after -or $serverBefore -ne $serverAfter -or $clientBefore -ne $clientAfter) { throw 'Policy or security requirements changed during read-only test.' } +Write-Host 'PASS: native policy/ADMX/runtime observation and dry-run evidence; audit policies and security requirements unchanged. No event generation or ingestion test performed.' diff --git a/website/docs/resources/changelog.ja.md b/website/docs/resources/changelog.ja.md index 4f073d1f..e80d7462 100644 --- a/website/docs/resources/changelog.ja.md +++ b/website/docs/resources/changelog.ja.md @@ -7,6 +7,7 @@ **改善:** +- 6つのネイティブSMB監査ポリシーを監査・計画・設定する任意実行の`smb-auditing`を追加しました。OSビルドとローカルADMXの正確な定義を確認してから書き込み、ポリシーのDWORD値と取得可能な実行時設定を分けて表示します。Dry-run、復旧用記録、最終検証に対応し、署名・暗号化要件やゲストアクセスは変更しません。実イベント生成と収集の検証は別途必要です。 (issue #377) (@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..b7a8b242 100644 --- a/website/docs/resources/changelog.md +++ b/website/docs/resources/changelog.md @@ -7,6 +7,7 @@ **Improvements:** +- Added opt-in `smb-auditing` audit, plan and configure actions for six native SMB audit policies. Host builds and exact local ADMX mappings gate writes; policy DWORDs and available runtime values are reported separately, with dry-run, recovery journaling and final verification. Signing/encryption requirements and guest access are not changed; runtime event/ingestion validation remains required. (issue #377) (@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)