@@ -2,7 +2,7 @@ use std::{
22 collections:: HashMap ,
33 net:: { IpAddr , Ipv4Addr , SocketAddr } ,
44 path:: Path ,
5- sync:: { atomic:: Ordering , Arc } ,
5+ sync:: { atomic:: Ordering , Arc , LazyLock , RwLock } ,
66 time:: Duration ,
77} ;
88
@@ -12,6 +12,7 @@ use axum::{
1212 extract:: { ConnectInfo , FromRef , State } ,
1313 http:: { header:: HeaderValue , Request , Response , StatusCode } ,
1414 middleware:: { self , Next } ,
15+ response:: IntoResponse ,
1516 routing:: { get, post} ,
1617 serve, Json , Router ,
1718} ;
@@ -21,7 +22,7 @@ use defguard_version::{server::DefguardVersionLayer, Version};
2122use serde:: Serialize ;
2223use tokio:: {
2324 net:: TcpListener ,
24- sync:: { mpsc, oneshot} ,
25+ sync:: { mpsc, oneshot, Mutex } ,
2526 task:: JoinSet ,
2627} ;
2728use tower_governor:: {
@@ -57,7 +58,7 @@ pub(crate) struct AppState {
5758 pub ( crate ) grpc_server : ProxyServer ,
5859 pub ( crate ) remote_mfa_sessions :
5960 Arc < tokio:: sync:: Mutex < HashMap < String , oneshot:: Sender < String > > > > ,
60- key : Key ,
61+ cookie_key : Arc < RwLock < Option < Key > > > ,
6162 url : Url ,
6263}
6364
@@ -79,7 +80,10 @@ impl AppState {
7980
8081impl FromRef < AppState > for Key {
8182 fn from_ref ( state : & AppState ) -> Self {
82- state. key . clone ( )
83+ let maybe_key = state. cookie_key . read ( ) . unwrap ( ) . clone ( ) ;
84+ // We return the dummy key only to satisfy the `FromRef` trait, but it is never
85+ // used in practice because of the `ensure_configured` middleware.
86+ maybe_key. unwrap_or_else ( || Key :: from ( & [ 0 ; 64 ] ) )
8387 }
8488}
8589
@@ -206,18 +210,45 @@ pub async fn run_setup(
206210 Ok ( configuration)
207211}
208212
213+ /// Middleware that gates all HTTP endpoints except health checks until the proxy
214+ /// is fully configured.
215+ ///
216+ /// The proxy cannot safely handle requests that rely on encrypted cookies
217+ /// (e.g. OpenID / MFA flows) until it receives the cookie encryption key from
218+ /// the core. This key is provided asynchronously after the core connects.
219+ ///
220+ /// Until the key is available, only health check endpoints are served and all
221+ /// other requests return HTTP 503 (Service Unavailable). Once the key is set,
222+ /// the middleware becomes a no-op and all routes are enabled.
223+ async fn ensure_configured (
224+ State ( state) : State < AppState > ,
225+ request : Request < Body > ,
226+ next : Next ,
227+ ) -> Response < Body > {
228+ // Allow healthchecks even before core connects and gives us the cookie key.
229+ let path = request. uri ( ) . path ( ) ;
230+ if matches ! ( path, "/api/v1/health" | "/api/v1/health-grpc" ) {
231+ return next. run ( request) . await ;
232+ }
233+
234+ // Block all other requests until cookie key is configured.
235+ if state. cookie_key . read ( ) . unwrap ( ) . is_none ( ) {
236+ return StatusCode :: SERVICE_UNAVAILABLE . into_response ( ) ;
237+ }
238+
239+ next. run ( request) . await
240+ }
241+
209242pub async fn run_server ( env_config : EnvConfig , config : Configuration ) -> anyhow:: Result < ( ) > {
210243 info ! ( "Starting Defguard Proxy server" ) ;
211244 debug ! ( "Using config: {env_config:?}" ) ;
212245
213246 let mut tasks = JoinSet :: new ( ) ;
214-
215- // Prepare the channel for gRPC -> http server communication.
216- // The channel sends private cookies key once core connects to gRPC.
217- let ( tx, mut rx) = mpsc:: unbounded_channel :: < Key > ( ) ;
247+ let cookie_key = Default :: default ( ) ;
218248
219249 // connect to upstream gRPC server
220- let grpc_server = ProxyServer :: new ( tx) ;
250+ let grpc_server = ProxyServer :: new ( Arc :: clone ( & cookie_key) ) ;
251+
221252 let server_clone = grpc_server. clone ( ) ;
222253 grpc_server. configure ( config) ;
223254
@@ -241,15 +272,10 @@ pub async fn run_server(env_config: EnvConfig, config: Configuration) -> anyhow:
241272 }
242273 } ) ;
243274
244- // Wait for core to connect to gRPC and send private cookies encryption key.
245- let Some ( key) = rx. recv ( ) . await else {
246- return Err ( anyhow:: Error :: msg ( "http channel closed" ) ) ;
247- } ;
248-
249275 // build application
250276 debug ! ( "Setting up API server" ) ;
251277 let shared_state = AppState {
252- key ,
278+ cookie_key ,
253279 grpc_server,
254280 remote_mfa_sessions : Arc :: new ( tokio:: sync:: Mutex :: new ( HashMap :: new ( ) ) ) ,
255281 url : env_config. url . clone ( ) ,
@@ -309,6 +335,10 @@ pub async fn run_server(env_config: EnvConfig, config: Configuration) -> anyhow:
309335 . route ( "/info" , get ( app_info) ) ,
310336 )
311337 . fallback_service ( get ( handle_404) )
338+ . layer ( middleware:: from_fn_with_state (
339+ shared_state. clone ( ) ,
340+ ensure_configured,
341+ ) )
312342 . layer ( middleware:: map_response ( powered_by_header) )
313343 . layer ( middleware:: from_fn_with_state (
314344 shared_state. clone ( ) ,
0 commit comments