Skip to content

Commit a9d266e

Browse files
Multiproxy private cookies (#229)
* http server waits for private cookies secret before launching * private cookies key error handling * handle premature http chanel closure
1 parent abe4691 commit a9d266e

File tree

2 files changed

+51
-13
lines changed

2 files changed

+51
-13
lines changed

src/grpc.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{
88
},
99
};
1010

11+
use axum_extra::extract::cookie::Key;
1112
use defguard_version::{
1213
get_tracing_variables,
1314
server::{grpc::DefguardVersionInterceptor, DefguardVersionLayer},
@@ -31,6 +32,7 @@ use crate::{
3132

3233
// connected clients
3334
type ClientMap = HashMap<SocketAddr, mpsc::UnboundedSender<Result<CoreRequest, Status>>>;
35+
static COOKIE_KEY_HEADER: &str = "dg-cookie-key-bin";
3436

3537
#[derive(Debug, Clone, Default)]
3638
pub(crate) struct Configuration {
@@ -42,6 +44,7 @@ pub(crate) struct ProxyServer {
4244
current_id: Arc<AtomicU64>,
4345
clients: Arc<Mutex<ClientMap>>,
4446
results: Arc<Mutex<HashMap<u64, oneshot::Sender<core_response::Payload>>>>,
47+
http_channel: mpsc::UnboundedSender<Key>,
4548
pub(crate) connected: Arc<AtomicBool>,
4649
pub(crate) core_version: Arc<Mutex<Option<Version>>>,
4750
config: Arc<Mutex<Option<Configuration>>>,
@@ -51,8 +54,9 @@ pub(crate) struct ProxyServer {
5154
impl ProxyServer {
5255
#[must_use]
5356
/// Create new `ProxyServer`.
54-
pub(crate) fn new() -> Self {
57+
pub(crate) fn new(http_channel: mpsc::UnboundedSender<Key>) -> Self {
5558
Self {
59+
http_channel,
5660
current_id: Arc::new(AtomicU64::new(1)),
5761
clients: Arc::new(Mutex::new(HashMap::new())),
5862
results: Arc::new(Mutex::new(HashMap::new())),
@@ -189,6 +193,7 @@ impl Clone for ProxyServer {
189193
results: Arc::clone(&self.results),
190194
connected: Arc::clone(&self.connected),
191195
core_version: Arc::clone(&self.core_version),
196+
http_channel: self.http_channel.clone(),
192197
config: Arc::clone(&self.config),
193198
setup_in_progress: Arc::clone(&self.setup_in_progress),
194199
}
@@ -228,6 +233,30 @@ impl proxy_server::Proxy for ProxyServer {
228233

229234
info!("Defguard Core gRPC client connected from: {address}");
230235

236+
// Retrieve private cookies key from the header.
237+
let cookie_key = request.metadata().get_bin(COOKIE_KEY_HEADER);
238+
let key = match cookie_key {
239+
Some(key) => Key::from(&key.to_bytes().map_err(|err| {
240+
error!("Failed to decode private cookie key: {err:?}");
241+
Status::internal("Failed to decode private cookie key")
242+
})?),
243+
// If the header is missing, fall back to generating a local key.
244+
// This preserves compatibility with older Core versions that did not
245+
// provide a shared cookie key. In this mode, cookie-based sessions will
246+
// not be shared across proxy instances and HA won't work.
247+
None => {
248+
warn!(
249+
"Private cookie key not provided by Core; falling back to a locally generated key. \
250+
This typically indicates an older Core version and disables cookie sharing across proxies."
251+
);
252+
Key::generate()
253+
}
254+
};
255+
self.http_channel.send(key).map_err(|err| {
256+
error!("Failed to send private cookies key to HTTP server: {err:?}");
257+
Status::internal("Failed to send private cookies key to HTTP server")
258+
})?;
259+
231260
let (tx, rx) = mpsc::unbounded_channel();
232261
self.clients
233262
.lock()

src/http.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use defguard_version::{server::DefguardVersionLayer, Version};
2222
use serde::Serialize;
2323
use tokio::{
2424
net::TcpListener,
25-
sync::{oneshot, Mutex},
25+
sync::{mpsc, oneshot, Mutex},
2626
task::JoinSet,
2727
};
2828
use tower_governor::{
@@ -178,18 +178,13 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
178178
debug!("Using config: {config:?}");
179179

180180
let mut tasks = JoinSet::new();
181-
// connect to upstream gRPC server
182-
let grpc_server = ProxyServer::new();
183181

184-
// build application
185-
debug!("Setting up API server");
186-
let shared_state = AppState {
187-
grpc_server: grpc_server.clone(),
188-
remote_mfa_sessions: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
189-
// Generate secret key for encrypting cookies.
190-
key: Key::generate(),
191-
url: config.url.clone(),
192-
};
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>();
185+
186+
// connect to upstream gRPC server
187+
let grpc_server = ProxyServer::new(tx);
193188

194189
let server_clone = grpc_server.clone();
195190

@@ -261,6 +256,20 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
261256
}
262257
});
263258

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+
264+
// build application
265+
debug!("Setting up API server");
266+
let shared_state = AppState {
267+
key,
268+
grpc_server,
269+
remote_mfa_sessions: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
270+
url: config.url.clone(),
271+
};
272+
264273
// Setup tower_governor rate-limiter
265274
debug!(
266275
"Configuring rate limiter, per_second: {}, burst: {}",

0 commit comments

Comments
 (0)