@@ -3,7 +3,7 @@ use std::{
33 fs:: read_to_string,
44 net:: { IpAddr , Ipv4Addr , SocketAddr } ,
55 path:: Path ,
6- sync:: { atomic:: Ordering , Arc , LazyLock } ,
6+ sync:: { atomic:: Ordering , Arc , LazyLock , RwLock } ,
77 time:: Duration ,
88} ;
99
@@ -13,6 +13,7 @@ use axum::{
1313 extract:: { ConnectInfo , FromRef , State } ,
1414 http:: { header:: HeaderValue , Request , Response , StatusCode } ,
1515 middleware:: { self , Next } ,
16+ response:: IntoResponse ,
1617 routing:: { get, post} ,
1718 serve, Json , Router ,
1819} ;
@@ -22,7 +23,7 @@ use defguard_version::{server::DefguardVersionLayer, Version};
2223use serde:: Serialize ;
2324use tokio:: {
2425 net:: TcpListener ,
25- sync:: { mpsc , oneshot, Mutex } ,
26+ sync:: { oneshot, Mutex } ,
2627 task:: JoinSet ,
2728} ;
2829use tower_governor:: {
@@ -63,7 +64,7 @@ pub(crate) struct AppState {
6364 pub ( crate ) grpc_server : ProxyServer ,
6465 pub ( crate ) remote_mfa_sessions :
6566 Arc < tokio:: sync:: Mutex < HashMap < String , oneshot:: Sender < String > > > > ,
66- key : Key ,
67+ cookie_key : Arc < RwLock < Option < Key > > > ,
6768 url : Url ,
6869}
6970
@@ -85,7 +86,10 @@ impl AppState {
8586
8687impl FromRef < AppState > for Key {
8788 fn from_ref ( state : & AppState ) -> Self {
88- state. key . clone ( )
89+ let maybe_key = state. cookie_key . read ( ) . unwrap ( ) . clone ( ) ;
90+ // We return the dummy key only to satisfy the `FromRef` trait, but it is never
91+ // used in practice because of the `ensure_configured` middleware.
92+ maybe_key. unwrap_or_else ( || Key :: from ( & [ 0 ; 64 ] ) )
8993 }
9094}
9195
@@ -173,18 +177,44 @@ async fn powered_by_header<B>(mut response: Response<B>) -> Response<B> {
173177 response
174178}
175179
180+ /// Middleware that gates all HTTP endpoints except health checks until the proxy
181+ /// is fully configured.
182+ ///
183+ /// The proxy cannot safely handle requests that rely on encrypted cookies
184+ /// (e.g. OpenID / MFA flows) until it receives the cookie encryption key from
185+ /// the core. This key is provided asynchronously after the core connects.
186+ ///
187+ /// Until the key is available, only health check endpoints are served and all
188+ /// other requests return HTTP 503 (Service Unavailable). Once the key is set,
189+ /// the middleware becomes a no-op and all routes are enabled.
190+ async fn ensure_configured (
191+ State ( state) : State < AppState > ,
192+ request : Request < Body > ,
193+ next : Next ,
194+ ) -> Response < Body > {
195+ // Allow healthchecks even before core connects and gives us the cookie key.
196+ let path = request. uri ( ) . path ( ) ;
197+ if matches ! ( path, "/api/v1/health" | "/api/v1/health-grpc" ) {
198+ return next. run ( request) . await ;
199+ }
200+
201+ // Block all other requests until cookie key is configured.
202+ if state. cookie_key . read ( ) . unwrap ( ) . is_none ( ) {
203+ return StatusCode :: SERVICE_UNAVAILABLE . into_response ( ) ;
204+ }
205+
206+ next. run ( request) . await
207+ }
208+
176209pub async fn run_server ( config : Config ) -> anyhow:: Result < ( ) > {
177210 info ! ( "Starting Defguard Proxy server" ) ;
178211 debug ! ( "Using config: {config:?}" ) ;
179212
180213 let mut tasks = JoinSet :: new ( ) ;
181-
182- // Prepare the channel for gRPC -> http server communication.
183- // The channel sends private cookies key once core connects to gRPC.
184- let ( tx, mut rx) = mpsc:: unbounded_channel :: < Key > ( ) ;
214+ let cookie_key = Default :: default ( ) ;
185215
186216 // connect to upstream gRPC server
187- let grpc_server = ProxyServer :: new ( tx ) ;
217+ let grpc_server = ProxyServer :: new ( Arc :: clone ( & cookie_key ) ) ;
188218
189219 let server_clone = grpc_server. clone ( ) ;
190220
@@ -256,15 +286,10 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
256286 }
257287 } ) ;
258288
259- // Wait for core to connect to gRPC and send private cookies encryption key.
260- let Some ( key) = rx. recv ( ) . await else {
261- return Err ( anyhow:: Error :: msg ( "http channel closed" ) ) ;
262- } ;
263-
264289 // build application
265290 debug ! ( "Setting up API server" ) ;
266291 let shared_state = AppState {
267- key ,
292+ cookie_key ,
268293 grpc_server,
269294 remote_mfa_sessions : Arc :: new ( tokio:: sync:: Mutex :: new ( HashMap :: new ( ) ) ) ,
270295 url : config. url . clone ( ) ,
@@ -324,6 +349,10 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
324349 . route ( "/info" , get ( app_info) ) ,
325350 )
326351 . fallback_service ( get ( handle_404) )
352+ . layer ( middleware:: from_fn_with_state (
353+ shared_state. clone ( ) ,
354+ ensure_configured,
355+ ) )
327356 . layer ( middleware:: map_response ( powered_by_header) )
328357 . layer ( middleware:: from_fn_with_state (
329358 shared_state. clone ( ) ,
0 commit comments