From 2fe1b4a8b21dbb2dfed5dd5677858e4c54cf1239 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Tue, 25 Apr 2023 16:35:32 +0200 Subject: [PATCH] Fetch upstream configuration --- Cargo.lock | 277 +++++++++++++++++++++++- Cargo.toml | 3 +- src/constants.rs | 5 +- src/controllers/providers_controller.rs | 24 +- src/data/jwt_signer.rs | 7 +- src/data/mod.rs | 1 + src/data/provider_configuration.rs | 75 +++++++ 7 files changed, 384 insertions(+), 8 deletions(-) create mode 100644 src/data/provider_configuration.rs diff --git a/Cargo.lock b/Cargo.lock index 8804781..873c0f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -568,6 +568,7 @@ dependencies = [ "mime_guess", "qrcode-generator", "rand", + "reqwest", "serde", "serde_json", "serde_yaml", @@ -858,6 +859,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -1137,6 +1148,15 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fdeflate" version = "0.3.0" @@ -1196,6 +1216,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.28" @@ -1401,6 +1430,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.8.0" @@ -1428,6 +1468,43 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.56" @@ -1514,6 +1591,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -1525,6 +1611,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + [[package]] name = "is-terminal" version = "0.4.7" @@ -1762,6 +1854,24 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -1899,6 +2009,12 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "openssl-sys" version = "0.9.86" @@ -1953,7 +2069,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -2133,6 +2249,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.8.1" @@ -2150,6 +2275,43 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -2235,6 +2397,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2261,6 +2432,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -2477,6 +2671,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2576,6 +2783,16 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.7" @@ -2599,6 +2816,12 @@ dependencies = [ "ring", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -2632,6 +2855,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -2754,6 +2983,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -2791,6 +3030,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -2920,6 +3171,21 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3052,6 +3318,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "x509-parser" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index 7c2c332..8536c90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,4 +37,5 @@ url = "2.3.1" aes-gcm = { version = "0.10.1", features = ["aes"] } bincode = "1.3.3" chrono = "0.4.24" -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" +reqwest = { version = "0.11.16", features = ["json"] } \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs index 808d21b..e357dd2 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -72,8 +72,11 @@ pub const OPEN_ID_REFRESH_TOKEN_TIMEOUT: u64 = 360000; pub const WEBAUTHN_REGISTER_CHALLENGE_EXPIRE: u64 = 3600; pub const WEBAUTHN_LOGIN_CHALLENGE_EXPIRE: u64 = 3600; -/// OpenID provider login constants +/// OpenID providers login state constants pub const OIDC_STATES_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); pub const MAX_OIDC_PROVIDERS_STATES: usize = 10; pub const OIDC_PROVIDERS_STATE_LEN: usize = 40; pub const OIDC_PROVIDERS_STATE_DURATION: u64 = 60 * 15; + +/// OpenID providers configuration constants +pub const OIDC_PROVIDERS_LIFETIME: u64 = 3600; diff --git a/src/controllers/providers_controller.rs b/src/controllers/providers_controller.rs index 27551a9..50ca53b 100644 --- a/src/controllers/providers_controller.rs +++ b/src/controllers/providers_controller.rs @@ -1,13 +1,16 @@ +use std::sync::Arc; + +use actix::Addr; +use actix_web::{web, HttpResponse, Responder}; + use crate::actors::providers_states_actor; use crate::actors::providers_states_actor::{ProviderLoginState, ProvidersStatesActor}; use crate::controllers::base_controller::build_fatal_error_page; use crate::data::action_logger::{Action, ActionLogger}; use crate::data::login_redirect::LoginRedirect; use crate::data::provider::{ProviderID, ProvidersManager}; +use crate::data::provider_configuration::ProviderConfigurationHelper; use crate::data::remote_ip::RemoteIP; -use actix::Addr; -use actix_web::{web, HttpResponse, Responder}; -use std::sync::Arc; #[derive(serde::Deserialize)] pub struct StartLoginQuery { @@ -29,7 +32,7 @@ pub async fn start_login( let provider = match providers.find_by_id(&query.id) { None => { return HttpResponse::NotFound() - .body(build_fatal_error_page("Login provider not found!")) + .body(build_fatal_error_page("Login provider not found!")); } Some(p) => p, }; @@ -49,6 +52,19 @@ pub async fn start_login( state: &state.state_id, }); + // Get provider configuration + let config = match ProviderConfigurationHelper::get_configuration(&provider).await { + Ok(c) => c, + Err(e) => { + log::error!("Failed to load provider configuration! {}", e); + return HttpResponse::InternalServerError().body(build_fatal_error_page( + "Failed to load provider configuration!", + )); + } + }; + + log::debug!("Provider configuration: {:?}", config); + HttpResponse::Ok().body(state.state_id) // Redirect user diff --git a/src/data/jwt_signer.rs b/src/data/jwt_signer.rs index c7d0017..c3f009f 100644 --- a/src/data/jwt_signer.rs +++ b/src/data/jwt_signer.rs @@ -11,8 +11,10 @@ use base64::Engine as _; use crate::utils::err::Res; use crate::utils::string_utils::rand_str; +const JWK_USE_SIGN: &str = "sig"; + /// Json Web Key -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct JsonWebKey { #[serde(rename = "alg")] algorithm: String, @@ -24,6 +26,8 @@ pub struct JsonWebKey { modulus: String, #[serde(rename = "e")] public_exponent: String, + #[serde(rename = "use", skip_serializing_if = "Option::is_none")] + usage: Option, } #[derive(Debug, Clone)] @@ -44,6 +48,7 @@ impl JWTSigner { key_id: self.0.key_id().as_ref().unwrap().to_string(), public_exponent: BASE64_URL_URL_SAFE.encode(components.e), modulus: BASE64_URL_SAFE_NO_PAD.encode(components.n), + usage: Some(JWK_USE_SIGN.to_string()), } } diff --git a/src/data/mod.rs b/src/data/mod.rs index d0df343..2d3c913 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -12,6 +12,7 @@ pub mod login_redirect; pub mod open_id_user_info; pub mod openid_config; pub mod provider; +pub mod provider_configuration; pub mod remote_ip; pub mod session_identity; pub mod totp_key; diff --git a/src/data/provider_configuration.rs b/src/data/provider_configuration.rs new file mode 100644 index 0000000..c1ccd43 --- /dev/null +++ b/src/data/provider_configuration.rs @@ -0,0 +1,75 @@ +use std::cell::RefCell; +use std::collections::HashMap; + +use crate::constants::OIDC_PROVIDERS_LIFETIME; +use crate::data::jwt_signer::JsonWebKey; +use crate::data::provider::Provider; +use crate::utils::err::Res; +use crate::utils::time::time; + +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ProviderDiscovery { + pub issuer: String, + pub authorization_endpoint: String, + pub token_endpoint: String, + pub userinfo_endpoint: Option, + pub jwks_uri: String, + pub claims_supported: Option>, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ProviderJWKs { + pub keys: Vec, +} + +/// Provider configuration +#[derive(Debug, Clone)] +pub struct ProviderConfiguration { + pub discovery: ProviderDiscovery, + pub keys: ProviderJWKs, + pub expire: u64, +} + +thread_local! { + static THREAD_CACHE: RefCell> = RefCell::new(Default::default()); +} + +pub struct ProviderConfigurationHelper {} + +impl ProviderConfigurationHelper { + /// Get or refresh the configuration for a provider + pub async fn get_configuration(provider: &Provider) -> Res { + let config = THREAD_CACHE.with(|i| i.borrow().get(&provider.configuration_url).cloned()); + + // Refresh config cache if needed + if config.is_none() || config.as_ref().unwrap().expire < time() { + let conf = Self::fetch_configuration(provider).await?; + + THREAD_CACHE.with(|i| { + i.borrow_mut() + .insert(provider.configuration_url.clone(), conf.clone()) + }); + + return Ok(conf); + } + + // We can return immediately previously extracted value + Ok(config.unwrap()) + } + + /// Get fresh configuration from provider + async fn fetch_configuration(provider: &Provider) -> Res { + let discovery: ProviderDiscovery = reqwest::get(&provider.configuration_url) + .await? + .json() + .await?; + + let keys: ProviderJWKs = reqwest::get(&discovery.jwks_uri).await?.json().await?; + + Ok(ProviderConfiguration { + discovery, + keys, + expire: time() + OIDC_PROVIDERS_LIFETIME, + }) + } +}