Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions example.pgdog.toml
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ auth_type = "scram"
# Prevents brute-force authentication attacks by limiting the number
# of authentication attempts from a single IP address.
#
# Default: 10
auth_rate_limit = 10
# Default: unlimited (disabled)
# auth_rate_limit = 10

# Disable cross-shard queries.
#
Expand Down
35 changes: 22 additions & 13 deletions pgdog/src/auth/rate_limit.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
//! Rate limiting for authentication attempts.

use governor::{
clock::DefaultClock,
state::keyed::DefaultKeyedStateStore,
Quota, RateLimiter,
};
use governor::{clock::DefaultClock, state::keyed::DefaultKeyedStateStore, Quota, RateLimiter};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use std::net::{IpAddr, Ipv6Addr};
Expand All @@ -26,7 +22,16 @@ fn normalize_ip(ip: IpAddr) -> IpAddr {
IpAddr::V6(v6) => {
let segments = v6.segments();
// Keep first 4 segments (64 bits), zero out the rest
let masked = Ipv6Addr::new(segments[0], segments[1], segments[2], segments[3], 0, 0, 0, 0);
let masked = Ipv6Addr::new(
segments[0],
segments[1],
segments[2],
segments[3],
0,
0,
0,
0,
);
IpAddr::V6(masked)
}
}
Expand Down Expand Up @@ -188,7 +193,10 @@ mod tests {
// Use unique IP to avoid interference from other tests
let ip: IpAddr = "10.0.0.1".parse().unwrap();

// Exhaust default limit of 10 on global limiter
// Set up a known limit first (don't depend on config defaults)
reload(Some(10));

// Exhaust limit of 10
for _ in 0..10 {
assert!(check(ip));
}
Expand Down Expand Up @@ -220,6 +228,12 @@ mod tests {
fn test_limit_of_one() {
// Edge case: minimum limit of 1
let limiter = test_limiter(1);
let ip: IpAddr = "192.168.100.2".parse().unwrap();

assert!(check_with_limiter(&limiter, ip));
assert!(!check_with_limiter(&limiter, ip));
}

#[test]
fn test_unlimited_mode_allows_all() {
// Temporarily set global limiter to None and ensure check() always returns true
Expand All @@ -231,11 +245,6 @@ mod tests {
// Restore to default reasonable limit for other tests
reload(Some(10));
}
let ip: IpAddr = "192.168.100.2".parse().unwrap();

assert!(check_with_limiter(&limiter, ip));
assert!(!check_with_limiter(&limiter, ip));
}

// Note: Time-based recovery test is not included because:
// 1. Governor crate uses DefaultClock (wall-clock time), not Tokio's time
Expand All @@ -244,4 +253,4 @@ mod tests {
//
// The rate limiting behavior is well-tested by the governor crate itself.
// Our tests verify correct integration with the library.
}
}
Loading