mirror of
https://github.com/pgsty/minio.git
synced 2026-07-20 04:30:26 +03:00
5e40665acd
Remove the per-username LDAP STS throttle bucket and keep the limiter keyed only by source IP. A username bucket is shared across all clients and lets one source keep a known account's bucket drained with bad-password attempts, locking the legitimate user out before LDAP bind. For trusted proxies, stop trusting the left-most forwarded address. Resolve X-Forwarded-For right-to-left, skip trusted proxy hops, reject catch-all trusted-proxy CIDRs, and intentionally ignore RFC 7239 Forwarded for this security-sensitive bucket. Document that X-Real-IP is trusted verbatim and must be overwritten by the trusted proxy, not passed through from clients. Update focused limiter, source-IP, trusted-proxy, and LDAP config tests to cover source-only buckets, spoofed appended XFF, multi-hop trusted proxies, Forwarded fallback, catch-all rejection, and the X-Real-IP deployment contract. Co-authored-by: Codex <codex@openai.com> Co-authored-by: Claude Code <claude-code@anthropic.com>
86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
package ldap
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestWrapAuthError(t *testing.T) {
|
|
baseErr := errors.New("LDAP auth failed for DN uid=dillon,dc=min,dc=io")
|
|
err := wrapAuthError(baseErr)
|
|
|
|
if !IsAuthError(err) {
|
|
t.Fatal("expected wrapped error to be recognized as auth failure")
|
|
}
|
|
if err.Error() != baseErr.Error() {
|
|
t.Fatalf("expected error text %q, got %q", baseErr.Error(), err.Error())
|
|
}
|
|
}
|
|
|
|
func TestWrapAuthErrorNil(t *testing.T) {
|
|
if wrapAuthError(nil) != nil {
|
|
t.Fatal("expected nil input to stay nil")
|
|
}
|
|
}
|
|
|
|
func TestIsAuthErrorNegative(t *testing.T) {
|
|
if IsAuthError(errors.New("ldap unavailable")) {
|
|
t.Fatal("expected plain errors to not be classified as auth failures")
|
|
}
|
|
}
|
|
|
|
func TestIsUserDNNotFoundError(t *testing.T) {
|
|
if isUserDNNotFoundError(nil) {
|
|
t.Fatal("expected nil error to not be detected as user-not-found")
|
|
}
|
|
if !isUserDNNotFoundError(errors.New("user DN not found for: dillon")) {
|
|
t.Fatal("expected lowercase user-not-found error to be detected")
|
|
}
|
|
if !isUserDNNotFoundError(errors.New("User DN not found for: dillon")) {
|
|
t.Fatal("expected legacy uppercase user-not-found error to be detected")
|
|
}
|
|
if isUserDNNotFoundError(errors.New("base DN (dc=min,dc=io) for user DN search does not exist")) {
|
|
t.Fatal("expected infrastructure lookup error to not be detected as user-not-found")
|
|
}
|
|
}
|
|
|
|
func TestSetSTSTrustedProxies(t *testing.T) {
|
|
var cfg Config
|
|
if err := cfg.SetSTSTrustedProxies("192.0.2.10,198.51.100.0/24;2001:db8::/126"); err != nil {
|
|
t.Fatalf("expected trusted proxies to parse, got %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
peerIP string
|
|
want bool
|
|
}{
|
|
{peerIP: "192.0.2.10", want: true},
|
|
{peerIP: "198.51.100.23", want: true},
|
|
{peerIP: "198.51.101.23", want: false},
|
|
{peerIP: "2001:db8::2", want: true},
|
|
{peerIP: "2001:db8::8", want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := cfg.IsSTSTrustedProxy(tt.peerIP); got != tt.want {
|
|
t.Fatalf("peer %q: expected %v, got %v", tt.peerIP, tt.want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetSTSTrustedProxiesRejectsInvalidEntries(t *testing.T) {
|
|
var cfg Config
|
|
if err := cfg.SetSTSTrustedProxies("192.0.2.0/24,not-an-ip"); err == nil {
|
|
t.Fatal("expected invalid trusted proxy list to fail")
|
|
}
|
|
}
|
|
|
|
func TestSetSTSTrustedProxiesRejectsCatchAll(t *testing.T) {
|
|
for _, value := range []string{"0.0.0.0/0", "::/0", "192.0.2.0/24,0.0.0.0/0"} {
|
|
var cfg Config
|
|
if err := cfg.SetSTSTrustedProxies(value); err == nil {
|
|
t.Fatalf("expected catch-all trusted proxy %q to be rejected", value)
|
|
}
|
|
}
|
|
}
|