feat(waf): CRS-App-Exclusion-Plugins (Nextcloud/WordPress/Drupal) pro Domain — v1.3.12
Statt manueller SecRuleRemoveById-IDs kann man pro Domain offizielle OWASP-CRS- Exclusion-Plugins aktivieren — pfad-genaue, upstream-gepflegte App-Ausnahmen. - Migration 0046: waf_configs.crs_plugins text[]. - Engine (engine.go): je gewähltem Plugin werden config/before VOR den CRS-Rules und after DANACH inkludiert (exakt nach OWASP-CRS-Plugin-Spec); nur die für DIESE Domain gewählten, nur wenn die Datei existiert. Whitelist KnownCRSPlugins. - Handler: crs_plugins im Upsert-Body + Whitelist-Validierung (Include-Pfad- Injection-Schutz). - Packaging (postinst): lädt die Plugins (coreruleset/<name>-plugin) nach <crs>/plugins/ — self-healing auf jedem configure, nur fehlende. - UI: Multi-Select „App-Profile (CRS-Plugins)" im WAF-Config-Drawer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -48,12 +48,23 @@ func buildDirectives(cfg models.WafConfig, crsDir string) string {
|
||||
pl = 1
|
||||
}
|
||||
fmt.Fprintf(&sb, "SecAction \"id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=%d\"\n", pl)
|
||||
setupConf := filepath.Join(crsDir, "crs-setup.conf")
|
||||
if _, err := os.Stat(setupConf); err == nil {
|
||||
fmt.Fprintf(&sb, "Include %s\n", setupConf)
|
||||
includeIfExists(&sb, filepath.Join(crsDir, "crs-setup.conf"))
|
||||
|
||||
// CRS-App-Exclusion-Plugins: config + before laufen VOR den CRS-Rules
|
||||
// (setzen Enable-Vars + pfad-scoped ctl:ruleRemoveById), after DANACH —
|
||||
// exakt nach OWASP-CRS-Plugin-Spec. Es werden NUR die für DIESE Domain
|
||||
// gewählten Plugins inkludiert (per-Domain, nicht global).
|
||||
plugins := resolveCRSPlugins(cfg.CRSPlugins)
|
||||
for _, prefix := range plugins {
|
||||
includeIfExists(&sb, filepath.Join(crsDir, "plugins", prefix+"-config.conf"))
|
||||
includeIfExists(&sb, filepath.Join(crsDir, "plugins", prefix+"-before.conf"))
|
||||
}
|
||||
// rules/*.conf ist ein Glob (kein Stat) — crsAvailable() hat oben bereits
|
||||
// bestätigt, dass mind. eine .conf existiert.
|
||||
fmt.Fprintf(&sb, "Include %s\n", filepath.Join(crsDir, "rules", "*.conf"))
|
||||
for _, prefix := range plugins {
|
||||
includeIfExists(&sb, filepath.Join(crsDir, "plugins", prefix+"-after.conf"))
|
||||
}
|
||||
rulesGlob := filepath.Join(crsDir, "rules", "*.conf")
|
||||
fmt.Fprintf(&sb, "Include %s\n", rulesGlob)
|
||||
}
|
||||
|
||||
// Rule exclusions (applied after CRS load so they override CRS).
|
||||
@@ -78,6 +89,35 @@ func buildDirectives(cfg models.WafConfig, crsDir string) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// KnownCRSPlugins mappt den kurzen Plugin-Namen (gespeichert in
|
||||
// waf_configs.crs_plugins, im UI gewählt) auf sein Datei-Prefix in
|
||||
// <crsDir>/plugins/. Nur diese werden paketiert (postinst) und akzeptiert.
|
||||
var KnownCRSPlugins = map[string]string{
|
||||
"nextcloud": "nextcloud-rule-exclusions",
|
||||
"wordpress": "wordpress-rule-exclusions",
|
||||
"drupal": "drupal-rule-exclusions",
|
||||
}
|
||||
|
||||
// resolveCRSPlugins mappt gewählte Plugin-Namen auf ihre Datei-Prefixe und
|
||||
// filtert unbekannte/leere raus — defensiv, nie ungültige Includes rendern.
|
||||
func resolveCRSPlugins(names []string) []string {
|
||||
out := make([]string, 0, len(names))
|
||||
for _, n := range names {
|
||||
if prefix, ok := KnownCRSPlugins[strings.TrimSpace(n)]; ok {
|
||||
out = append(out, prefix)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// includeIfExists rendert eine Include-Zeile nur, wenn die Datei existiert — so
|
||||
// bricht ein gewähltes-aber-nicht-installiertes Plugin die Config nicht.
|
||||
func includeIfExists(sb *strings.Builder, path string) {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
fmt.Fprintf(sb, "Include %s\n", path)
|
||||
}
|
||||
}
|
||||
|
||||
func ruleEngineMode(mode string) string {
|
||||
switch mode {
|
||||
case "blocking":
|
||||
|
||||
Reference in New Issue
Block a user