Add CMake and CI Pipeline for ModSecurityIIS in ModSecurity V2 #3
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 | |
| - 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 | |
| - 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 | |
| - 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 | |
| } | |
| $modSecurityConfigFile = "$modSecurityConfigDir\modsecurity_iis.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 | |
| } | |
| 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"; ExpectedCode = 403}, | |
| @{Url = "http://localhost/?q=<script>alert('test')</script>"; Description = "XSS attempt"; ExpectedCode = 403} | |
| ) | |
| 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)" | |
| } | |
| } | |
| # Check event log | |
| $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: Install go-ftw | |
| shell: pwsh | |
| run: | | |
| go install github.com/coreruleset/go-ftw@latest | |
| # Certain rules are disabled due to specific IIS behavior patterns. | |
| # Using go-ftw in cloud mode as the IIS connector does not generate logs in file format. | |
| # Technically, Event logs can be streamed to files, but this requires implementing rate limits to avoid log overflow. | |
| - 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 --cloud -e "920100-2$|920100-4$|920100-8$|920100-12$|920272-5$|920290-1$|920620-1$|920380-1$" --show-failures-only | |