diff --git a/Cargo.lock b/Cargo.lock index 7f3db2d..983a2c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,6 +500,7 @@ dependencies = [ "hyper", "ldap3", "lru_time_cache", + "rand", "regex", "rust-ini", "secrecy", @@ -736,6 +737,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.50" @@ -754,6 +761,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.16" diff --git a/Cargo.toml b/Cargo.toml index 440abdc..e1f6528 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,9 @@ systemd-units = { enable = false } name = "ldap_authz_proxy" path = "src/main.rs" +[profile.release] +lto = true + [dependencies] anyhow = "1.0.68" async-recursion = "1.0.2" @@ -46,6 +49,7 @@ docopt = "1.1.1" hyper = { version = "0.14.23", features = ["full"] } ldap3 = "0.11.1" lru_time_cache = "0.11.11" +rand = "0.8.5" regex = "1.7.1" rust-ini = "0.18.0" secrecy = "0.8.0" diff --git a/src/main.rs b/src/main.rs index de2840a..a0da13b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,13 @@ use std::process::exit; use std::str::FromStr; use std::sync::atomic::AtomicU64; use std::sync::{Arc}; +use std::time::Duration; use config::ConfigSection; use hyper::header::HeaderName; use hyper::http::HeaderValue; use hyper::service::service_fn; use hyper::{Request, Response, Body, StatusCode}; +use rand::random; use tokio::net::TcpListener; use docopt::Docopt; @@ -133,7 +135,7 @@ async fn ldap_query( async fn do_query(conf: &ConfigSection, query: &str) -> ldap3::result::Result> { - let settings = ldap3::LdapConnSettings::new().set_conn_timeout(std::time::Duration::from_millis((conf.ldap_conn_timeout*1000.0) as u64)); + let settings = ldap3::LdapConnSettings::new().set_conn_timeout(Duration::from_millis((conf.ldap_conn_timeout*1000.0) as u64)); let (conn, mut ldap) = LdapConnAsync::with_settings(settings, conf.ldap_server_url.as_str()).await?; ldap3::drive!(conn); @@ -149,10 +151,10 @@ async fn ldap_query( &conf.ldap_attribs).await?.success() { Ok((rows, _)) => { - ldap.unbind().await?; + ldap.unbind().await.ok(); Ok(rows) }, - Err(e) => Err(e) + Err(e) => { ldap.unbind().await.ok(); Err(e) } } } @@ -166,13 +168,17 @@ async fn ldap_query( }, Err(e) => { use ldap3::LdapError::*; - if i == MAX_RETRY-1 || !matches!(e, ResultRecv{..} | Io{..} | EndOfStream{..} | Timeout{..}) { - // Last attempt or non-temporary error - return Err(e); + if i < MAX_RETRY-1 && matches!(e, ResultRecv{..} | Io{..} | EndOfStream{..} | Timeout{..}) { + let wait_time = 1.0 + (i as f32) * 0.5 * random::(); + span.in_scope(|| { tracing::warn!("Temporary LDAP error: {:?}. Retry ({}/{}) in {wait_time} second.", e, i+1, MAX_RETRY, wait_time=wait_time); }); + tokio::time::sleep(Duration::from_secs_f32(wait_time)).await; } else { - let wait_time = 1.0 + (i as f32) * 0.5; - span.in_scope(|| { tracing::info!("Temporary LDAP error: {:?}. Retrying in {wait_time} second.", e); }); - tokio::time::sleep(std::time::Duration::from_secs_f32(wait_time)).await; + if i == MAX_RETRY-1 { + span.in_scope(|| { tracing::error!("Max retries ({MAX_RETRY}) exhausted, LDAP error persists: {:?}", e); }); + } else { + span.in_scope(|| { tracing::error!("Non-temporary LDAP error, giving up: {:?}", e); }); + } + return Err(e); } } }; @@ -220,7 +226,12 @@ async fn ldap_query( .filter(|s| !seen.contains(*s)) .map(|s| { span.in_scope(|| { tracing::debug!("Recursing into [{}] (rule {:?})", s, conf.sub_query_join); }); - tokio::spawn(ldap_query(req_id, s.clone(), username.clone(), confs.clone(), cache.clone(), seen_sections.clone())) + let (s, username, confs, cache, seen_sections) = (s.clone(), username.clone(), confs.clone(), cache.clone(), seen_sections.clone()); + tokio::spawn(async move { + // Random delay to avoid thundering herd + tokio::time::sleep(Duration::from_secs_f32(random::() * 0.05)).await; + ldap_query(req_id, s, username, confs, cache, seen_sections).await + }) }).collect::>() }; @@ -277,6 +288,7 @@ async fn http_handler(req: Request, ctx: Arc) -> Result, ctx: Arc) -> Result { @@ -329,7 +344,7 @@ async fn http_handler(req: Request, ctx: Arc) -> Result { let span = span.record("cached", &la.cached); if let Some(ldap_res) = la.ldap_res { - span.in_scope(|| { tracing::info!("User authorized Ok"); }); + span.in_scope(|| { tracing::info!("Authorized Ok"); }); let mut resp = Response::new(Body::from("200 OK - LDAP result found")); // Store LDAP result attributes to response HTTP headers @@ -350,12 +365,13 @@ async fn http_handler(req: Request, ctx: Arc) -> Result Result<(), Box> let mut caches = HashMap::new(); for sect in conf.as_ref() { tracing::debug!("CONFIG DUMP: {:?}", sect); - let ttl = ::std::time::Duration::from_secs_f32(sect.cache_time); + let ttl = Duration::from_secs_f32(sect.cache_time); let cache = LdapCache::with_expiry_duration_and_capacity(ttl, sect.cache_size); caches.insert(sect.section.clone(), Arc::new(Mutex::new(cache))); }