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>
49 lines
1.5 KiB
Go
49 lines
1.5 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|