mirror of
https://github.com/pgsty/minio.git
synced 2026-07-20 12:40:24 +03:00
fix: tighten LDAP STS rate-limit accounting
Prevent LDAP STS reservation cancel paths from over-crediting rate-limit buckets by capping refill and refund capacity against in-flight reservations. Add an explicit trusted-proxy allowlist for LDAP STS source bucketing, prefer clean X-Real-IP values on trusted peers, and extend tests/docs for the new behavior.
This commit is contained in:
@@ -22,7 +22,9 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
@@ -43,6 +45,7 @@ type Config struct {
|
||||
LDAP ldap.Config
|
||||
|
||||
stsExpiryDuration time.Duration // contains converted value
|
||||
stsTrustedProxies []netip.Prefix
|
||||
}
|
||||
|
||||
// Enabled returns if LDAP is enabled.
|
||||
@@ -58,6 +61,7 @@ func (l *Config) Clone() Config {
|
||||
cfg := Config{
|
||||
LDAP: l.LDAP.Clone(),
|
||||
stsExpiryDuration: l.stsExpiryDuration,
|
||||
stsTrustedProxies: append([]netip.Prefix(nil), l.stsTrustedProxies...),
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
@@ -76,6 +80,7 @@ const (
|
||||
TLSSkipVerify = "tls_skip_verify"
|
||||
ServerInsecure = "server_insecure"
|
||||
ServerStartTLS = "server_starttls"
|
||||
STSTrustedProxies = "sts_trusted_proxies"
|
||||
|
||||
EnvServerAddr = "MINIO_IDENTITY_LDAP_SERVER_ADDR"
|
||||
EnvSRVRecordName = "MINIO_IDENTITY_LDAP_SRV_RECORD_NAME"
|
||||
@@ -90,6 +95,7 @@ const (
|
||||
EnvGroupSearchBaseDN = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"
|
||||
EnvLookupBindDN = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN"
|
||||
EnvLookupBindPassword = "MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD"
|
||||
EnvSTSTrustedProxies = "MINIO_IDENTITY_LDAP_STS_TRUSTED_PROXIES"
|
||||
)
|
||||
|
||||
var removedKeys = []string{
|
||||
@@ -155,9 +161,77 @@ var (
|
||||
Key: LookupBindPassword,
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: STSTrustedProxies,
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func parseSTSTrustedProxies(value string) ([]netip.Prefix, error) {
|
||||
fields := strings.FieldsFunc(value, func(r rune) bool {
|
||||
switch r {
|
||||
case ',', ';', ' ', '\n', '\r', '\t':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
prefixes := make([]netip.Prefix, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
if prefix, err := netip.ParsePrefix(field); err == nil {
|
||||
prefixes = append(prefixes, prefix.Masked())
|
||||
continue
|
||||
}
|
||||
|
||||
addr, err := netip.ParseAddr(field)
|
||||
if err != nil {
|
||||
return nil, config.Errorf("invalid LDAP STS trusted proxy %q", field)
|
||||
}
|
||||
bits := 32
|
||||
if addr.Is6() {
|
||||
bits = 128
|
||||
}
|
||||
prefixes = append(prefixes, netip.PrefixFrom(addr, bits))
|
||||
}
|
||||
|
||||
return prefixes, nil
|
||||
}
|
||||
|
||||
// SetSTSTrustedProxies parses and stores the LDAP STS trusted proxy allowlist.
|
||||
func (l *Config) SetSTSTrustedProxies(value string) error {
|
||||
prefixes, err := parseSTSTrustedProxies(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.stsTrustedProxies = prefixes
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSTSTrustedProxy reports whether the peer IP is allowed to supply forwarded headers
|
||||
// for LDAP STS source bucketing.
|
||||
func (l *Config) IsSTSTrustedProxy(peerIP string) bool {
|
||||
if l == nil || len(l.stsTrustedProxies) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
addr, err := netip.ParseAddr(peerIP)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, prefix := range l.stsTrustedProxies {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Enabled returns if LDAP config is enabled.
|
||||
func Enabled(kvs config.KVS) bool {
|
||||
return kvs.Get(ServerAddr) != ""
|
||||
@@ -255,6 +329,9 @@ func Lookup(s config.Config, rootCAs *x509.CertPool) (l Config, err error) {
|
||||
// Group search params configuration
|
||||
l.LDAP.GroupSearchFilter = getCfgVal(GroupSearchFilter)
|
||||
l.LDAP.GroupSearchBaseDistName = getCfgVal(GroupSearchBaseDN)
|
||||
if err = l.SetSTSTrustedProxies(getCfgVal(STSTrustedProxies)); err != nil {
|
||||
return l, err
|
||||
}
|
||||
|
||||
// If enable flag was not explicitly set, we treat it as implicitly set at
|
||||
// this point as necessary configuration is available.
|
||||
|
||||
@@ -102,6 +102,12 @@ var (
|
||||
Optional: true,
|
||||
Type: "on|off",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: STSTrustedProxies,
|
||||
Description: `"," separated list of trusted proxy IPs or CIDRs whose forwarded client IP headers may be used for LDAP STS rate limiting` + defaultHelpPostfix(STSTrustedProxies),
|
||||
Optional: true,
|
||||
Type: "list",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.Comment,
|
||||
Description: config.DefaultComment,
|
||||
|
||||
@@ -43,3 +43,34 @@ func TestIsUserDNNotFoundError(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user