name: CI/CD for IIS Module on: push: branches: - v2/test-ci-windows pull_request: branches: - v2/test-ci-windows jobs: build: strategy: matrix: arch: [x86, x64] 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 # Original Make file contain comment build script for ssdeep, # which is rely on MSYS2, so we need to install MSYS2. # If it's enabled, it need msys2 library for ssdeep. # - name: Setup MSYS2 # uses: msys2/setup-msys2@v2 # 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 }}') # echo "Converted workspace path: $MSYS2_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-${{ matrix.arch }}/bin" # mkdir -p "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/include" # cp -v fuzzy.dll "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/bin/" # cp -v fuzzy.h "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/include/" # cp -v fuzzy.def "${MSYS2_WORKSPACE}/ssdeep-install-${{ matrix.arch }}/" - name: Restore vcpkg cache id: vcpkg-cache uses: TAServers/vcpkg-cache@v3 with: token: ${{ secrets.GITHUB_TOKEN }} prefix: vcpkg-iis-module-${{ matrix.arch }}/ - uses: ammaraskar/msvc-problem-matcher@master - 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" ` -A $cmakeArch ` -DWITH_LUA=ON ` -DWITH_YAJL=ON ` -S IIS -B "iis\build" # -DSSDEEP_ROOT="${{ github.workspace }}\ssdeep-install-${{ matrix.arch }}" ` # -DWITH_SSDEEP=ON ` - 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="; 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 - 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