From 1edfe36724c0f86e7c07028450aeac2523cdf3ab Mon Sep 17 00:00:00 2001 From: spetz Date: Wed, 18 Mar 2026 21:11:04 +0100 Subject: [PATCH 1/3] feat(security): generate random JWT secrets when not configured --- core/server/config.toml | 6 ++++-- core/server/src/http/http_server.rs | 29 ++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/core/server/config.toml b/core/server/config.toml index 03d0135f85..b3be68e580 100644 --- a/core/server/config.toml +++ b/core/server/config.toml @@ -114,10 +114,12 @@ clock_skew = "5 s" not_before = "0 s" # Secret key for encoding JWTs. -encoding_secret = "top_secret$iggy123$_jwt_HS256_key#!" +# If left empty, a secure random secret will be generated on each server start. +encoding_secret = "" # Secret key for decoding JWTs. -decoding_secret = "top_secret$iggy123$_jwt_HS256_key#!" +# If left empty, a secure random secret will be generated on each server start. +decoding_secret = "" # Indicates if the secret key is base64 encoded. # `true` means the secret is base64 encoded. diff --git a/core/server/src/http/http_server.rs b/core/server/src/http/http_server.rs index 4e71f3fe1d..3ab781e13f 100644 --- a/core/server/src/http/http_server.rs +++ b/core/server/src/http/http_server.rs @@ -30,6 +30,7 @@ use crate::shard::task_registry::ShutdownToken; use crate::shard::tasks::periodic::spawn_jwt_token_cleaner; use crate::shard::transmission::event::ShardEvent; use crate::streaming::persistence::persister::PersisterKind; +use crate::streaming::utils::crypto; use axum::extract::DefaultBodyLimit; use axum::extract::connect_info::Connected; use axum::http::Method; @@ -45,7 +46,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; use tower_http::cors::{AllowOrigin, CorsLayer}; -use tracing::{error, info}; +use tracing::{error, info, warn}; #[derive(Debug, Clone, Copy)] pub struct CompioSocketAddr(pub SocketAddr); @@ -268,12 +269,30 @@ async fn build_app_state( tokens_path = shard.config.system.get_state_tokens_path(); } - let jwt_manager = JwtManager::from_config(persister, &tokens_path, &config.jwt); - if let Err(e) = jwt_manager { - panic!("Failed to initialize JWT manager: {e}"); + let mut jwt_config = config.jwt.clone(); + let encoding_empty = jwt_config.encoding_secret.is_empty(); + let decoding_empty = jwt_config.decoding_secret.is_empty(); + if encoding_empty || decoding_empty { + let secret = crypto::generate_secret(32..64); + let redacted = secret.chars().take(3).collect::(); + if encoding_empty { + jwt_config.encoding_secret = secret.clone(); + warn!( + "JWT encoding secret is not configured - generated a random secret: {redacted}***. JWT tokens will be invalidated on server restart. Set 'encoding_secret' in the config to use a persistent secret." + ); + } + if decoding_empty { + jwt_config.decoding_secret = secret; + warn!( + "JWT decoding secret is not configured - generated a random secret: {redacted}***. JWT tokens will be invalidated on server restart. Set 'decoding_secret' in the config to use a persistent secret." + ); + } } - let jwt_manager = jwt_manager.unwrap(); + let jwt_manager = match JwtManager::from_config(persister, &tokens_path, &jwt_config) { + Ok(manager) => manager, + Err(error) => panic!("Failed to initialize JWT manager: {error}"), + }; if jwt_manager.load_revoked_tokens().await.is_err() { panic!("Failed to load revoked access tokens"); } From a6b4f4d4ef671d74b0f8921217b5f338f4aa3ef1 Mon Sep 17 00:00:00 2001 From: spetz Date: Fri, 20 Mar 2026 13:41:14 +0100 Subject: [PATCH 2/3] update version --- Cargo.lock | 2 +- DEPENDENCIES.md | 2 +- core/server/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5421abad92..711b658df7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9625,7 +9625,7 @@ dependencies = [ [[package]] name = "server" -version = "0.7.2-edge.1" +version = "0.7.3-edge.1" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index ca963183cd..95c211adcd 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -835,7 +835,7 @@ serde_with_macros: 3.17.0, "Apache-2.0 OR MIT", serde_yaml_ng: 0.10.0, "MIT", serial_test: 3.4.0, "MIT", serial_test_derive: 3.4.0, "MIT", -server: 0.7.2-edge.1, "Apache-2.0", +server: 0.7.3-edge.1, "Apache-2.0", sha1: 0.10.6, "Apache-2.0 OR MIT", sha2: 0.10.9, "Apache-2.0 OR MIT", sha3: 0.10.8, "Apache-2.0 OR MIT", diff --git a/core/server/Cargo.toml b/core/server/Cargo.toml index b1eb2a74f0..5138288c36 100644 --- a/core/server/Cargo.toml +++ b/core/server/Cargo.toml @@ -17,7 +17,7 @@ [package] name = "server" -version = "0.7.2-edge.1" +version = "0.7.3-edge.1" edition = "2024" license = "Apache-2.0" From 8cf616de67b1c10d1f0b6e64e52adc7b2b0ac3de Mon Sep 17 00:00:00 2001 From: spetz Date: Fri, 20 Mar 2026 14:10:44 +0100 Subject: [PATCH 3/3] Add more info warns for jwt secrets --- core/server/src/http/http_server.rs | 33 ++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/core/server/src/http/http_server.rs b/core/server/src/http/http_server.rs index 3ab781e13f..177e4f7ef7 100644 --- a/core/server/src/http/http_server.rs +++ b/core/server/src/http/http_server.rs @@ -272,20 +272,37 @@ async fn build_app_state( let mut jwt_config = config.jwt.clone(); let encoding_empty = jwt_config.encoding_secret.is_empty(); let decoding_empty = jwt_config.decoding_secret.is_empty(); - if encoding_empty || decoding_empty { - let secret = crypto::generate_secret(32..64); - let redacted = secret.chars().take(3).collect::(); - if encoding_empty { + match (encoding_empty, decoding_empty) { + (true, true) => { + let secret = crypto::generate_secret(32..64); + let redacted: String = secret.chars().take(3).collect(); + warn!( + "JWT encoding and decoding secrets are not configured - generated a random secret: {redacted}***. JWT tokens will be invalidated on server restart. Set 'encoding_secret' and 'decoding_secret' in the config to use persistent secrets." + ); jwt_config.encoding_secret = secret.clone(); + jwt_config.decoding_secret = secret; + } + (true, false) => { warn!( - "JWT encoding secret is not configured - generated a random secret: {redacted}***. JWT tokens will be invalidated on server restart. Set 'encoding_secret' in the config to use a persistent secret." + "JWT encoding secret is not configured but decoding secret is set - using decoding secret for both. Set 'encoding_secret' in the config to avoid this warning." ); + jwt_config.encoding_secret = jwt_config.decoding_secret.clone(); } - if decoding_empty { - jwt_config.decoding_secret = secret; + (false, true) => { warn!( - "JWT decoding secret is not configured - generated a random secret: {redacted}***. JWT tokens will be invalidated on server restart. Set 'decoding_secret' in the config to use a persistent secret." + "JWT decoding secret is not configured but encoding secret is set - using encoding secret for both. Set 'decoding_secret' in the config to avoid this warning." ); + jwt_config.decoding_secret = jwt_config.encoding_secret.clone(); + } + (false, false) => { + if jwt_config.encoding_secret != jwt_config.decoding_secret + && jwt_config.algorithm.starts_with("HS") + { + warn!( + "JWT encoding and decoding secrets are different but algorithm is {} (HMAC) - both secrets must be identical for symmetric algorithms.", + jwt_config.algorithm + ); + } } }