Skip to content

Commit e598f9c

Browse files
authored
adjust rate limiter config (#2787)
* rate limit only API routes * make default rate limit more permissive * avoid rate limiting in tests * also add rate limiter to sse routes
1 parent c3f9b9f commit e598f9c

4 files changed

Lines changed: 70 additions & 48 deletions

File tree

crates/defguard_common/src/config.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,12 @@ pub struct DefGuardConfig {
195195

196196
/// Maximum number of requests per second per client IP before rate limiting kicks in.
197197
/// Set to 0 to disable rate limiting.
198-
#[arg(long, env = "DEFGUARD_RATELIMIT_PERSECOND", default_value_t = 10)]
198+
#[arg(long, env = "DEFGUARD_RATELIMIT_PERSECOND", default_value_t = 100)]
199199
pub rate_limit_per_second: u64,
200200

201201
/// Maximum burst size for the rate limiter (token bucket capacity per client IP).
202202
/// Set to 0 to disable rate limiting.
203-
#[arg(long, env = "DEFGUARD_RATELIMIT_BURST", default_value_t = 100)]
203+
#[arg(long, env = "DEFGUARD_RATELIMIT_BURST", default_value_t = 1000)]
204204
pub rate_limit_burst: u32,
205205
}
206206

@@ -302,8 +302,8 @@ impl DefGuardConfig {
302302
grpc_bind_address: None,
303303
adopt_gateway: None,
304304
adopt_edge: None,
305-
rate_limit_per_second: 10,
306-
rate_limit_burst: 100,
305+
rate_limit_per_second: 0,
306+
rate_limit_burst: 0,
307307
};
308308

309309
config

crates/defguard_core/src/lib.rs

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ pub fn build_webapp(
261261
incompatible_components: Arc<RwLock<IncompatibleComponents>>,
262262
proxy_control_tx: tokio::sync::mpsc::Sender<ProxyControlMessage>,
263263
tls_active: Arc<AtomicBool>,
264+
server_config: &DefGuardConfig,
264265
) -> Router {
265266
let webapp: Router<AppState> = Router::new()
266267
.route("/", get(index))
@@ -270,7 +271,8 @@ pub fn build_webapp(
270271
.route("/svg/{*path}", get(svg))
271272
.fallback_service(get(handle_404));
272273

273-
let webapp = webapp.nest(
274+
// Collect all API routes into a single router so the rate-limiter can be scoped to API routes only
275+
let api_router: Router<AppState> = Router::new().nest(
274276
"/api/v1",
275277
Router::new()
276278
.route("/health", get(health_check))
@@ -430,7 +432,7 @@ pub fn build_webapp(
430432
);
431433

432434
// Enterprise features
433-
let webapp = webapp.nest(
435+
let api_router = api_router.nest(
434436
"/api/v1/openid",
435437
Router::new()
436438
.route(
@@ -448,15 +450,15 @@ pub fn build_webapp(
448450
.route("/auth_info", get(get_auth_info)),
449451
);
450452

451-
let webapp = webapp.nest(
453+
let api_router = api_router.nest(
452454
"/api/v1",
453455
Router::new()
454456
.route("/enterprise_info", get(check_enterprise_info))
455457
.route("/test_directory_sync", get(test_dirsync_connection)),
456458
);
457459

458460
// activity log stream
459-
let webapp = webapp.nest(
461+
let api_router = api_router.nest(
460462
"/api/v1/activity_log_stream",
461463
Router::new()
462464
.route(
@@ -469,7 +471,7 @@ pub fn build_webapp(
469471
),
470472
);
471473

472-
let webapp = webapp
474+
let api_router = api_router
473475
.nest(
474476
"/api/v1/oauth",
475477
Router::new()
@@ -491,7 +493,7 @@ pub fn build_webapp(
491493
get(openid_configuration),
492494
);
493495

494-
let webapp = webapp.nest(
496+
let api_router = api_router.nest(
495497
"/api/v1/acl",
496498
Router::new()
497499
.route("/rule", get(list_acl_rules).post(create_acl_rule))
@@ -526,7 +528,7 @@ pub fn build_webapp(
526528
.route("/destination/apply", put(apply_acl_destinations)),
527529
);
528530

529-
let webapp = webapp.nest(
531+
let api_router = api_router.nest(
530532
"/api/v1",
531533
Router::new()
532534
// FIXME: Conflict; change /device/{device_id} to /device/{username}.
@@ -623,7 +625,7 @@ pub fn build_webapp(
623625
.route("/license/check", post(license_check)),
624626
);
625627

626-
let webapp = webapp.nest(
628+
let api_router = api_router.nest(
627629
"/api/v1/worker",
628630
Router::new()
629631
.route("/job", post(create_job))
@@ -633,6 +635,49 @@ pub fn build_webapp(
633635
.layer(Extension(worker_state)),
634636
);
635637

638+
// Setup rate limiter
639+
debug!(
640+
"Configuring rate limiter, per_second: {}, burst: {}",
641+
server_config.rate_limit_per_second, server_config.rate_limit_burst
642+
);
643+
let governor_config = GovernorConfigBuilder::default()
644+
.key_extractor(SmartIpKeyExtractor)
645+
.per_second(server_config.rate_limit_per_second)
646+
.burst_size(server_config.rate_limit_burst)
647+
.finish();
648+
let governor_config = if let Some(conf) = governor_config {
649+
let governor_limiter = conf.limiter().clone();
650+
spawn(async move {
651+
loop {
652+
sleep(RATE_LIMITER_CLEANUP_PERIOD).await;
653+
debug!(
654+
"Cleaning-up rate limiter storage, current size: {}",
655+
governor_limiter.len()
656+
);
657+
governor_limiter.retain_recent();
658+
}
659+
});
660+
info!(
661+
"Rate limiter configured: {} req/s per IP, burst {}",
662+
server_config.rate_limit_per_second, server_config.rate_limit_burst
663+
);
664+
Some(Arc::new(conf))
665+
} else {
666+
info!("Rate limiting disabled (per_second or burst is 0)");
667+
None
668+
};
669+
670+
// Apply rate-limiter to API routes only, leaving static asset routes unaffected.
671+
// Use Arc::clone so the same underlying limiter is shared with the SSE routes below.
672+
let api_router = if let Some(ref conf) = governor_config {
673+
api_router.layer(GovernorLayer::new(Arc::clone(conf)))
674+
} else {
675+
api_router
676+
};
677+
678+
// Merge rate-limited API routes into the static-assets webapp.
679+
let webapp = webapp.merge(api_router);
680+
636681
// SSE routes are long-lived connections; they must not be wrapped by the
637682
// request timeout. They are merged in after TimeoutLayer is applied to the
638683
// main router so that they bypass the timeout while still receiving all
@@ -647,6 +692,11 @@ pub fn build_webapp(
647692
get(setup_gateway_tls_stream),
648693
),
649694
);
695+
let sse_routes = if let Some(conf) = governor_config {
696+
sse_routes.layer(GovernorLayer::new(conf))
697+
} else {
698+
sse_routes
699+
};
650700

651701
let app_state = AppState::new(
652702
pool.clone(),
@@ -735,7 +785,9 @@ pub async fn run_web_server(
735785

736786
let tls_active = Arc::new(AtomicBool::new(false));
737787

738-
let mut webapp = build_webapp(
788+
let server_config = server_config();
789+
790+
let webapp = build_webapp(
739791
webhook_tx,
740792
webhook_rx,
741793
wireguard_tx,
@@ -748,43 +800,11 @@ pub async fn run_web_server(
748800
incompatible_components,
749801
proxy_control_tx,
750802
Arc::clone(&tls_active),
803+
server_config,
751804
);
752805
info!("Started web services");
753-
let server_config = server_config();
754-
755-
// Setup rate limiter. Both fields default to non-zero so limiting is on by default;
756-
// operators can set either env var to 0 to disable.
757-
debug!(
758-
"Configuring rate limiter, per_second: {}, burst: {}",
759-
server_config.rate_limit_per_second, server_config.rate_limit_burst
760-
);
761-
let governor_conf = GovernorConfigBuilder::default()
762-
.key_extractor(SmartIpKeyExtractor)
763-
.per_second(server_config.rate_limit_per_second)
764-
.burst_size(server_config.rate_limit_burst)
765-
.finish();
766-
if let Some(conf) = governor_conf {
767-
let governor_limiter = conf.limiter().clone();
768-
spawn(async move {
769-
loop {
770-
sleep(RATE_LIMITER_CLEANUP_PERIOD).await;
771-
debug!(
772-
"Cleaning-up rate limiter storage, current size: {}",
773-
governor_limiter.len()
774-
);
775-
governor_limiter.retain_recent();
776-
}
777-
});
778-
info!(
779-
"Rate limiter configured: {} req/s per IP, burst {}",
780-
server_config.rate_limit_per_second, server_config.rate_limit_burst
781-
);
782-
webapp = webapp.layer(GovernorLayer::new(conf));
783-
} else {
784-
info!("Rate limiting disabled (per_second or burst is 0)");
785-
}
786806

787-
webapp = apply_security_layers(webapp, Arc::clone(&tls_active));
807+
let webapp = apply_security_layers(webapp, Arc::clone(&tls_active));
788808

789809
let addr = SocketAddr::new(
790810
server_config

crates/defguard_core/tests/integration/api/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ pub(crate) async fn make_base_client(
153153
Arc::default(),
154154
proxy_control_tx,
155155
Arc::clone(&tls_active),
156+
&config,
156157
);
157158
let webapp = apply_security_layers(webapp, tls_active);
158159

crates/defguard_core/tests/integration/api/proxy_certs.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ async fn make_test_client_with_proxy_rx(
9999
.await
100100
.expect("Could not bind ephemeral socket");
101101
let port = listener.local_addr().unwrap().port();
102-
let _config = init_config(Some(&format!("http://localhost:{port}")), &pool).await;
102+
let config = init_config(Some(&format!("http://localhost:{port}")), &pool).await;
103103
initialize_users(&pool).await;
104104
initialize_current_settings(&pool)
105105
.await
@@ -150,6 +150,7 @@ async fn make_test_client_with_proxy_rx(
150150
Arc::default(),
151151
proxy_control_tx,
152152
Arc::new(AtomicBool::new(false)),
153+
&config,
153154
);
154155

155156
let client = TestClient::new(webapp, listener, api_event_rx);

0 commit comments

Comments
 (0)