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:
Debian
2026-08-03 15:51:39 +02:00
parent 0846eaa05b
commit 5c268425c1
10 changed files with 776 additions and 20 deletions

View File

@@ -1880,7 +1880,11 @@
"deleteFailed": "Löschen fehlgeschlagen",
"secretSet": "Gespeichert — leer lassen, um es unverändert zu lassen.",
"secretUnset": "Noch nichts gespeichert.",
"tabs": { "settings": "Einstellungen", "clients": "Clients (NAS)", "users": "Benutzer" },
"tabs": {
"settings": "Einstellungen",
"clients": "Clients (NAS)",
"users": "Benutzer"
},
"settings": {
"enabled": "RADIUS auf dieser Node aktiv",
"listen": "Listen-Adressen"
@@ -1951,11 +1955,15 @@
"exclusionAddPlaceholder": "Regel suchen (ID oder Beschreibung)…",
"exclusionAddNote": "Notiz (optional)",
"exclusionAddNotFound": "Keine Regel gefunden",
"exclusionAddBtn": "Hinzufügen"
"exclusionAddBtn": "Hinzufügen",
"appProfiles": "Eigene App-Profile",
"appProfilesHint": "Wiederverwendbare, selbst gepflegte Profile (Regel-ID-Bündel) — zentral unter „App-Profile“ anlegen und hier pro Domain zuweisen. Ergänzt die OWASP-Plugins oben.",
"appProfilesPlaceholder": "Eigene Profile wählen (optional)"
},
"tabs": {
"domains": "Domains",
"alerts": "Alarme"
"alerts": "Alarme",
"profiles": "App-Profile"
},
"alerts": {
"total": "Einträge",
@@ -1984,6 +1992,39 @@
"exceptionModalHint": "Optional: Begründung warum diese Regel ein False Positive für diese Domain ist.",
"exceptionNotePlaceholder": "z.B. Unsere API verwendet nicht-standardisierte Header die diese Regel auslösen.",
"alreadyExcluded": "Bereits Ausnahme"
},
"profiles": {
"intro": "Wiederverwendbare Ausnahme-Profile (Bündel von CRS-Regel-IDs). Einmal anlegen, pro Domain zuweisen. Die eingebauten OWASP-Plugins sind read-only und werden pro Domain gewählt.",
"new": "Neues Profil",
"empty": "Noch keine eigenen Profile. Lege eins an, um Regel-Ausnahmen wiederzuverwenden.",
"builtinInfo": "Eingebaute OWASP-CRS-Plugins (dateibasiert, gepflegt). Read-only — pro Domain im WAF-Drawer wählbar. „Als Vorlage“ erstellt daraus ein leeres eigenes Profil zum Befüllen.",
"typeBuiltin": "OWASP",
"typeCustom": "Eigen",
"col": {
"name": "Name",
"type": "Typ",
"description": "Beschreibung",
"rules": "Ausnahmen"
},
"edit": "Bearbeiten",
"delete": "Löschen",
"clone": "Klonen",
"asTemplate": "Als Vorlage",
"deleteConfirm": "Profil wirklich löschen? Zuweisungen an Domains verlieren dann diese Ausnahmen.",
"createTitle": "Neues App-Profil",
"editTitle": "App-Profil bearbeiten",
"name": "Name",
"namePlaceholder": "z.B. Meine WebApp",
"description": "Beschreibung",
"descriptionPlaceholder": "Wofür ist dieses Profil? (optional)",
"rules": "Regel-Ausnahmen",
"rulesHint": "CRS-Regel-IDs die für zugewiesene Domains deaktiviert werden. Durchsuchbar nach ID oder Beschreibung.",
"rulesPlaceholder": "Regel-IDs suchen und hinzufügen…",
"saved": "Profil gespeichert.",
"saveFailed": "Profil konnte nicht gespeichert werden.",
"deleted": "Profil gelöscht.",
"deleteFailed": "Profil konnte nicht gelöscht werden.",
"cloneSuffix": "Kopie"
}
}
}
}

View File

@@ -1880,7 +1880,11 @@
"deleteFailed": "Delete failed",
"secretSet": "Stored — leave empty to keep unchanged.",
"secretUnset": "Nothing stored yet.",
"tabs": { "settings": "Settings", "clients": "Clients (NAS)", "users": "Users" },
"tabs": {
"settings": "Settings",
"clients": "Clients (NAS)",
"users": "Users"
},
"settings": {
"enabled": "RADIUS active on this node",
"listen": "Listen addresses"
@@ -1951,11 +1955,15 @@
"exclusionAddPlaceholder": "Search rule (ID or description)…",
"exclusionAddNote": "Note (optional)",
"exclusionAddNotFound": "No rule found",
"exclusionAddBtn": "Add"
"exclusionAddBtn": "Add",
"appProfiles": "Custom App Profiles",
"appProfilesHint": "Reusable, self-maintained profiles (bundles of rule IDs) — create them centrally under “App Profiles” and assign them here per domain. Complements the OWASP plugins above.",
"appProfilesPlaceholder": "Select custom profiles (optional)"
},
"tabs": {
"domains": "Domains",
"alerts": "Alerts"
"alerts": "Alerts",
"profiles": "App Profiles"
},
"alerts": {
"total": "entries",
@@ -1984,6 +1992,39 @@
"exceptionModalHint": "Optional: describe why this rule is a false positive for this domain.",
"exceptionNotePlaceholder": "e.g. Our custom API uses non-standard headers that trigger this rule.",
"alreadyExcluded": "Already excluded"
},
"profiles": {
"intro": "Reusable exclusion profiles (bundles of CRS rule IDs). Create once, assign per domain. The built-in OWASP plugins are read-only and selected per domain.",
"new": "New Profile",
"empty": "No custom profiles yet. Create one to reuse rule exclusions.",
"builtinInfo": "Built-in OWASP CRS plugins (file-based, maintained). Read-only — selectable per domain in the WAF drawer. “Use as template” creates an empty custom profile from it to fill in.",
"typeBuiltin": "OWASP",
"typeCustom": "Custom",
"col": {
"name": "Name",
"type": "Type",
"description": "Description",
"rules": "Exclusions"
},
"edit": "Edit",
"delete": "Delete",
"clone": "Clone",
"asTemplate": "Use as template",
"deleteConfirm": "Really delete this profile? Domains using it will lose these exclusions.",
"createTitle": "New App Profile",
"editTitle": "Edit App Profile",
"name": "Name",
"namePlaceholder": "e.g. My WebApp",
"description": "Description",
"descriptionPlaceholder": "What is this profile for? (optional)",
"rules": "Rule Exclusions",
"rulesHint": "CRS rule IDs disabled for assigned domains. Searchable by ID or description.",
"rulesPlaceholder": "Search and add rule IDs…",
"saved": "Profile saved.",
"saveFailed": "Could not save profile.",
"deleted": "Profile deleted.",
"deleteFailed": "Could not delete profile.",
"cloneSuffix": "Copy"
}
}
}
}