@@ -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
0 commit comments