feat(storage): support deleting backends and improve admin UI
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m41s
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.
This commit is contained in:
@@ -21,19 +21,19 @@ func ClientIPFromContext(r *http.Request) (string, bool) {
|
||||
// ClientIP resolves the effective client IP. When trustedProxies is empty,
|
||||
// forwarded headers are trusted for easy reverse-proxy/container defaults.
|
||||
func ClientIP(remoteAddr, forwardedFor, realIP string, trustedProxies []string) string {
|
||||
remoteIP := remoteIPOnly(remoteAddr)
|
||||
remoteIP := IPOnly(remoteAddr)
|
||||
if len(trustedProxies) == 0 || remoteTrusted(remoteIP, trustedProxies) {
|
||||
if ip := firstForwardedIP(forwardedFor); ip != "" {
|
||||
return ip
|
||||
return IPOnly(ip)
|
||||
}
|
||||
if ip := strings.TrimSpace(realIP); ip != "" {
|
||||
return ip
|
||||
return IPOnly(ip)
|
||||
}
|
||||
}
|
||||
return remoteIP
|
||||
}
|
||||
|
||||
func remoteIPOnly(remoteAddr string) string {
|
||||
func IPOnly(remoteAddr string) string {
|
||||
host := strings.TrimSpace(remoteAddr)
|
||||
if splitHost, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
||||
host = splitHost
|
||||
@@ -41,14 +41,65 @@ func remoteIPOnly(remoteAddr string) string {
|
||||
return strings.Trim(host, "[]")
|
||||
}
|
||||
|
||||
func firstForwardedIP(forwardedFor string) string {
|
||||
for _, part := range strings.Split(forwardedFor, ",") {
|
||||
ip := strings.TrimSpace(part)
|
||||
if ip != "" {
|
||||
return strings.Trim(ip, "[]")
|
||||
func IsProtectedProxyIP(ip string, trustedProxies []string) bool {
|
||||
parsed := net.ParseIP(IPOnly(ip))
|
||||
if parsed == nil {
|
||||
return false
|
||||
}
|
||||
if parsed.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
return remoteTrusted(parsed.String(), trustedProxies)
|
||||
}
|
||||
|
||||
func ProtectedBanTarget(target string, trustedProxies []string) bool {
|
||||
normalized, err := NormalizeBanTarget(target)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(normalized, "/") {
|
||||
return IsProtectedProxyIP(normalized, trustedProxies)
|
||||
}
|
||||
_, targetNet, err := net.ParseCIDR(normalized)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if targetNet.Contains(net.ParseIP("127.0.0.1")) || targetNet.Contains(net.ParseIP("::1")) {
|
||||
return true
|
||||
}
|
||||
for _, trusted := range trustedProxies {
|
||||
trusted = strings.TrimSpace(trusted)
|
||||
if trusted == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(trusted, "/") {
|
||||
if _, trustedNet, err := net.ParseCIDR(trusted); err == nil && networksOverlap(targetNet, trustedNet) {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(IPOnly(trusted)); ip != nil && targetNet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return false
|
||||
}
|
||||
|
||||
func firstForwardedIP(forwardedFor string) string {
|
||||
var fallback string
|
||||
for _, part := range strings.Split(forwardedFor, ",") {
|
||||
ip := IPOnly(part)
|
||||
if net.ParseIP(ip) == nil {
|
||||
continue
|
||||
}
|
||||
if fallback == "" {
|
||||
fallback = ip
|
||||
}
|
||||
if isExternalIP(ip) {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func remoteTrusted(remoteIP string, trustedProxies []string) bool {
|
||||
@@ -73,3 +124,17 @@ func remoteTrusted(remoteIP string, trustedProxies []string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isExternalIP(ip string) bool {
|
||||
parsed := net.ParseIP(IPOnly(ip))
|
||||
return parsed != nil &&
|
||||
!parsed.IsLoopback() &&
|
||||
!parsed.IsPrivate() &&
|
||||
!parsed.IsLinkLocalUnicast() &&
|
||||
!parsed.IsLinkLocalMulticast() &&
|
||||
!parsed.IsUnspecified()
|
||||
}
|
||||
|
||||
func networksOverlap(a, b *net.IPNet) bool {
|
||||
return a.Contains(b.IP) || b.Contains(a.IP)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user