feat(waf): Alerts — Regelübereinstimmungen in DB + UI — v1.2.77
- Migration 0038: waf_alerts-Tabelle - AlertWriter (Buffered-Channel → async DB-Write) - SPOE: MatchedRules → sendAlert() nach ProcessRequestHeaders() - API: GET /waf/alerts + DELETE /waf/alerts - WAF-Page: Tabs Domains | Alarme; Alarme-Tabelle mit Rule-ID, Severity, Aktion (Detected/Blocked), URI, Client-IP + Purge-Button Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/corazawaf/coraza/v3/types"
|
||||
"github.com/dropmorepackets/haproxy-go/pkg/encoding"
|
||||
"github.com/dropmorepackets/haproxy-go/spop"
|
||||
)
|
||||
@@ -13,8 +14,9 @@ import (
|
||||
// SPOEAgent wraps the haproxy-go SPOE server and dispatches each
|
||||
// inspected request to the appropriate per-domain Coraza engine.
|
||||
type SPOEAgent struct {
|
||||
Manager *Manager
|
||||
Addr string
|
||||
Manager *Manager
|
||||
AlertWriter *AlertWriter
|
||||
Addr string
|
||||
}
|
||||
|
||||
// ListenAndServe starts the SPOE agent. Blocks until ctx is cancelled.
|
||||
@@ -106,33 +108,48 @@ func (a *SPOEAgent) handle(ctx context.Context, w *encoding.ActionWriter, m *enc
|
||||
|
||||
// Evaluate request headers.
|
||||
interruption := tx.ProcessRequestHeaders()
|
||||
|
||||
// Log all matched rules (detection + blocking).
|
||||
for _, mr := range tx.MatchedRules() {
|
||||
a.sendAlert(host, clientIP, method, uri, mr, interruption != nil)
|
||||
}
|
||||
|
||||
if interruption != nil {
|
||||
status := interruption.Status
|
||||
if status == 0 {
|
||||
status = http.StatusForbidden
|
||||
}
|
||||
slog.Info("waf: request blocked",
|
||||
"host", host,
|
||||
"method", method,
|
||||
"uri", uri,
|
||||
"client", clientIP,
|
||||
"status", status,
|
||||
"mode", de.Mode,
|
||||
"host", host, "method", method, "uri", uri,
|
||||
"client", clientIP, "status", status, "rule", interruption.RuleID,
|
||||
)
|
||||
if de.Mode == "blocking" {
|
||||
if err := w.SetInt64(encoding.VarScopeTransaction, "status", int64(status)); err != nil {
|
||||
slog.Warn("waf: SetInt64 status", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendAlert enqueues a WAF alert for async DB write.
|
||||
func (a *SPOEAgent) sendAlert(host, clientIP, method, uri string, mr types.MatchedRule, blocked bool) {
|
||||
if a.AlertWriter == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Alert-only log for detection mode.
|
||||
if tx.IsInterrupted() && de.Mode != "blocking" {
|
||||
slog.Info("waf: request flagged (detection)",
|
||||
"host", host, "method", method, "uri", uri, "client", clientIP,
|
||||
)
|
||||
action := "detected"
|
||||
if blocked && mr.Disruptive() {
|
||||
action = "blocked"
|
||||
}
|
||||
a.AlertWriter.Send(Alert{
|
||||
Hostname: host,
|
||||
ClientIP: clientIP,
|
||||
Method: method,
|
||||
URI: uri,
|
||||
RuleID: mr.Rule().ID(),
|
||||
RuleMsg: mr.Message(),
|
||||
Severity: mr.Rule().Severity().String(),
|
||||
Action: action,
|
||||
})
|
||||
}
|
||||
|
||||
// parseHeaders splits HAProxy raw headers ("Name: value\r\n…") and
|
||||
|
||||
Reference in New Issue
Block a user