feat(security): implement IP ban for repeated management key and Redis AUTH failures

- Added IP ban logic to `AuthenticateManagementKey` and Redis protocol handlers, blocking requests after multiple failed attempts.
- Introduced unit tests to validate IP ban behavior across localhost and remote clients.
- Synchronized Redis protocol's authentication policy with management key validation.
This commit is contained in:
Luis Pater
2026-04-25 21:39:58 +08:00
parent 28d78273e4
commit 2c626efc59
4 changed files with 328 additions and 76 deletions
+57 -3
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net"
"net/http"
"strconv"
"strings"
@@ -66,10 +67,38 @@ func (s *Server) handleRedisConnection(conn net.Conn, reader *bufio.Reader) {
}
cmd := strings.ToUpper(strings.TrimSpace(args[0]))
if cmd != "AUTH" && !authed {
if s.mgmt != nil {
_, statusCode, errMsg := s.mgmt.AuthenticateManagementKey(clientIP, localClient, "")
if statusCode == http.StatusForbidden && strings.HasPrefix(errMsg, "IP banned due to too many failed attempts") {
_ = writeRedisError(writer, "ERR "+errMsg)
} else {
_ = writeRedisError(writer, "NOAUTH Authentication required.")
}
} else {
_ = writeRedisError(writer, "NOAUTH Authentication required.")
}
if !flush() {
return
}
continue
}
switch cmd {
case "AUTH":
password, ok := parseAuthPassword(args)
if !ok {
if s.mgmt != nil {
_, statusCode, errMsg := s.mgmt.AuthenticateManagementKey(clientIP, localClient, "")
if statusCode == http.StatusForbidden && strings.HasPrefix(errMsg, "IP banned due to too many failed attempts") {
_ = writeRedisError(writer, "ERR "+errMsg)
if !flush() {
return
}
continue
}
}
_ = writeRedisError(writer, "ERR wrong number of arguments for 'auth' command")
if !flush() {
return
@@ -151,10 +180,35 @@ func resolveRemoteIP(addr net.Addr) (ip string, localClient bool) {
if addr == nil {
return "", false
}
host := addr.String()
if h, _, err := net.SplitHostPort(host); err == nil {
host = h
var host string
switch a := addr.(type) {
case *net.TCPAddr:
if a != nil && a.IP != nil {
if ip4 := a.IP.To4(); ip4 != nil {
host = ip4.String()
} else {
host = a.IP.String()
}
}
default:
host = addr.String()
if h, _, err := net.SplitHostPort(host); err == nil {
host = h
}
host = strings.TrimSpace(host)
if raw, _, ok := strings.Cut(host, "%"); ok {
host = raw
}
if parsed := net.ParseIP(host); parsed != nil {
if ip4 := parsed.To4(); ip4 != nil {
host = ip4.String()
} else {
host = parsed.String()
}
}
}
host = strings.TrimSpace(host)
localClient = host == "127.0.0.1" || host == "::1"
return host, localClient