fix: resolve WAF integration tests and benchmark workflow
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user