Skip to content

Commit 6939a21

Browse files
authored
Merge pull request #3452 from A13501350/v2/test-ci-windows
Add CMake and CI Pipeline for ModSecurityIIS in ModSecurity V2
2 parents d84d200 + 326da02 commit 6939a21

File tree

6 files changed

+786
-421
lines changed

6 files changed

+786
-421
lines changed
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
name: CI/CD for IIS Module
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
build:
9+
strategy:
10+
matrix:
11+
arch: [x64, x86]
12+
config: [Release, RelWithDebInfo]
13+
runs-on: windows-latest
14+
15+
# For Caching
16+
permissions:
17+
actions: read
18+
contents: read
19+
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v5
23+
24+
- name: Install Apache for x86
25+
if: matrix.arch == 'x86'
26+
shell: pwsh
27+
run: |
28+
$apachePath = "${{ github.workspace }}\apache-x86"
29+
New-Item -ItemType Directory -Path $apachePath -Force
30+
choco install apache-httpd -y --force --forcex86 --no-progress -r --params="'/installLocation:$apachePath /noService'"
31+
echo "APACHE_ROOT=$apachePath\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
32+
33+
- name: Set Apache path for x64
34+
if: matrix.arch == 'x64'
35+
shell: pwsh
36+
run: |
37+
echo "APACHE_ROOT=C:\tools\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
38+
39+
- name: Setup MSYS2
40+
uses: msys2/setup-msys2@fb197b72ce45fb24f17bf3f807a388985654d1f2
41+
with:
42+
msystem: ${{ matrix.arch == 'x86' && 'MINGW32' || 'UCRT64' }}
43+
update: true
44+
install: >
45+
git
46+
make
47+
autoconf
48+
automake
49+
libtool
50+
${{ matrix.arch == 'x86' && 'mingw-w64-i686-gcc' || 'mingw-w64-ucrt-x86_64-gcc' }}
51+
${{ matrix.arch == 'x86' && 'mingw-w64-i686-pkg-config' || 'mingw-w64-ucrt-x86_64-pkg-config' }}
52+
53+
- name: Clone and build ssdeep
54+
shell: msys2 {0}
55+
run: |
56+
MSYS2_WORKSPACE=$(cygpath -u '${{ github.workspace }}')
57+
58+
git clone https://github.com/ssdeep-project/ssdeep.git --depth 1
59+
cd ssdeep
60+
autoreconf -i
61+
62+
if [ "${{ matrix.arch }}" = "x86" ]; then
63+
./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3" --build=i686-pc-mingw32
64+
else
65+
./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3"
66+
fi
67+
68+
make dll
69+
70+
mkdir -p "${MSYS2_WORKSPACE}/ssdeep-install/"
71+
cp -v fuzzy.dll "${MSYS2_WORKSPACE}/ssdeep-install/"
72+
cp -v fuzzy.h "${MSYS2_WORKSPACE}/ssdeep-install/"
73+
cp -v fuzzy.def "${MSYS2_WORKSPACE}/ssdeep-install/"
74+
75+
- name: Restore vcpkg cache
76+
id: vcpkg-cache
77+
uses: TAServers/vcpkg-cache@e848939f754daf406a06006be2e05eb5b17cc481
78+
with:
79+
token: ${{ secrets.GITHUB_TOKEN }}
80+
prefix: vcpkg-iis-module-${{ matrix.arch }}/
81+
82+
- uses: ammaraskar/msvc-problem-matcher@1ebcb382869bfdc2cc645e8a2a43b6d319ea1cc0
83+
84+
- name: Configure CMake for IIS Module
85+
env:
86+
VCPKG_FEATURE_FLAGS: "binarycaching"
87+
VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite"
88+
VCPKG_DEFAULT_TRIPLET: ${{ matrix.arch }}-windows
89+
run: |
90+
$archFlag = "${{ matrix.arch }}"
91+
$cmakeArch = if ($archFlag -eq "x86") { "Win32" } else { "x64" }
92+
$installDir = if ($archFlag -eq "x86") { "x86" } else { "amd64" }
93+
94+
cmake `
95+
-DAPACHE_ROOT="$env:APACHE_ROOT" `
96+
-DCMAKE_INSTALL_PREFIX="${{ github.workspace }}\iis\release\$installDir" `
97+
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" `
98+
-DSSDEEP_ROOT="${{ github.workspace }}\ssdeep-install" `
99+
-DWITH_SSDEEP=ON `
100+
-A $cmakeArch `
101+
-DWITH_LUA=ON `
102+
-DWITH_YAJL=ON `
103+
-S IIS -B "iis\build"
104+
105+
- name: Build IIS Module
106+
shell: pwsh
107+
run: |
108+
cmake --build "iis\build" --config ${{ matrix.config }}
109+
110+
- name: Upload artifacts
111+
uses: actions/upload-artifact@v4
112+
with:
113+
name: iis-module-${{ matrix.arch }}-${{ matrix.config }}
114+
path: iis/build/${{ matrix.config }}/
115+
116+
package:
117+
needs: build
118+
runs-on: windows-latest
119+
strategy:
120+
matrix:
121+
config: [Release, RelWithDebInfo]
122+
steps:
123+
- name: Checkout code
124+
uses: actions/checkout@v5
125+
126+
- name: Download x64 artifacts
127+
uses: actions/download-artifact@v4
128+
with:
129+
name: iis-module-x64-${{ matrix.config }}
130+
path: iis/release/amd64/
131+
132+
- name: Download x86 artifacts
133+
uses: actions/download-artifact@v4
134+
with:
135+
name: iis-module-x86-${{ matrix.config }}
136+
path: iis/release/x86/
137+
138+
- name: Generate MSI files
139+
shell: pwsh
140+
run: |
141+
heat dir "iis\release\amd64" -cg ModSec64Components -dr inetsrv64 -gg -sreg -srd -var var.ModSecurityIISRelease64 -out "iis\ModSec64.wxs"
142+
heat dir "iis\release\x86" -cg ModSec32Components -dr inetsrv32 -gg -sreg -srd -var var.ModSecurityIISRelease32 -out "iis\ModSec32.wxs"
143+
candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wxs" "iis\ModSec64.wxs" -arch x64 -dModSecurityIISRelease64="iis\release\amd64\" -out iis\
144+
candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\ModSec32.wxs" -arch x86 -dModSecurityIISRelease32="iis\release\x86\" -out iis\
145+
light.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wixobj" "iis\ModSec32.wixobj" "iis\ModSec64.wixobj" -out "iis\modsecurityiis.msi"
146+
147+
- name: Upload artifacts
148+
uses: actions/upload-artifact@v4
149+
with:
150+
name: modsecurityiis-installers-${{ matrix.config }}
151+
path: iis/modsecurityiis.msi
152+
153+
test:
154+
needs: package
155+
runs-on: windows-latest
156+
strategy:
157+
matrix:
158+
config: [Release, RelWithDebInfo]
159+
steps:
160+
- name: Checkout code
161+
uses: actions/checkout@v5
162+
163+
- name: Download MSI files
164+
uses: actions/download-artifact@v4
165+
with:
166+
name: modsecurityiis-installers-${{ matrix.config }}
167+
path: ${{ github.workspace }}/
168+
169+
- name: Install MSI
170+
shell: pwsh
171+
run: |
172+
$msiPath = "${{ github.workspace }}\modsecurityiis.msi"
173+
if (-not (Test-Path $msiPath)) {
174+
Write-Error "MSI file not found at $msiPath"
175+
exit 1
176+
}
177+
178+
# Install with logging for debugging
179+
$installLog = "${{ github.workspace }}\install.log"
180+
$installResult = Start-Process -FilePath "msiexec.exe" -ArgumentList @(
181+
"/i", "`"$msiPath`"",
182+
"/qn",
183+
"/norestart",
184+
"/l*", "`"$installLog`""
185+
) -Wait -PassThru
186+
187+
if ($installResult.ExitCode -ne 0) {
188+
Write-Error "MSI installation failed with exit code $($installResult.ExitCode)"
189+
Get-Content $installLog | Write-Host
190+
exit 1
191+
}
192+
193+
$installDir = "C:\Program Files\ModSecurity IIS"
194+
$requiredFiles = @(
195+
"modsecurity.conf",
196+
"modsecurity_iis.conf"
197+
)
198+
199+
foreach ($file in $requiredFiles) {
200+
$filePath = Join-Path $installDir $file
201+
if (-not (Test-Path $filePath)) {
202+
Write-Error "Required file $file not found in installation directory"
203+
exit 1
204+
}
205+
}
206+
207+
- name: Install OWASP Core Rules
208+
shell: pwsh
209+
run: |
210+
$crsVersion = "v4.18.0"
211+
$crsUrl = "https://github.com/coreruleset/coreruleset/archive/refs/tags/$crsVersion.tar.gz"
212+
$crsDir = "C:\Program Files\ModSecurity IIS\coreruleset"
213+
$modSecurityConfigDir = "C:\Program Files\ModSecurity IIS"
214+
215+
try {
216+
New-Item -ItemType Directory -Path $crsDir -Force
217+
Invoke-WebRequest -Uri $crsUrl -OutFile "$crsDir\$crsVersion.tar.gz"
218+
tar -xzf "$crsDir\$crsVersion.tar.gz" -C $crsDir --strip-components=1
219+
220+
Get-ChildItem "$crsDir" -Recurse -Filter "*.example" | ForEach-Object {
221+
$newName = $_.Name.Replace(".example", "")
222+
Rename-Item -Path $_.FullName -NewName $newName
223+
}
224+
225+
$modSecurityConfigFile = "$modSecurityConfigDir\modsecurity_iis.conf"
226+
227+
$crsRules = @(
228+
"Include coreruleset/crs-setup.conf",
229+
"Include coreruleset/plugins/*-config.conf",
230+
"Include coreruleset/plugins/*-before.conf",
231+
"Include coreruleset/rules/*.conf",
232+
"Include coreruleset/plugins/*-after.conf"
233+
)
234+
235+
Add-Content -Path $modSecurityConfigFile -Value $crsRules
236+
237+
(Get-Content -Path $modSecurityConfigDir\modsecurity.conf) -replace 'SecRuleEngine DetectionOnly', 'SecRuleEngine On' | Set-Content -Path $modSecurityConfigDir\modsecurity.conf
238+
239+
}
240+
catch {
241+
Write-Error "Failed to install OWASP Core Rules: $($_.Exception.Message)"
242+
exit 1
243+
}
244+
245+
- name: Test IIS Module
246+
shell: pwsh
247+
run: |
248+
$iisConfigDir = "C:\Program Files\ModSecurity IIS\"
249+
250+
Restart-Service W3SVC -Force
251+
252+
$modules = & "$env:SystemRoot\system32\inetsrv\appcmd.exe" list modules
253+
Write-Host "IIS modules: $modules"
254+
if ($LASTEXITCODE -ne 0) {
255+
Write-Error "appcmd failed with exit code $LASTEXITCODE"
256+
exit 1
257+
}
258+
259+
if (-not ($modules -match "ModSecurity")) {
260+
Write-Error "ModSecurity module not found in IIS modules"
261+
Write-Host "IIS modules: $modules"
262+
exit 1
263+
}
264+
265+
$testCases = @(
266+
@{Url = "http://localhost/"; Description = "Normal request"; ExpectedCode = 200},
267+
@{Url = "http://localhost/?id=1' OR '1'='1"; Description = "SQL injection attempt"; ExpectedCode = 403},
268+
@{Url = "http://localhost/?q=<script>alert('test')</script>"; Description = "XSS attempt"; ExpectedCode = 403}
269+
)
270+
271+
foreach ($test in $testCases) {
272+
try {
273+
$response = Invoke-WebRequest $test.Url -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 30
274+
275+
if ($response.StatusCode -eq $test.ExpectedCode) {
276+
Write-Host "PASS: $($test.Description) - returned $($response.StatusCode)"
277+
}
278+
else {
279+
Write-Host "FAIL: $($test.Description) - expected $($test.ExpectedCode) but got $($response.StatusCode)"
280+
}
281+
}
282+
catch {
283+
Write-Host "ERROR: $($test.Description) - request failed: $($_.Exception.Message)"
284+
}
285+
}
286+
287+
288+
# Check event log
289+
$badMessagePattern = 'Failed to find the RegisterModule entrypoint|The description for Event ID|The data is the error|dll failed to load'
290+
291+
$events = Get-EventLog -LogName Application -Newest 100 |
292+
Where-Object { $_.Message -match $badMessagePattern } |
293+
Where-Object { $_.Source -match 'IIS|W3SVC|mscor|IIS-W3SVC|IIS-W3WP|ModSecurity' }
294+
295+
if ($events -and $events.Count -gt 0) {
296+
Write-Host '::error:: Found errors in event log'
297+
$events | Select-Object TimeGenerated, Source, EntryType, EventID, Message | Format-List
298+
Exit 1
299+
}
300+
301+
Get-EventLog -LogName Application -Source ModSecurity | Format-List
302+
303+
- name: Install go-ftw
304+
shell: pwsh
305+
run: |
306+
go install github.com/coreruleset/go-ftw@latest
307+
308+
# Certain rules are disabled due to specific IIS behavior patterns.
309+
# Using go-ftw in cloud mode as the IIS connector does not generate logs in file format.
310+
# Technically, Event logs can be streamed to files, but this requires implementing rate limits to avoid log overflow.
311+
- name: Test ModSecurity Rules
312+
shell: pwsh
313+
run: |
314+
$testRuleDir = "C:\Program Files\ModSecurity IIS\coreruleset\tests\regression\tests"
315+
$goBinPath = ""
316+
if ($env:GOBIN) {
317+
$goBinPath = $env:GOBIN
318+
} elseif ($env:GOPATH) {
319+
$goBinPath = Join-Path $env:GOPATH "bin"
320+
} else {
321+
$goBinPath = Join-Path $env:USERPROFILE "go\bin"
322+
}
323+
324+
& "$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
325+

iis/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

0 commit comments

Comments
 (0)