feat(waf): benutzerdefinierte App-Profile + Fix: CRS-Plugins wurden im Agent nie geladen — v1.3.17
Neu: eigene WAF-App-Profile (benannte, wiederverwendbare Rule-ID-Ausnahme- Bündel) — zentrale Bibliothek im UI (eigener Tab), pro Domain zuweisbar, Built-in-OWASP-Plugins bleiben read-only + als Vorlage klonbar. Nur reine Rule-IDs/Ranges (keine SecLang-Ausführung, injektionssicher). - Migration 0047: Tabelle waf_app_profiles (repliziert via reconcile) + waf_configs.app_profiles. - Service/Handler: CRUD (/waf/profiles), Built-ins geschützt (builtin=false-Gate). - Agent-Loader: app_profiles → in effektive rule_exclusions gemerged; ihr updated_at hebt das effektive updated_at der Domain → Engine-Rebuild bei Profil-Edit. - UI: Profile-Tab (Liste/Editor mit durchsuchbaren Rule-IDs) + Multi-Select im Domain-Drawer. FIX (wichtig): ListAllWithDomain — der EINZIGE Loader des laufenden WAF-Agents — selektierte crs_plugins nie. Dadurch war cfg.CRSPlugins im Agent immer leer und KEIN Built-in-CRS-Plugin (Nextcloud/WordPress/Drupal) wurde je in die Engine inkludiert. Jetzt geladen (+ app_profiles). Die per-Domain-Plugin-Wahl wirkt damit erstmals tatsächlich. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
48
internal/services/waf/appprofiles_test.go
Normal file
48
internal/services/waf/appprofiles_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
|
||||
)
|
||||
|
||||
func TestMergeProfileExclusions(t *testing.T) {
|
||||
base := time.Date(2026, 8, 3, 10, 0, 0, 0, time.UTC)
|
||||
newer := base.Add(1 * time.Hour)
|
||||
profiles := map[string]models.WafAppProfile{
|
||||
"nc": {Name: "nc", RuleExclusions: []string{"942100", "920420"}, UpdatedAt: newer},
|
||||
"wp": {Name: "wp", RuleExclusions: []string{"942100", "941100"}, UpdatedAt: base},
|
||||
}
|
||||
|
||||
t.Run("keine Profile → unverändert", func(t *testing.T) {
|
||||
got, ts := mergeProfileExclusions([]string{"1000"}, base, nil, profiles)
|
||||
if len(got) != 1 || got[0] != "1000" || !ts.Equal(base) {
|
||||
t.Fatalf("got=%v ts=%v", got, ts)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("union dedupliziert, Reihenfolge stabil", func(t *testing.T) {
|
||||
got, ts := mergeProfileExclusions([]string{"942100", "900001"}, base, []string{"nc", "wp"}, profiles)
|
||||
want := []string{"942100", "900001", "920420", "941100"} // 942100 nicht doppelt
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("got=%v want=%v", got, want)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("got=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
// effektives updated_at = max(base, nc.newer) = newer
|
||||
if !ts.Equal(newer) {
|
||||
t.Fatalf("ts=%v want=%v (Profil-Edit muss Rebuild ausloesen)", ts, newer)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unbekanntes Profil defensiv ignoriert", func(t *testing.T) {
|
||||
got, ts := mergeProfileExclusions([]string{"1000"}, base, []string{"gibtsnicht"}, profiles)
|
||||
if len(got) != 1 || got[0] != "1000" || !ts.Equal(base) {
|
||||
t.Fatalf("got=%v ts=%v", got, ts)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user