From fc104d02a4797f7e38493da53f5913b0d7a262d9 Mon Sep 17 00:00:00 2001 From: Vladislav Grechannik Date: Sat, 10 Jan 2026 03:16:00 +0100 Subject: [PATCH] Add an option to read SSO client secret from a file --- src/core/config/check.rs | 29 +++++++++++++++++++++++++++++ src/core/config/mod.rs | 25 ++++++++++++++++++++++++- src/service/oauth/mod.rs | 8 ++++++-- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/core/config/check.rs b/src/core/config/check.rs index 7df6173f..fe0a6a75 100644 --- a/src/core/config/check.rs +++ b/src/core/config/check.rs @@ -288,6 +288,35 @@ pub fn check(config: &Config) -> Result { )); } + for (i, provider) in config.identity_provider.iter().enumerate() { + if provider.client_secret.is_some() { + continue; + } + + let Some(secret_path) = &provider.client_secret_file else { + return Err!(Config( + "client_secret", + "Either client secret or a client secret file must be set on identity provider \ + №{i}." + )); + }; + + let Ok(secret) = std::fs::read_to_string(secret_path) else { + return Err!(Config( + "client_secret_file", + "Client secret file was specified but failed to be read at identity provider \ + №{i}" + )); + }; + + if secret.is_empty() { + return Err!(Config( + "client_secret_file", + "Client secret file was specified but is empty on identity provider №{i}" + )); + } + } + Ok(()) } diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index c210d55c..3eb9e555 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -2517,7 +2517,15 @@ pub struct IdentityProvider { /// Secret key the provider generated for you along with the `client_id` /// above. Unlike the `client_id`, the `client_secret` can be changed here /// whenever the provider regenerates one for you. - pub client_secret: String, + pub client_secret: Option, + + /// Secret key to use that's read from the file path specified. + /// + /// This takes priority over "client_secret" first, and falls back to + /// "client_secret" if invalid or failed to open. + /// + /// example: "/etc/tuwunel/.client_secret" + pub client_secret_file: Option, /// The callback URL configured when registering the OAuth application with /// the provider. Tuwunel's callback URL must be strictly formatted exactly @@ -2613,6 +2621,21 @@ pub struct IdentityProvider { impl IdentityProvider { #[must_use] pub fn id(&self) -> &str { self.client_id.as_str() } + + pub async fn get_client_secret(&self) -> Result { + if let Some(client_secret) = &self.client_secret { + return Ok(client_secret.clone()); + } + + futures::future::OptionFuture::from( + self.client_secret_file + .as_ref() + .map(tokio::fs::read_to_string), + ) + .await + .transpose()? + .ok_or_else(|| err!("No client secret or client secret file configured")) + } } impl Hash for IdentityProvider { diff --git a/src/service/oauth/mod.rs b/src/service/oauth/mod.rs index 0313ea4e..b69f85ac 100644 --- a/src/service/oauth/mod.rs +++ b/src/service/oauth/mod.rs @@ -97,9 +97,11 @@ pub async fn revoke_token(&self, (provider, session): (&Provider, &Session)) -> client_secret: &'a str, } + let client_secret = provider.get_client_secret().await?; + let query = RevokeQuery { client_id: &provider.client_id, - client_secret: &provider.client_secret, + client_secret: &client_secret, }; let url = provider @@ -130,9 +132,11 @@ pub async fn request_token( redirect_uri: Option<&'a str>, } + let client_secret = provider.get_client_secret().await?; + let query = TokenQuery { client_id: &provider.client_id, - client_secret: &provider.client_secret, + client_secret: &client_secret, grant_type: "authorization_code", code, code_verifier: session.code_verifier.as_deref(),