fix: resolve WAF integration tests and benchmark workflow

This commit is contained in:
GitHub Actions
2025-12-03 19:36:48 +00:00
parent 969ca50177
commit cc61830908
14 changed files with 121 additions and 17 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
update-draft:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Draft Release
uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6
env:
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
name: Performance Regression Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
@@ -34,7 +34,7 @@ jobs:
- name: Run Benchmark
working-directory: backend
run: go test -bench=. -benchmem ./... | tee output.txt
run: go test -bench=. -benchmem -run='^$' ./... | tee output.txt
- name: Store Benchmark Result
uses: benchmark-action/github-action-benchmark@v1
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0
@@ -47,7 +47,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
language: [ 'go', 'javascript-typescript' ]
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Initialize CodeQL
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v4
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
hadolint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Run Hadolint
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
+3 -3
View File
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Normalize image name
run: |
@@ -181,7 +181,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Normalize image name
run: |
@@ -258,7 +258,7 @@ jobs:
if: github.event_name == 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Build image locally for PR
run: |
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
steps:
# Step 1: Get the code
- name: 📥 Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
# Step 2: Set up Node.js (for building any JS-based doc tools)
- name: 🔧 Set up Node.js
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
name: Backend (Go)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
@@ -62,7 +62,7 @@ jobs:
name: Frontend (React)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
CHARON_TOKEN: ${{ secrets.CHARON_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
+9 -1
View File
@@ -131,7 +131,7 @@ func (m *Manager) ApplyConfig(ctx context.Context) error {
engineMode := "On" // default to blocking
if rs.Mode == "detection" || rs.Mode == "monitor" {
engineMode = "DetectionOnly"
} else if rs.Mode == "" && secCfg.WAFMode == "monitor" {
} else if rs.Mode == "" && strings.EqualFold(secCfg.WAFMode, "monitor") {
// No per-ruleset mode set, use global WAFMode
engineMode = "DetectionOnly"
}
@@ -386,6 +386,14 @@ func (m *Manager) computeEffectiveFlags(ctx context.Context) (cerbEnabled bool,
crowdsecEnabled = false
}
}
// runtime override for WAF mode
var sc models.SecurityConfig
if err := m.db.Where("name = ?", "default").First(&sc).Error; err == nil {
if sc.WAFMode != "" {
wafEnabled = !strings.EqualFold(sc.WAFMode, "disabled")
}
}
}
// ACL, WAF, RateLimit and CrowdSec should only be considered enabled if Cerberus is enabled.
+74
View File
@@ -8,6 +8,7 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
@@ -459,3 +460,76 @@ func TestComputeEffectiveFlags_DB_ACLTrueAndFalse(t *testing.T) {
_, acl, _, _, _ = manager.computeEffectiveFlags(context.Background())
require.False(t, acl)
}
func TestComputeEffectiveFlags_DB_WAFMonitor(t *testing.T) {
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(&models.Setting{}, &models.SecurityConfig{}))
secCfg := config.SecurityConfig{CerberusEnabled: true, WAFMode: "enabled"}
manager := NewManager(nil, db, "", "", false, secCfg)
// Set WAF mode to monitor
res := db.Create(&models.SecurityConfig{Name: "default", Enabled: true, WAFMode: "monitor"})
require.NoError(t, res.Error)
_, _, waf, _, _ := manager.computeEffectiveFlags(context.Background())
require.True(t, waf) // Should still be true (enabled)
}
func TestManager_ApplyConfig_WAFMonitor(t *testing.T) {
// Mock Caddy Admin API
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/load" && r.Method == "POST" {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusNotFound)
}))
defer caddyServer.Close()
// Setup DB
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}, &models.SecurityConfig{}, &models.SecurityRuleSet{}, &models.SecurityDecision{}))
// Set WAF mode to monitor
db.Create(&models.SecurityConfig{Name: "default", Enabled: true, WAFMode: "monitor", AdminWhitelist: "127.0.0.1"})
// Create a ruleset
db.Create(&models.SecurityRuleSet{Name: "owasp-crs", Content: "SecRule REQUEST_URI \"@rx ^/admin\" \"id:101,phase:1,deny,status:403\""})
// Setup Manager
tmpDir := t.TempDir()
client := NewClient(caddyServer.URL)
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{CerberusEnabled: true, WAFMode: "enabled"})
// Capture file writes to verify WAF mode injection
var writtenContent string
originalWriteFile := writeFileFunc
defer func() { writeFileFunc = originalWriteFile }()
writeFileFunc = func(filename string, data []byte, perm os.FileMode) error {
if strings.Contains(filename, "owasp-crs.conf") {
writtenContent = string(data)
}
return originalWriteFile(filename, data, perm)
}
// Create a host
host := models.ProxyHost{
DomainNames: "example.com",
ForwardHost: "127.0.0.1",
ForwardPort: 8080,
}
db.Create(&host)
// Apply Config
err = manager.ApplyConfig(context.Background())
assert.NoError(t, err)
// Verify that DetectionOnly was injected into the ruleset file
assert.Contains(t, writtenContent, "SecRuleEngine DetectionOnly")
assert.Contains(t, writtenContent, "SecRequestBodyAccess On")
}
@@ -0,0 +1,22 @@
package services
import (
"testing"
"time"
)
func BenchmarkFormatDuration(b *testing.B) {
d := 3665 * time.Second
b.ResetTimer()
for i := 0; i < b.N; i++ {
formatDuration(d)
}
}
func BenchmarkExtractPort(b *testing.B) {
url := "http://example.com:8080"
b.ResetTimer()
for i := 0; i < b.N; i++ {
extractPort(url)
}
}