All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m41s
- Implement storage backend deletion, which automatically resets default storage settings and user-specific overrides when a backend is removed. - Add unit tests covering the delete action and its cleanup side effects. - Improve admin UI responsiveness, fixing table scrolling, flex wrapping, and text truncation for long storage backend names. - Update security documentation to clarify trusted proxy configurations and explain how trusted proxies are protected from automatic bans.
65 lines
2.6 KiB
Go
65 lines
2.6 KiB
Go
package middleware
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"warpbox.dev/backend/libs/services"
|
|
)
|
|
|
|
func Bans(logger *slog.Logger, bans *services.BanService, trustedProxies []string) Middleware {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ip, ok := services.ClientIPFromContext(r)
|
|
if !ok {
|
|
ip = services.ClientIP(r.RemoteAddr, r.Header.Get("X-Forwarded-For"), r.Header.Get("X-Real-IP"), trustedProxies)
|
|
r = services.WithClientIP(r, ip)
|
|
}
|
|
now := time.Now().UTC()
|
|
protectedProxy := services.IsProtectedProxyIP(ip, trustedProxies)
|
|
|
|
if bans != nil && !protectedProxy {
|
|
if matched, ok, err := bans.Match(ip, now); err != nil {
|
|
logger.Error("ban match failed", "source", "ban", "severity", "error", "code", 5001, "ip", ip, "error", err.Error())
|
|
} else if ok {
|
|
logger.Warn("banned request blocked", "source", "ban", "severity", "warn", "code", 4030, "ip", ip, "ban_id", matched.Ban.ID, "target", matched.Ban.Normalized, "path", r.URL.Path)
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.WriteHeader(http.StatusForbidden)
|
|
_, _ = w.Write([]byte("forbidden\n"))
|
|
return
|
|
}
|
|
|
|
settings, err := bans.Settings()
|
|
if err != nil {
|
|
logger.Error("ban settings load failed", "source", "ban", "severity", "error", "code", 5004, "ip", ip, "error", err.Error())
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if !settings.AutoBanEnabled {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if pattern, err := bans.MaliciousPattern(r.URL.Path); err != nil {
|
|
logger.Error("malicious path check failed", "source", "ban", "severity", "error", "code", 5002, "ip", ip, "error", err.Error())
|
|
} else if pattern != "" {
|
|
if result, err := bans.RecordAbuse(ip, services.AbuseKindMaliciousPath, r.URL.Path, settings.MaliciousPathThreshold, now); err != nil {
|
|
logger.Error("malicious path event failed", "source", "ban", "severity", "error", "code", 5003, "ip", ip, "path", r.URL.Path, "error", err.Error())
|
|
} else if result.Enabled {
|
|
logger.Warn("malicious path requested", "source", "ban", "severity", "warn", "code", 4302, "ip", ip, "path", r.URL.Path, "pattern", pattern, "count", result.Event.Count)
|
|
if result.Triggered {
|
|
logger.Warn("ip auto-banned for malicious path", "source", "ban", "severity", "warn", "code", 4303, "ip", ip, "ban_id", result.Ban.ID, "path", r.URL.Path)
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.WriteHeader(http.StatusForbidden)
|
|
_, _ = w.Write([]byte("forbidden\n"))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|