mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-08 15:25:24 +02:00
Integrate reviewed CLI and native channel configuration changes
This commit is contained in:
commit
f3bd83a521
11 files changed
+287
-2
No files matched your search
@@ -0,0 +1,35 @@
|
||||
name: Public CLI unknown argument rejection
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
cli-arguments:
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2022, windows-2025]
|
||||
engine: [powershell, pwsh]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
- name: Windows PowerShell 5.1 process and native state checks
|
||||
if: matrix.engine == 'powershell'
|
||||
shell: powershell
|
||||
run: ./tests/CliArguments.Tests.ps1
|
||||
- name: PowerShell 7 process and native state checks
|
||||
if: matrix.engine == 'pwsh'
|
||||
shell: pwsh
|
||||
run: ./tests/CliArguments.Tests.ps1
|
||||
- name: Retain native before and after observations
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: cli-arguments-${{ matrix.os }}-${{ matrix.engine }}
|
||||
path: ${{ runner.temp }}/wela-cli-arguments.json
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Native channel configuration acceptance
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
native-channel-configure:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2022, windows-2025]
|
||||
engine: [powershell, pwsh]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
- name: Fixtures and public guards in Windows PowerShell5.1
|
||||
if: matrix.engine == 'powershell'
|
||||
shell: powershell
|
||||
run: |
|
||||
./tests/NativeChannelAccess.Tests.ps1
|
||||
./tests/NativeChannelAccess.Windows.Tests.ps1
|
||||
- name: Native channel configuration in Windows PowerShell5.1
|
||||
if: matrix.engine == 'powershell'
|
||||
shell: powershell
|
||||
run: ./tests/NativeChannelConfigure.Windows.Tests.ps1 -AllowDisposableChannelWrite
|
||||
- name: Fixtures and public guards in PowerShell7
|
||||
if: matrix.engine == 'pwsh'
|
||||
shell: pwsh
|
||||
run: |
|
||||
./tests/NativeChannelAccess.Tests.ps1
|
||||
./tests/NativeChannelAccess.Windows.Tests.ps1
|
||||
- name: Native channel configuration in PowerShell7
|
||||
if: matrix.engine == 'pwsh'
|
||||
shell: pwsh
|
||||
run: ./tests/NativeChannelConfigure.Windows.Tests.ps1 -AllowDisposableChannelWrite
|
||||
- name: Retain owned fixture evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: native-channel-configure-${{ matrix.os }}-${{ matrix.engine }}
|
||||
path: ${{ runner.temp }}/wela-channel-configure-*/
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
- 既存の Script AuditOnly ポリシーに対し、固定の Windows PowerShell5.1 スクリプトを実行する `applocker-script-probe` を追加しました。実行者と子プロセスのログオン、保持したファイル、高精度 UTC とイベント記録境界を確認し、Script8005 の許可と8006 の監査専用ブロック判定を区別します。拒否・上限・重複・状態変化は未検証とし、設定変更や Sigma の評価加算は行いません。使い捨て Windows の4構成で両イベントとポリシー・チャネル・タスクの復元を検証し、保護された AppIDSvc を停止できない場合は明記します。 (関連 #381) (@Shirofune-Security)
|
||||
|
||||
- 従来の設定コマンドでも、未対応の `-WhatIf` や入力ミスなどの未認識引数を実行前に拒否するようにしました。`-ErrorAction` や `-Verbose` などの PowerShell 共通パラメーターも拒否するため、自動化ラッパーへの影響をヘルプと診断に明記しました。正しい位置指定引数と文書化された `-DryRun` の動作は維持し、Windows PowerShell 5.1 と PowerShell 7 で公開CLIを検証します。 (@Shirofune-Security)
|
||||
|
||||
- Windows PowerShell 5.1 と PowerShell 7、使い捨ての Server 2022/2025 で標準チャネル設定の公開CLIを検証するテストを追加しました。有効化・サイズ・CAPI2読み取り専用権限の適用、既存記述子と大きいバッファーの保持、変更前記録、DryRun、再実行時の無変更、元設定への復元を確認し、ハッシュ付きの証拠を保存します。転送・保存期間・Sigmaの検証は別途必要です。 (@Shirofune-Security)
|
||||
|
||||
- 完了済みのファイアウォールテキストログ設定を1プロファイルずつ復元する、明示的な `firewall-recovery` を追加しました。元の記録・結果、確認済み計画ハッシュ、実行者・ソース、永続記録と変更前後の確認により、PersistentStore の4項目だけを復元し、強制設定・他のプロファイル・ルールとフィルターを保持します。実効ポリシーを別に報告し、途中失敗を未検証として扱い、自動ロールバックや Sigma 加点は行いません。使い捨て環境の公開 CLI で設定、変更検出、復元、冪等性、完全な後始末を検証します。(関連 #375) (@Shirofune-Security)
|
||||
|
||||
- 固定のオフライン一時証明書チェーン構築とローカル CAPI2 イベント11を確認する `capi2-probe` Plan/Run を追加。公開証明書・nonce・PID・トークン・高精度UTCによる照合、ワーカーと収集の時間制限、証拠保存に対応。既存のチャネル、証明書ストア、信頼設定を維持し、TLS、失効確認、リモート転送、Sigma の検証実績は付与しません。
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
- Added opt-in `applocker-script-probe` for a fixed native Windows PowerShell5.1 script under an existing Script AuditOnly policy. Actual caller/child logon context, held file bytes, precise UTC and native record boundaries distinguish exact Script8005 allowed and8006 would-block events; denied, capped, ambiguous or drifted runs remain unverified. The disposable four-way Windows suite requires both native decisions and policy/channel/task cleanup, with the protected-AppIDSvc stopping boundary recorded. No configuration changes or Sigma credit. (Related #381) (@Shirofune-Security)
|
||||
|
||||
- Reject unbound command-line arguments before dispatch, including unsupported `-WhatIf` and misspelled options on legacy configuration commands. PowerShell common parameters such as `-ErrorAction` and `-Verbose` are also rejected; help and diagnostics explain the automation-wrapper compatibility change. Valid positional arguments and documented `-DryRun` behavior are preserved. Public CLI regressions cover both Windows PowerShell 5.1 and PowerShell 7. (@Shirofune-Security)
|
||||
|
||||
- Added disposable Server 2022/2025 validation of public native channel configuration under Windows PowerShell 5.1 and PowerShell 7. Tests apply enable/size controls and the explicit CAPI2 read-only grant, verify descriptor preservation, journals, larger buffers, DryRun and idempotence, and retain hashed native evidence with exact fixture cleanup. Forwarding, retention-duration and Sigma validation remain separate. (@Shirofune-Security)
|
||||
|
||||
- Added opt-in `firewall-recovery` for one completed firewall text-log operation. Strict original journal/result matching, reviewed plan hashes, native operator/source guards, durable receipts and exact four-field PersistentStore restoration preserve enforcement, other profiles and bounded rule/filter configuration. Effective policy stays separately reported; partial writes remain unverified without automatic rollback or Sigma credit. Disposable public-CLI tests cover configuration, drift refusal, recovery, idempotence and exact cleanup. (Related #375) (@Shirofune-Security)
|
||||
|
||||
- Added explicit `capi2-probe` Plan/Run for a fixed offline ephemeral certificate-chain build and exact local CAPI2 event 11 evidence, with public certificate/nonce/PID/token/precise-UTC binding, bounded worker/collection and retained artifacts. Existing channel, certificate-store and trust configuration are preserved; no TLS, revocation, remote delivery or Sigma credit is claimed.
|
||||
|
||||
@@ -1945,6 +1945,10 @@ function Get-WelaUserProfiles {
|
||||
}
|
||||
|
||||
$usage = @"
|
||||
WELA.ps1 accepts only its documented script parameters. PowerShell common parameters
|
||||
(-ErrorAction, -Verbose, -WarningAction, -InformationAction) are not supported.
|
||||
Remove these options from automation wrappers; check WELA's exit code instead.
|
||||
|
||||
Usage:
|
||||
./WELA.ps1 dns-analytical -Help # Dedicated DNS Server direct-channel lifecycle
|
||||
./WELA.ps1 wec-runtime -WecRuntimeId subscription-id -ResultsPath new-runtime.json
|
||||
@@ -2238,6 +2242,13 @@ if ($Cmd -ne 'ldap-diagnostics' -and @($PSBoundParameters.Keys | Where-Object {
|
||||
throw 'LDAP options require the dedicated ldap-diagnostics command. No command was run.'
|
||||
}
|
||||
|
||||
# Plain scripts retain unknown named options in $args. Check them before every
|
||||
# dispatch, including the profile shortcut, so an unsupported -WhatIf or typo
|
||||
# cannot accidentally reach a writer. Keep dedicated option diagnostics above.
|
||||
if ($args.Count -gt 0) {
|
||||
throw 'Unsupported trailing arguments. PowerShell common parameters (for example -ErrorAction or -Verbose) are not supported. Check -Help for documented options; no command was run.'
|
||||
}
|
||||
|
||||
if ($Profile -and $Cmd.ToLower() -in @('plan', 'audit', 'audit-settings', 'configure') -and -not $Help) {
|
||||
Invoke-WelaProfileCommand -Command $Cmd.ToLower()
|
||||
return
|
||||
@@ -2701,6 +2712,7 @@ switch ($Cmd.ToLower()) {
|
||||
Write-Host " -BackupPath New directory for the pre-change recovery journal (unique default beside WELA)"
|
||||
Write-Host " -ResultsPath Save structured per-control outcomes as JSON"
|
||||
Write-Host ""
|
||||
Write-Host "PowerShell common parameters (-ErrorAction, -Verbose, -WarningAction, -InformationAction) are not supported. Remove them from wrappers and check the exit code."
|
||||
Write-Host "Without -Profile, configure applies the YamatoSecurity native logging settings. -Profile applies advanced audit policy and its precedence prerequisite. -DryRun and recovery/results options work with both."
|
||||
Write-Host ""
|
||||
return
|
||||
|
||||
@@ -18,6 +18,10 @@ or result-file error also exits with status 1.
|
||||
.\WELA.ps1 configure -Auto -ResultsPath .\results.json
|
||||
```
|
||||
|
||||
Unknown named options and other arguments left unbound by PowerShell are rejected before command dispatch. This includes unsupported `-WhatIf`, `-Confirm` and misspelled `-DryRun` options, even with `-Auto`. Use each command's `-Help` for its supported preview options; `-DryRun` is accepted only where documented. Valid positional binding and PowerShell's unambiguous parameter abbreviations remain supported.
|
||||
|
||||
`WELA.ps1` is a plain PowerShell script and does not accept PowerShell common parameters such as `-ErrorAction`, `-Verbose`, `-WarningAction` or `-InformationAction`. Earlier versions silently ignored those unbound options; they now produce exit code 1 before any command runs, including read-only commands. Remove them from automation wrappers and use WELA's exit code and structured results to check the outcome. The explicitly declared WELA `-Debug` switch remains supported where documented.
|
||||
|
||||
Keep the complete WELA directory, including `scripts/Configuration.ps1`. Choose a
|
||||
recovery path whose parent directory is writable only by the operators who manage
|
||||
these settings. The backup directory must not already exist. Without `-BackupPath`,
|
||||
|
||||
@@ -36,9 +36,11 @@ Recovery is manual: review each journal `Before` against the current settings, i
|
||||
|
||||
The checked-in inventory maps source query IDs to 12 baseline channels and 8 suspect channels (18 unique combined). Baseline queries 12 (EMET) and 39 (Sysmon) are explicitly excluded. Native-only scope excludes external agents; channel settings do not create subscriptions, add service identities to groups or configure WinRM/collectors. The Microsoft [source prerequisite guidance and sample queries](https://learn.microsoft.com/en-us/windows/security/operating-system-security/device-management/use-windows-event-forwarding-to-assist-in-intrusion-detection) remain a starting point for reviewing role applicability. The report always lists token/group membership, producer configuration, representative event generation and forwarding/ingestion as unverified; required disabled or missing channels remain visible. Other inventoried channels are not automatically enabled.
|
||||
|
||||
Safe tests exercise the actual command/JSON/runner with mocked Windows setters. Windows PowerShell 5.1 and PowerShell 7 CI additionally exercise real descriptor serialization and read-only CLI inspection. Release packaging already includes the whole `config`, `modules` and `scripts` directories.
|
||||
Safe tests exercise the actual command/JSON/runner with mocked Windows setters. Windows PowerShell 5.1 and PowerShell 7 CI also exercise real descriptor serialization and read-only CLI inspection. A separate four-way disposable Server 2022/2025 suite exercises the public Plan, Configure with DryRun, Configure without a reader grant, explicit read-only grant, and repeated idempotent configuration. It verifies exact native descriptor bytes, recovery journal contents, preservation of an existing 2 GiB buffer, all other channel XML fields, and restoration of the original channel settings and all 59 audit masks. Hashed artifacts retain original/configured XML, reports, journal and cleanup evidence.
|
||||
|
||||
**Isolated Windows acceptance evidence is still pending; related to issue #367, not sufficient to close it.** On patched Windows 11, member server, DC and ADCS snapshots where the channels exist:
|
||||
That fixture changes only the three declared channels on explicitly opted-in GitHub-hosted disposable VMs. Restoring the original smaller sizes can discard events generated during the test; it does not restore event records or prove retention duration. It never supplies production forwarding-token access, event generation, collector arrival, policy-refresh persistence or Sigma evidence. Do not run the mutating fixture on ordinary machines. Release packaging already includes the whole `config`, `modules` and `scripts` directories.
|
||||
|
||||
**Broader Windows acceptance remains pending; related to issue #367, not sufficient to close it.** Hosted server configuration checks do not cover Windows 11 or domain-specific access and forwarding behavior. On patched Windows 11, member server, DC and ADCS snapshots where the channels exist:
|
||||
|
||||
1. Save the plan, channel metadata, descriptor and policy context. Review capacity and the intended forwarding identity. Capture the actual identity/token memberships separately.
|
||||
2. Apply the opt-in profile, retain the journal/results, then independently read enablement, exact bytes and full SDDL. Compare all original ACEs plus owner/group/SACL/flags and repeat after policy refresh.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Public process-boundary regression: no mocked dispatcher or Windows writers.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = Split-Path $PSScriptRoot -Parent
|
||||
$engine = (Get-Process -Id $PID).Path
|
||||
$count = 0
|
||||
$root = Join-Path ([IO.Path]::GetTempPath()) ('wela-cli-arguments-' + [guid]::NewGuid().ToString('N'))
|
||||
$null = New-Item -ItemType Directory -Path $root
|
||||
function Assert($Value, $Message) { if (-not $Value) { throw $Message }; $script:count++ }
|
||||
function Invoke-Case([string[]]$Arguments, [int]$Expected, [string]$Pattern) {
|
||||
$prior = $ErrorActionPreference
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$output = & $engine -NoLogo -NoProfile -NonInteractive -File "$repo/WELA.ps1" @Arguments 2>&1 | Out-String
|
||||
$code = $LASTEXITCODE
|
||||
} finally { $ErrorActionPreference = $prior }
|
||||
Assert ($code -eq $Expected -and $output -match $Pattern) "Unexpected public CLI exit/output [$code]: $output"
|
||||
}
|
||||
$isWindowsHost = [Environment]::OSVersion.Platform -eq [PlatformID]::Win32NT
|
||||
function Read-NativeState {
|
||||
$logs = @('Security','System','Application','ForwardedEvents','Microsoft-Windows-CAPI2/Operational')
|
||||
$state = [ordered]@{ Audit = Get-WelaEffectiveAuditPolicy; Channels = @() }
|
||||
foreach ($name in $logs) { $state.Channels += Get-WelaNativeChannel $name }
|
||||
return ($state | ConvertTo-Json -Depth 12 -Compress)
|
||||
}
|
||||
try {
|
||||
if ($isWindowsHost) {
|
||||
Import-Module "$repo/modules/AuditProfiles.psm1" -Force
|
||||
Import-Module "$repo/modules/NativeProviders.psm1" -Force
|
||||
$before = Read-NativeState
|
||||
}
|
||||
# These previously reached legacy writers, including the profile fast path.
|
||||
$commands = @(
|
||||
@('configure','-Auto'),
|
||||
@('configure','-Profile','wela-2.2.0','-Auto'),
|
||||
@('configure-eventlogs','-LogProfile','asd-collector-archive-2021-10','-ApplyLogMode','-Auto'),
|
||||
@('configure-sacl','-Auto'),
|
||||
@('channel-settings','-ChannelAction','Configure','-GrantEventLogReaders','-Auto'),
|
||||
@('powershell-transcription','-TranscriptionAction','Configure','-Auto'),
|
||||
@('firewall-logging','-FirewallAction','Configure','-Auto'),
|
||||
@('smb-auditing','-SmbAction','Configure','-Auto'),
|
||||
@('audit-integrity','-IntegrityAction','Configure','-Auto'),
|
||||
@('provider-packs','-ProviderAction','Configure','-Auto'),
|
||||
@('wec-collector','-WefAction','Configure','-Auto'),
|
||||
@('audit-settings','-Help')
|
||||
)
|
||||
foreach ($command in $commands) {
|
||||
foreach ($unknown in @('-WhatIf','-DryRnu')) {
|
||||
Invoke-Case ($command + @('-BackupPath',"$root/journal",'-ResultsPath',"$root/result.json",$unknown)) 1 'Unsupported trailing arguments'
|
||||
Assert (-not (Test-Path "$root/journal") -and -not (Test-Path "$root/result.json")) 'Rejected arguments must not create journals/results'
|
||||
}
|
||||
}
|
||||
# Unknown argument values are deliberately omitted from WELA's diagnostic.
|
||||
Invoke-Case @('configure','-Auto','-UnrecognizedOption','opaque-value') 1 'Unsupported trailing arguments'
|
||||
Invoke-Case @('configure','-Help','-WhatIf:$false') 1 'Unsupported trailing arguments'
|
||||
Invoke-Case @('-WhatIf','configure','-Auto') 1 'Unsupported trailing arguments'
|
||||
# Preserve documented named/positional binding, help, abbreviations and DryRun.
|
||||
Invoke-Case @('configure','-Help','-Auto','-DryRun') 0 'Read live state'
|
||||
Invoke-Case @('-Cmd','configure','-Help') 0 'Usage:'
|
||||
Invoke-Case @('configure','std','-Help') 0 'Usage:'
|
||||
Invoke-Case @('configure','-Hel') 0 'Usage:'
|
||||
Invoke-Case @('profiles') 0 'wela-2.2.0'
|
||||
Invoke-Case @('failed-logon-probe','-FailedLogonAction','Run','-WhatIf') 1 'only dedicated'
|
||||
if ($isWindowsHost) {
|
||||
Assert ((Read-NativeState) -ceq $before) 'Actual audit masks and native channel settings must remain unchanged'
|
||||
$evidence = [ordered]@{ Status='Passed'; Engine=$PSVersionTable.PSVersion.ToString(); OS=[Environment]::OSVersion.Version.ToString(); StateUnchanged=$true; Before=($before|ConvertFrom-Json); After=((Read-NativeState)|ConvertFrom-Json) }
|
||||
if ($env:RUNNER_TEMP) { $evidence | ConvertTo-Json -Depth 16 | Set-Content (Join-Path $env:RUNNER_TEMP 'wela-cli-arguments.json') -Encoding UTF8 }
|
||||
}
|
||||
Write-Host "PASS: $count public CLI argument assertions."
|
||||
} finally { Remove-Item -LiteralPath $root -Recurse -Force }
|
||||
$global:LASTEXITCODE = 0
|
||||
@@ -0,0 +1,99 @@
|
||||
param([switch]$AllowDisposableChannelWrite)
|
||||
$ErrorActionPreference='Stop'
|
||||
if([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not $AllowDisposableChannelWrite -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted'){throw 'Explicit disposable hosted Windows opt-in required.'}
|
||||
$repo=Split-Path $PSScriptRoot -Parent;$script:ScriptRoot=$repo
|
||||
Import-Module "$repo/modules/AuditProfiles.psm1" -Force
|
||||
Import-Module "$repo/modules/EventLogSettings.psm1" -Force
|
||||
Import-Module "$repo/modules/NativeProviders.psm1" -Force
|
||||
Import-Module "$repo/modules/NativeChannelAccess.psm1" -Force
|
||||
. "$repo/scripts/Configuration.ps1"
|
||||
. "$repo/scripts/NativeChannelConfiguration.ps1"
|
||||
$count=0
|
||||
function Assert($Value,$Message){if(-not $Value){throw $Message};$script:count++}
|
||||
function Binary($Value){$bytes=New-Object byte[] $Value.BinaryLength;$Value.GetBinaryForm($bytes,0);[Convert]::ToBase64String($bytes)}
|
||||
function Read-Raw([string]$Name){
|
||||
$r=Invoke-WelaNative wevtutil.exe @('gl',$Name,'/f:xml')
|
||||
$x=New-Object Xml.XmlDocument;$x.XmlResolver=$null;$x.LoadXml(($r.Output -join "`n"));return ,$x
|
||||
}
|
||||
function Guard-Raw($Xml){
|
||||
$x=$Xml.CloneNode($true);$x.DocumentElement.RemoveAttribute('enabled');$x.DocumentElement.RemoveAttribute('channelAccess')
|
||||
foreach($node in @($x.SelectNodes("/*/*[local-name()='logging']/*[local-name()='maxSize']"))){$null=$node.ParentNode.RemoveChild($node)}
|
||||
return $x.OuterXml
|
||||
}
|
||||
$engine=(Get-Process -Id $PID).Path
|
||||
$root=Join-Path $env:RUNNER_TEMP ('wela-channel-configure-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $root
|
||||
$profile=Get-WelaNativeChannelProfile
|
||||
$before=@{};$raw=@{};$policies=Get-WelaEffectiveAuditPolicy
|
||||
foreach($control in $profile.controls){$name=$control.channel;$before[$name]=Get-WelaNativeChannel $name;if(Test-WelaNativeChannelSnapshot $before[$name]){$raw[$name]=Read-Raw $name}}
|
||||
$rawEvidence=@{};foreach($name in $raw.Keys){$rawEvidence[$name]=$raw[$name].OuterXml};$rawEvidence|ConvertTo-Json -Depth 4|Set-Content "$root/raw-before.json" -Encoding UTF8
|
||||
$before|ConvertTo-Json -Depth 14|Set-Content "$root/before.json" -Encoding UTF8
|
||||
$missing=@($before.Values|Where-Object State -eq 'Not installed').Count
|
||||
$expected=if($missing){1}else{0}
|
||||
$capi='Microsoft-Windows-CAPI2/Operational';$app='Microsoft-Windows-AppLocker/EXE and DLL'
|
||||
$primary=$null
|
||||
function Run-Cli([string]$Name,[string[]]$Options){
|
||||
$prior=$ErrorActionPreference;try{$ErrorActionPreference='Continue';$lines=@(&$engine -NoLogo -NoProfile -NonInteractive -File "$repo/WELA.ps1" channel-settings @Options -ResultsPath "$root/$Name.json" 2>&1);$code=$LASTEXITCODE}finally{$ErrorActionPreference=$prior}
|
||||
$lines|Out-String|Set-Content "$root/$Name.txt" -Encoding UTF8
|
||||
Assert ($code -eq $expected) "Public $Name exit $code expected $expected : $($lines -join ' ')"
|
||||
$report=Get-Content "$root/$Name.json" -Raw|ConvertFrom-Json
|
||||
Assert ($report.ExitCode -eq $code -and $report.ForwardingReadiness -eq 'Not verified') 'Report agrees with native command exit and makes no forwarding claim'
|
||||
return $report
|
||||
}
|
||||
try{
|
||||
Assert ($raw.ContainsKey($capi) -and $raw.ContainsKey($app)) 'CAPI2 and AppLocker channels are required for this disposable fixture'
|
||||
Assert (@($before.Values|Where-Object { $_.State -notin @('Enabled','Disabled','Not installed') }).Count -eq 0) 'Unreadable original metadata refuses fixture writes'
|
||||
# Fixture-only remove this group read grant so the public opt-in must append it.
|
||||
$descriptor=[Security.AccessControl.RawSecurityDescriptor]::new($before[$capi].SecurityDescriptor)
|
||||
for($i=$descriptor.DiscretionaryAcl.Count-1;$i -ge 0;$i--){
|
||||
$ace=$descriptor.DiscretionaryAcl[$i]
|
||||
if($ace -is [Security.AccessControl.CommonAce] -and $ace.AceQualifier -eq 'AccessAllowed' -and $ace.SecurityIdentifier.Value -eq 'S-1-5-32-573' -and ($ace.AccessMask -band 1)){$descriptor.DiscretionaryAcl.RemoveAce($i)}
|
||||
}
|
||||
$withoutRead=$descriptor.GetSddlForm('All');$access=Get-WelaChannelAccessPlan $withoutRead
|
||||
Assert ($access.State -eq 'GrantRequired') 'Prepared actual descriptor supports one lossless read-only grant'
|
||||
$null=Invoke-WelaNative wevtutil.exe @('sl',$capi,'/e:false','/ms:1048576',('/ca:'+$withoutRead))
|
||||
# Existing sizes above the signed 32-bit range must not be narrowed.
|
||||
$null=Invoke-WelaNative wevtutil.exe @('sl',$app,'/ms:2147483648')
|
||||
$prepared=@{};foreach($name in $raw.Keys){$prepared[$name]=Get-WelaNativeChannel $name}
|
||||
$null=Run-Cli 'plan' @('-ChannelAction','Plan','-GrantEventLogReaders')
|
||||
$null=Run-Cli 'dry-run' @('-ChannelAction','Configure','-GrantEventLogReaders','-DryRun','-Auto','-BackupPath',"$root/unused")
|
||||
Assert (-not (Test-Path "$root/unused")) 'DryRun creates no journal'
|
||||
foreach($name in $raw.Keys){Assert (Test-WelaNativeChannelSnapshotEqual $prepared[$name] (Get-WelaNativeChannel $name)) 'Plan and DryRun preserve actual native channel settings'}
|
||||
$plain=Run-Cli 'configure' @('-ChannelAction','Configure','-Auto','-BackupPath',"$root/plain-journal")
|
||||
$plainCapi=Get-WelaNativeChannel $capi
|
||||
Assert ($plainCapi.IsEnabled -and $plainCapi.MaximumSizeInBytes -eq 102432768 -and (Test-WelaChannelDescriptorEqual $plainCapi.SecurityDescriptor $withoutRead)) 'Public Configure enables/resizes CAPI2 and preserves its ACL without explicit grant'
|
||||
Assert ((Get-WelaNativeChannel $app).MaximumSizeInBytes -eq 2147483648) 'A larger existing 2GiB buffer is preserved'
|
||||
$granted=Run-Cli 'grant' @('-ChannelAction','Configure','-GrantEventLogReaders','-Auto','-BackupPath',"$root/grant-journal")
|
||||
$actual=Get-WelaNativeChannel $capi
|
||||
Assert (Test-WelaChannelDescriptorEqual $actual.SecurityDescriptor $access.ProposedDescriptor) 'Native readback matches the precise planned grant descriptor'
|
||||
$afterAcl=[Security.AccessControl.RawSecurityDescriptor]::new($actual.SecurityDescriptor)
|
||||
Assert ($afterAcl.DiscretionaryAcl.Count -eq $descriptor.DiscretionaryAcl.Count+1) 'Exactly one native DACL ACE is added'
|
||||
$newAce=$afterAcl.DiscretionaryAcl[$access.AddedAceIndex]
|
||||
Assert ($newAce.SecurityIdentifier.Value -eq 'S-1-5-32-573' -and $newAce.AccessMask -eq 1 -and $newAce.AceFlags -eq 0 -and -not $newAce.IsCallback) 'Added grant is unconditional read only'
|
||||
$afterAcl.DiscretionaryAcl.RemoveAce($access.AddedAceIndex)
|
||||
Assert ((Binary $afterAcl) -ceq (Binary $descriptor)) 'Owner/group/SACL/flags and every original ACE byte/order survive native application'
|
||||
$again=Run-Cli 'idempotent' @('-ChannelAction','Configure','-GrantEventLogReaders','-Auto','-BackupPath',"$root/repeat-journal")
|
||||
Assert (@($again.Results|Where-Object Status -eq 'Applied').Count -eq 0) 'Repeated native configuration does not apply another mutation'
|
||||
Assert (-not (Test-Path "$root/repeat-journal/before.jsonl")) 'Idempotent invocation journals no write'
|
||||
$journal=@(Get-Content "$root/grant-journal/before.jsonl"|ForEach-Object {$_|ConvertFrom-Json})
|
||||
Assert ($journal.Count -eq 1 -and $journal[0].Target.Channel -eq $capi -and (Test-WelaChannelDescriptorEqual $journal[0].Before.SecurityDescriptor $withoutRead)) 'Durable actual pre-grant journal preserves the original descriptor'
|
||||
$rawAfter=@{};foreach($name in $raw.Keys){$rawAfter[$name]=(Read-Raw $name).OuterXml};$rawAfter|ConvertTo-Json -Depth 4|Set-Content "$root/raw-configured.json" -Encoding UTF8
|
||||
foreach($name in $raw.Keys){Assert ((Guard-Raw (Read-Raw $name)) -ceq (Guard-Raw $raw[$name])) 'Native channel path/retention/provider metadata remain unchanged'}
|
||||
Write-Host "PASS: $count native public channel configuration assertions."
|
||||
}catch{$primary=$_}
|
||||
finally{
|
||||
$errors=@()
|
||||
foreach($name in $raw.Keys){
|
||||
try{$s=$before[$name];$null=Invoke-WelaNative wevtutil.exe @('sl',$name,('/e:'+$s.IsEnabled.ToString().ToLowerInvariant()),('/ms:'+$s.MaximumSizeInBytes),('/ca:'+$s.SecurityDescriptor))
|
||||
if(-not (Test-WelaNativeChannelSnapshotEqual $s (Get-WelaNativeChannel $name)) -or (Read-Raw $name).OuterXml -cne $raw[$name].OuterXml){throw 'Original native channel configuration differs after cleanup'}
|
||||
}catch{$errors+="$name : $($_.Exception.Message)"}
|
||||
}
|
||||
$now=Get-WelaEffectiveAuditPolicy;foreach($guid in $policies.Keys){if($policies[$guid] -ne $now[$guid]){$errors+='Audit mask changed: '+$guid}}
|
||||
$after=@{};foreach($name in $before.Keys){$after[$name]=Get-WelaNativeChannel $name}
|
||||
[ordered]@{CleanupVerified=($errors.Count -eq 0);Before=$before;After=$after;AuditMasksCompared=$policies.Count;Diagnostic=$errors;Assertions=$count;PrimaryError=[string]$primary}|ConvertTo-Json -Depth 14|Set-Content "$root/cleanup.json" -Encoding UTF8
|
||||
if($errors.Count){throw "Cleanup failed: $($errors -join '; '); primary: $primary"}
|
||||
Write-Host 'Exact original channel metadata and all audit masks verified after cleanup; existing event records/retention duration not claimed.'
|
||||
}
|
||||
$artifacts=@(Get-ChildItem -LiteralPath $root -File -Recurse|ForEach-Object {[ordered]@{Path=$_.FullName.Substring($root.Length+1);Sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash}})
|
||||
$sources=@('WELA.ps1','scripts/Configuration.ps1','scripts/NativeChannelConfiguration.ps1','modules/NativeChannelAccess.psm1','modules/NativeProviders.psm1','modules/EventLogSettings.psm1','tests/NativeChannelConfigure.Windows.Tests.ps1')|ForEach-Object {[ordered]@{Path=$_;Sha256=(Get-FileHash -LiteralPath (Join-Path $repo $_) -Algorithm SHA256).Hash}}
|
||||
[ordered]@{Status=$(if($primary){'Failed'}else{'Passed'});Commit=$env:GITHUB_SHA;Engine=$PSVersionTable.PSVersion.ToString();Assertions=$count;Artifacts=$artifacts;Sources=@($sources);EventGenerationVerified=$false;ForwardingVerified=$false;ReadyRuleCredit=0}|ConvertTo-Json -Depth 8|Set-Content "$root/manifest.json" -Encoding UTF8
|
||||
if($primary){throw $primary};$global:LASTEXITCODE=0
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
- 既存の Script AuditOnly ポリシーに対し、固定の Windows PowerShell5.1 スクリプトを実行する `applocker-script-probe` を追加しました。実行者と子プロセスのログオン、保持したファイル、高精度 UTC とイベント記録境界を確認し、Script8005 の許可と8006 の監査専用ブロック判定を区別します。拒否・上限・重複・状態変化は未検証とし、設定変更や Sigma の評価加算は行いません。使い捨て Windows の4構成で両イベントとポリシー・チャネル・タスクの復元を検証し、保護された AppIDSvc を停止できない場合は明記します。 (関連 #381) (@Shirofune-Security)
|
||||
|
||||
- 従来の設定コマンドでも、未対応の `-WhatIf` や入力ミスなどの未認識引数を実行前に拒否するようにしました。`-ErrorAction` や `-Verbose` などの PowerShell 共通パラメーターも拒否するため、自動化ラッパーへの影響をヘルプと診断に明記しました。正しい位置指定引数と文書化された `-DryRun` の動作は維持し、Windows PowerShell 5.1 と PowerShell 7 で公開CLIを検証します。 (@Shirofune-Security)
|
||||
|
||||
- Windows PowerShell 5.1 と PowerShell 7、使い捨ての Server 2022/2025 で標準チャネル設定の公開CLIを検証するテストを追加しました。有効化・サイズ・CAPI2読み取り専用権限の適用、既存記述子と大きいバッファーの保持、変更前記録、DryRun、再実行時の無変更、元設定への復元を確認し、ハッシュ付きの証拠を保存します。転送・保存期間・Sigmaの検証は別途必要です。 (@Shirofune-Security)
|
||||
|
||||
- 完了済みのファイアウォールテキストログ設定を1プロファイルずつ復元する、明示的な `firewall-recovery` を追加しました。元の記録・結果、確認済み計画ハッシュ、実行者・ソース、永続記録と変更前後の確認により、PersistentStore の4項目だけを復元し、強制設定・他のプロファイル・ルールとフィルターを保持します。実効ポリシーを別に報告し、途中失敗を未検証として扱い、自動ロールバックや Sigma 加点は行いません。使い捨て環境の公開 CLI で設定、変更検出、復元、冪等性、完全な後始末を検証します。(関連 #375) (@Shirofune-Security)
|
||||
|
||||
- 固定のオフライン一時証明書チェーン構築とローカル CAPI2 イベント11を確認する `capi2-probe` Plan/Run を追加。公開証明書・nonce・PID・トークン・高精度UTCによる照合、ワーカーと収集の時間制限、証拠保存に対応。既存のチャネル、証明書ストア、信頼設定を維持し、TLS、失効確認、リモート転送、Sigma の検証実績は付与しません。
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
- Added opt-in `applocker-script-probe` for a fixed native Windows PowerShell5.1 script under an existing Script AuditOnly policy. Actual caller/child logon context, held file bytes, precise UTC and native record boundaries distinguish exact Script8005 allowed and8006 would-block events; denied, capped, ambiguous or drifted runs remain unverified. The disposable four-way Windows suite requires both native decisions and policy/channel/task cleanup, with the protected-AppIDSvc stopping boundary recorded. No configuration changes or Sigma credit. (Related #381) (@Shirofune-Security)
|
||||
|
||||
- Reject unbound command-line arguments before dispatch, including unsupported `-WhatIf` and misspelled options on legacy configuration commands. PowerShell common parameters such as `-ErrorAction` and `-Verbose` are also rejected; help and diagnostics explain the automation-wrapper compatibility change. Valid positional arguments and documented `-DryRun` behavior are preserved. Public CLI regressions cover both Windows PowerShell 5.1 and PowerShell 7. (@Shirofune-Security)
|
||||
|
||||
- Added disposable Server 2022/2025 validation of public native channel configuration under Windows PowerShell 5.1 and PowerShell 7. Tests apply enable/size controls and the explicit CAPI2 read-only grant, verify descriptor preservation, journals, larger buffers, DryRun and idempotence, and retain hashed native evidence with exact fixture cleanup. Forwarding, retention-duration and Sigma validation remain separate. (@Shirofune-Security)
|
||||
|
||||
- Added opt-in `firewall-recovery` for one completed firewall text-log operation. Strict original journal/result matching, reviewed plan hashes, native operator/source guards, durable receipts and exact four-field PersistentStore restoration preserve enforcement, other profiles and bounded rule/filter configuration. Effective policy stays separately reported; partial writes remain unverified without automatic rollback or Sigma credit. Disposable public-CLI tests cover configuration, drift refusal, recovery, idempotence and exact cleanup. (Related #375) (@Shirofune-Security)
|
||||
|
||||
- Added explicit `capi2-probe` Plan/Run for a fixed offline ephemeral certificate-chain build and exact local CAPI2 event 11 evidence, with public certificate/nonce/PID/token/precise-UTC binding, bounded worker/collection and retained artifacts. Existing channel, certificate-store and trust configuration are preserved; no TLS, revocation, remote delivery or Sigma credit is claimed.
|
||||
|
||||
Reference in new issue
Block a user