fix: record real response status for IIS audit log F part #199
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD for IIS Module | |
| on: | |
| push: | |
| pull_request: | |
| jobs: | |
| build: | |
| strategy: | |
| matrix: | |
| arch: [x64, x86] | |
| config: [Release, RelWithDebInfo] | |
| runs-on: windows-latest | |
| # For Caching | |
| permissions: | |
| actions: read | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| - name: Install Apache for x86 | |
| if: matrix.arch == 'x86' | |
| shell: pwsh | |
| run: | | |
| $apachePath = "${{ github.workspace }}\apache-x86" | |
| New-Item -ItemType Directory -Path $apachePath -Force | |
| choco install apache-httpd -y --force --forcex86 --no-progress -r --params="'/installLocation:$apachePath /noService'" | |
| echo "APACHE_ROOT=$apachePath\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| - name: Set Apache path for x64 | |
| if: matrix.arch == 'x64' | |
| shell: pwsh | |
| run: | | |
| echo "APACHE_ROOT=C:\tools\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| - name: Setup MSYS2 | |
| uses: msys2/setup-msys2@fb197b72ce45fb24f17bf3f807a388985654d1f2 | |
| with: | |
| msystem: ${{ matrix.arch == 'x86' && 'MINGW32' || 'UCRT64' }} | |
| update: true | |
| install: > | |
| git | |
| make | |
| autoconf | |
| automake | |
| libtool | |
| ${{ matrix.arch == 'x86' && 'mingw-w64-i686-gcc' || 'mingw-w64-ucrt-x86_64-gcc' }} | |
| ${{ matrix.arch == 'x86' && 'mingw-w64-i686-pkg-config' || 'mingw-w64-ucrt-x86_64-pkg-config' }} | |
| - name: Clone and build ssdeep | |
| shell: msys2 {0} | |
| run: | | |
| MSYS2_WORKSPACE=$(cygpath -u '${{ github.workspace }}') | |
| git clone https://github.com/ssdeep-project/ssdeep.git --depth 1 | |
| cd ssdeep | |
| autoreconf -i | |
| if [ "${{ matrix.arch }}" = "x86" ]; then | |
| ./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3" --build=i686-pc-mingw32 | |
| else | |
| ./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3" | |
| fi | |
| make dll | |
| mkdir -p "${MSYS2_WORKSPACE}/ssdeep-install/" | |
| cp -v fuzzy.dll "${MSYS2_WORKSPACE}/ssdeep-install/" | |
| cp -v fuzzy.h "${MSYS2_WORKSPACE}/ssdeep-install/" | |
| cp -v fuzzy.def "${MSYS2_WORKSPACE}/ssdeep-install/" | |
| - name: Restore vcpkg cache | |
| id: vcpkg-cache | |
| uses: TAServers/vcpkg-cache@e848939f754daf406a06006be2e05eb5b17cc481 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| prefix: vcpkg-iis-module-${{ matrix.arch }}/ | |
| - uses: ammaraskar/msvc-problem-matcher@1ebcb382869bfdc2cc645e8a2a43b6d319ea1cc0 | |
| - name: Configure CMake for IIS Module | |
| env: | |
| VCPKG_FEATURE_FLAGS: "binarycaching" | |
| VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite" | |
| VCPKG_DEFAULT_TRIPLET: ${{ matrix.arch }}-windows | |
| run: | | |
| $archFlag = "${{ matrix.arch }}" | |
| $cmakeArch = if ($archFlag -eq "x86") { "Win32" } else { "x64" } | |
| $installDir = if ($archFlag -eq "x86") { "x86" } else { "amd64" } | |
| cmake ` | |
| -DAPACHE_ROOT="$env:APACHE_ROOT" ` | |
| -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}\iis\release\$installDir" ` | |
| -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" ` | |
| -DSSDEEP_ROOT="${{ github.workspace }}\ssdeep-install" ` | |
| -DWITH_SSDEEP=ON ` | |
| -A $cmakeArch ` | |
| -DWITH_LUA=ON ` | |
| -DWITH_YAJL=ON ` | |
| -S IIS -B "iis\build" | |
| - name: Build IIS Module | |
| shell: pwsh | |
| run: | | |
| cmake --build "iis\build" --config ${{ matrix.config }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: iis-module-${{ matrix.arch }}-${{ matrix.config }} | |
| path: iis/build/${{ matrix.config }}/ | |
| package: | |
| needs: build | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| config: [Release, RelWithDebInfo] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| - name: Download x64 artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: iis-module-x64-${{ matrix.config }} | |
| path: iis/release/amd64/ | |
| - name: Download x86 artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: iis-module-x86-${{ matrix.config }} | |
| path: iis/release/x86/ | |
| - name: Generate MSI files | |
| shell: pwsh | |
| run: | | |
| heat dir "iis\release\amd64" -cg ModSec64Components -dr inetsrv64 -gg -sreg -srd -var var.ModSecurityIISRelease64 -out "iis\ModSec64.wxs" | |
| heat dir "iis\release\x86" -cg ModSec32Components -dr inetsrv32 -gg -sreg -srd -var var.ModSecurityIISRelease32 -out "iis\ModSec32.wxs" | |
| candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wxs" "iis\ModSec64.wxs" -arch x64 -dModSecurityIISRelease64="iis\release\amd64\" -out iis\ | |
| candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\ModSec32.wxs" -arch x86 -dModSecurityIISRelease32="iis\release\x86\" -out iis\ | |
| light.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wixobj" "iis\ModSec32.wixobj" "iis\ModSec64.wixobj" -out "iis\modsecurityiis.msi" | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: modsecurityiis-installers-${{ matrix.config }} | |
| path: iis/modsecurityiis.msi | |
| test: | |
| needs: package | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| config: [Release, RelWithDebInfo] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| - name: Download MSI files | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: modsecurityiis-installers-${{ matrix.config }} | |
| path: ${{ github.workspace }}/ | |
| - name: Install MSI | |
| shell: pwsh | |
| run: | | |
| $msiPath = "${{ github.workspace }}\modsecurityiis.msi" | |
| if (-not (Test-Path $msiPath)) { | |
| Write-Error "MSI file not found at $msiPath" | |
| exit 1 | |
| } | |
| # Install with logging for debugging | |
| $installLog = "${{ github.workspace }}\install.log" | |
| $installResult = Start-Process -FilePath "msiexec.exe" -ArgumentList @( | |
| "/i", "`"$msiPath`"", | |
| "/qn", | |
| "/norestart", | |
| "/l*", "`"$installLog`"" | |
| ) -Wait -PassThru | |
| if ($installResult.ExitCode -ne 0) { | |
| Write-Error "MSI installation failed with exit code $($installResult.ExitCode)" | |
| Get-Content $installLog | Write-Host | |
| exit 1 | |
| } | |
| $installDir = "C:\Program Files\ModSecurity IIS" | |
| $requiredFiles = @( | |
| "modsecurity.conf", | |
| "modsecurity_iis.conf" | |
| ) | |
| foreach ($file in $requiredFiles) { | |
| $filePath = Join-Path $installDir $file | |
| if (-not (Test-Path $filePath)) { | |
| Write-Error "Required file $file not found in installation directory" | |
| exit 1 | |
| } | |
| } | |
| - name: Install OWASP Core Rules | |
| shell: pwsh | |
| run: | | |
| $crsVersion = "v4.18.0" | |
| $crsUrl = "https://github.com/coreruleset/coreruleset/archive/refs/tags/$crsVersion.tar.gz" | |
| $crsDir = "C:\Program Files\ModSecurity IIS\coreruleset" | |
| $modSecurityConfigDir = "C:\Program Files\ModSecurity IIS" | |
| try { | |
| New-Item -ItemType Directory -Path $crsDir -Force | |
| Invoke-WebRequest -Uri $crsUrl -OutFile "$crsDir\$crsVersion.tar.gz" | |
| tar -xzf "$crsDir\$crsVersion.tar.gz" -C $crsDir --strip-components=1 | |
| Get-ChildItem "$crsDir" -Recurse -Filter "*.example" | ForEach-Object { | |
| $newName = $_.Name.Replace(".example", "") | |
| Rename-Item -Path $_.FullName -NewName $newName | |
| } | |
| $testConfigDir = "${{ github.workspace }}\iis\tests" | |
| $modSecurityConfigFile = "$modSecurityConfigDir\modsecurity_iis.conf" | |
| Add-Content -Path "$crsDir\crs-setup.conf" -Value (Get-Content -Path "$testConfigDir\crs-900005.conf") | |
| $crsRules = @( | |
| "Include coreruleset/crs-setup.conf", | |
| "Include coreruleset/plugins/*-config.conf", | |
| "Include coreruleset/plugins/*-before.conf", | |
| "Include coreruleset/rules/*.conf", | |
| "Include coreruleset/plugins/*-after.conf" | |
| ) | |
| Add-Content -Path $modSecurityConfigFile -Value $crsRules | |
| (Get-Content -Path $modSecurityConfigDir\modsecurity.conf) -replace 'SecRuleEngine DetectionOnly', 'SecRuleEngine On' | Set-Content -Path $modSecurityConfigDir\modsecurity.conf | |
| $auditLogDir = "c:\inetpub\logs" | |
| New-Item -ItemType Directory -Path $auditLogDir -Force | Out-Null | |
| $acl = Get-Acl -LiteralPath $auditLogDir | |
| foreach ($identity in @("NETWORK SERVICE", "IIS_IUSRS")) { | |
| $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, "Modify", "ContainerInherit,ObjectInherit", "None", "Allow") | |
| $acl.AddAccessRule($rule) | |
| } | |
| Set-Acl -LiteralPath $auditLogDir -AclObject $acl | |
| Add-Content -Path $modSecurityConfigFile -Value (Get-Content -Path "$testConfigDir\audit-logging.conf") | |
| } | |
| catch { | |
| Write-Error "Failed to install OWASP Core Rules: $($_.Exception.Message)" | |
| exit 1 | |
| } | |
| - name: Test IIS Module | |
| shell: pwsh | |
| run: | | |
| $iisConfigDir = "C:\Program Files\ModSecurity IIS\" | |
| Restart-Service W3SVC -Force | |
| $modules = & "$env:SystemRoot\system32\inetsrv\appcmd.exe" list modules | |
| Write-Host "IIS modules: $modules" | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "appcmd failed with exit code $LASTEXITCODE" | |
| exit 1 | |
| } | |
| if (-not ($modules -match "ModSecurity")) { | |
| Write-Error "ModSecurity module not found in IIS modules" | |
| Write-Host "IIS modules: $modules" | |
| exit 1 | |
| } | |
| $testCases = @( | |
| @{Url = "http://localhost/"; Description = "Normal request"; ExpectedCode = 200}, | |
| @{Url = "http://localhost/?id=1' OR '1'='1"; Description = "SQL injection attempt (logged, not blocked in DetectionOnly)"; ExpectedCode = 200}, | |
| @{Url = "http://localhost/?q=<script>alert('test')</script>"; Description = "XSS attempt (logged, not blocked in DetectionOnly)"; ExpectedCode = 200} | |
| ) | |
| foreach ($test in $testCases) { | |
| try { | |
| $response = Invoke-WebRequest $test.Url -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 30 | |
| if ($response.StatusCode -eq $test.ExpectedCode) { | |
| Write-Host "PASS: $($test.Description) - returned $($response.StatusCode)" | |
| } | |
| else { | |
| Write-Host "FAIL: $($test.Description) - expected $($test.ExpectedCode) but got $($response.StatusCode)" | |
| } | |
| } | |
| catch { | |
| Write-Host "ERROR: $($test.Description) - request failed: $($_.Exception.Message)" | |
| } | |
| } | |
| $badMessagePattern = 'Failed to find the RegisterModule entrypoint|The description for Event ID|The data is the error|dll failed to load' | |
| $events = Get-EventLog -LogName Application -Newest 100 | | |
| Where-Object { $_.Message -match $badMessagePattern } | | |
| Where-Object { $_.Source -match 'IIS|W3SVC|mscor|IIS-W3SVC|IIS-W3WP|ModSecurity' } | |
| if ($events -and $events.Count -gt 0) { | |
| Write-Host '::error:: Found errors in event log' | |
| $events | Select-Object TimeGenerated, Source, EntryType, EventID, Message | Format-List | |
| Exit 1 | |
| } | |
| Get-EventLog -LogName Application -Source ModSecurity | Format-List | |
| - name: Verify audit log F part records real response status | |
| shell: pwsh | |
| run: | | |
| $auditLogPath = "c:\inetpub\logs\modsec_audit.log" | |
| if (-not (Test-Path -LiteralPath $auditLogPath)) { | |
| Write-Error "Audit log not found: $auditLogPath" | |
| exit 1 | |
| } | |
| $marker = "hello-marker-$(Get-Random)" | |
| $resp = Invoke-WebRequest "http://localhost/?probe=1" -Headers @{ "X-CRS-Test" = $marker } -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 30 | |
| if ($resp.StatusCode -ne 200) { Write-Error "Probe returned $($resp.StatusCode)"; exit 1 } | |
| Start-Sleep -Seconds 2 | |
| $lines = Get-Content -LiteralPath $auditLogPath | |
| $markerLine = $lines | Select-String -Pattern "X-CRS-Test $marker" | Select-Object -First 1 | |
| if (-not $markerLine) { | |
| Write-Error "Marker '$marker' not found in audit log" | |
| exit 1 | |
| } | |
| $fPart = $lines[($markerLine.LineNumber)..($lines.Count - 1)] | Select-String -Pattern "^HTTP/1\.1 \d{3}" | Select-Object -First 1 | |
| if (-not $fPart -or $fPart.Line -notmatch "^HTTP/1\.1 200") { | |
| Write-Error "Audit log F part is not 200 (bogus status): $($fPart.Line)" | |
| exit 1 | |
| } | |
| Write-Host "OK: audit log F part = $($fPart.Line)" | |
| $bogus = $lines | Select-String -Pattern "HTTP/1\.1 500 Internal Server Error" | |
| if ($bogus) { | |
| Write-Error "Found bogus 500 in audit log F parts: $($bogus.Count)" | |
| exit 1 | |
| } | |
| Write-Host "OK: no bogus 500 entries in audit log" | |
| - name: Install go-ftw and albedo | |
| shell: pwsh | |
| run: | | |
| go install github.com/coreruleset/go-ftw@latest | |
| go install github.com/coreruleset/albedo@latest | |
| - name: Configure IIS reverse proxy to albedo backend | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $goBinPath = "" | |
| if ($env:GOBIN) { | |
| $goBinPath = $env:GOBIN | |
| } elseif ($env:GOPATH) { | |
| $goBinPath = Join-Path $env:GOPATH "bin" | |
| } else { | |
| $goBinPath = Join-Path $env:USERPROFILE "go\bin" | |
| } | |
| # Install URL Rewrite 2.0 and Application Request Routing (ARR) | |
| choco install urlrewrite iis-arr -y --no-progress | |
| & "$env:SystemRoot\system32\inetsrv\appcmd.exe" set config /section:system.webServer/proxy /enabled:true | |
| if ($LASTEXITCODE -ne 0) { Write-Error "Failed to enable ARR proxy"; exit 1 } | |
| Copy-Item -LiteralPath "${{ github.workspace }}\iis\tests\web.config" -Destination "C:\inetpub\wwwroot\web.config" -Force | |
| $albedoExe = Join-Path $goBinPath "albedo.exe" | |
| if (-not (Test-Path -LiteralPath $albedoExe)) { | |
| Write-Error "albedo.exe not found at $albedoExe" | |
| exit 1 | |
| } | |
| Start-Process -FilePath $albedoExe -ArgumentList "-p", "8080" -WindowStyle Hidden | |
| $ready = $false | |
| for ($i = 0; $i -lt 30; $i++) { | |
| try { | |
| $resp = Invoke-WebRequest "http://localhost:8080/capabilities?quiet=true" -UseBasicParsing -TimeoutSec 3 | |
| if ($resp.StatusCode -eq 200) { $ready = $true; break } | |
| } catch { | |
| Start-Sleep -Seconds 1 | |
| } | |
| } | |
| if (-not $ready) { Write-Error "albedo did not become ready on port 8080"; exit 1 } | |
| Write-Host "albedo backend is ready on http://localhost:8080" | |
| Restart-Service W3SVC -Force | |
| try { | |
| $probe = Invoke-WebRequest "http://localhost/status/200" -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 15 | |
| Write-Host "Reverse proxy probe /status/200 -> $($probe.StatusCode)" | |
| } catch { | |
| Write-Host "Reverse proxy probe error: $($_.Exception.Message)" | |
| } | |
| - name: Test ModSecurity Rules | |
| shell: pwsh | |
| run: | | |
| $testRuleDir = "C:\Program Files\ModSecurity IIS\coreruleset\tests\regression\tests" | |
| $goBinPath = "" | |
| if ($env:GOBIN) { | |
| $goBinPath = $env:GOBIN | |
| } elseif ($env:GOPATH) { | |
| $goBinPath = Join-Path $env:GOPATH "bin" | |
| } else { | |
| $goBinPath = Join-Path $env:USERPROFILE "go\bin" | |
| } | |
| & "$goBinPath\go-ftw.exe" run -d $testRuleDir --config "${{ github.workspace }}\iis\tests\ftw.yaml" --show-failures-only | |
| $exitCode = $LASTEXITCODE | |
| Write-Host "go-ftw exit code: $exitCode" | |
| $auditLogPath = "c:\inetpub\logs\modsec_audit.log" | |
| if (Test-Path -LiteralPath $auditLogPath) { | |
| Copy-Item -LiteralPath $auditLogPath -Destination "${{ github.workspace }}\modsec_audit.log" -Force | |
| Write-Host "Audit log copied for upload: $((Get-Item -LiteralPath $auditLogPath).Length) bytes" | |
| Write-Host "--- TAIL OF AUDIT LOG AFTER go-ftw ---" | |
| Get-Content -LiteralPath $auditLogPath -Tail 40 | |
| } | |
| exit $exitCode | |
| - name: Upload FTW logs and audit log | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ftw-logs-${{ matrix.config }} | |
| path: | | |
| ${{ github.workspace }}/modsec_audit.log | |