From 2bd8606cb636b1b183fc87aae351b978e4c62efe Mon Sep 17 00:00:00 2001 From: vcwai Date: Wed, 5 May 2021 12:38:32 +0800 Subject: [PATCH] 397 Caching password badlist (#425) --- kanidmd/src/lib/idm/authsession.rs | 147 +++++++++++++++++++---------- kanidmd/src/lib/idm/server.rs | 84 ++++++++++------- kanidmd/src/lib/server.rs | 26 +++++ 3 files changed, 176 insertions(+), 81 deletions(-) diff --git a/kanidmd/src/lib/idm/authsession.rs b/kanidmd/src/lib/idm/authsession.rs index f1a3d6cb9..8e3beaa87 100644 --- a/kanidmd/src/lib/idm/authsession.rs +++ b/kanidmd/src/lib/idm/authsession.rs @@ -2,6 +2,7 @@ use crate::idm::account::Account; use crate::idm::claim::Claim; use crate::idm::AuthState; use crate::prelude::*; +use hashbrown::HashSet; use kanidm_proto::v1::OperationError; use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech}; @@ -15,7 +16,6 @@ use crate::credential::webauthn::WebauthnDomainConfig; use std::time::Duration; use uuid::Uuid; // use webauthn_rs::proto::Credential as WebauthnCredential; -use crate::value::PartialValue; pub use std::collections::BTreeSet as Set; use webauthn_rs::proto::RequestChallengeResponse; use webauthn_rs::{AuthenticationState, Webauthn}; @@ -181,13 +181,13 @@ impl CredHandler { pw: &mut Password, who: Uuid, async_tx: &Sender, - pw_badlist_set: Option>, + pw_badlist_set: Option<&HashSet>, ) -> CredState { match cred { AuthCredential::Password(cleartext) => { if pw.verify(cleartext.as_str()).unwrap_or(false) { match pw_badlist_set { - Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => { + Some(p) if p.contains(&cleartext.to_lowercase()) => { lsecurity!( au, "Handler::Password -> Result::Denied - Password found in badlist during login" @@ -227,7 +227,7 @@ impl CredHandler { webauthn: &Webauthn, who: Uuid, async_tx: &Sender, - pw_badlist_set: Option>, + pw_badlist_set: Option<&HashSet>, ) -> CredState { match (&pw_mfa.mfa_state, &pw_mfa.pw_state) { (CredVerifyState::Init, CredVerifyState::Init) => { @@ -290,7 +290,7 @@ impl CredHandler { AuthCredential::Password(cleartext) => { if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) { match pw_badlist_set { - Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => { + Some(p) if p.contains(&cleartext.to_lowercase()) => { pw_mfa.pw_state = CredVerifyState::Fail; lsecurity!( au, @@ -404,7 +404,7 @@ impl CredHandler { who: Uuid, async_tx: &Sender, webauthn: &Webauthn, - pw_badlist_set: Option>, + pw_badlist_set: Option<&HashSet>, ) -> CredState { match self { CredHandler::Anonymous => Self::validate_anonymous(au, cred), @@ -501,8 +501,6 @@ pub(crate) struct AuthSession { // // This handler will then handle the mfa and stepping up through to generate the auth states state: AuthSessionState, - // Store the password badlist - pw_badlist_set: Option>, } impl AuthSession { @@ -512,7 +510,6 @@ impl AuthSession { _appid: &Option, webauthn: &Webauthn, ct: Duration, - pw_badlist_set: Option>, ) -> (Option, AuthState) { // During this setup, determine the credential handler that we'll be using // for this session. This is currently based on presentation of an application @@ -556,11 +553,7 @@ impl AuthSession { (None, AuthState::Denied(reason.to_string())) } else { // We can proceed - let auth_session = AuthSession { - account, - state, - pw_badlist_set, - }; + let auth_session = AuthSession { account, state }; // Get the set of mechanisms that can proceed. This is tied // to the session so that it can mutate state and have progression // of what's next, or ordering. @@ -644,6 +637,7 @@ impl AuthSession { time: &Duration, async_tx: &Sender, webauthn: &Webauthn, + pw_badlist_set: Option<&HashSet>, ) -> Result { let (next_state, response) = match &mut self.state { AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => { @@ -659,7 +653,7 @@ impl AuthSession { self.account.uuid, async_tx, webauthn, - self.pw_badlist_set.clone(), + pw_badlist_set, ) { CredState::Success(claims) => { lsecurity!(au, "Successful cred handling"); @@ -738,6 +732,7 @@ mod tests { use crate::idm::delayed::DelayedAction; use crate::idm::AuthState; use crate::prelude::*; + use hashbrown::HashSet; pub use std::collections::BTreeSet as Set; use crate::utils::duration_from_epoch_now; @@ -749,6 +744,12 @@ mod tests { use tokio::sync::mpsc::unbounded_channel as unbounded; use webauthn_authenticator_rs::{softtok::U2FSoft, WebauthnAuthenticator}; + fn create_pw_badlist_cache() -> HashSet { + let mut s = HashSet::new(); + s.insert((&"list@no3IBTyqHu$bad").to_lowercase()); + s + } + fn create_webauthn() -> Webauthn { Webauthn::new(WebauthnDomainConfig { rp_name: "example.com".to_string(), @@ -774,7 +775,6 @@ mod tests { &None, &webauthn, duration_from_epoch_now(), - Option::None, ); if let AuthState::Choose(auth_mechs) = state { @@ -822,7 +822,6 @@ mod tests { &Some("NonExistantAppID".to_string()), &webauthn, duration_from_epoch_now(), - Option::None, ); // We now ignore appids. @@ -841,15 +840,12 @@ mod tests { $account:expr, $webauthn:expr ) => {{ - let mut pw_badlist_set = Set::new(); - pw_badlist_set.insert(Value::new_iutf8("list@no3IBTyqHu$bad")); let (session, state) = AuthSession::new( $audit, $account.clone(), &None, $webauthn, duration_from_epoch_now(), - Some(pw_badlist_set), ); let mut session = session.unwrap(); @@ -879,7 +875,7 @@ mod tests { panic!("Invalid auth state") } - session + (session, create_pw_badlist_cache()) }}; } @@ -901,7 +897,8 @@ mod tests { let (async_tx, mut async_rx) = unbounded(); // now check - let mut session = start_password_session!(&mut audit, account, &webauthn); + let (mut session, pw_badlist_cache) = + start_password_session!(&mut audit, account, &webauthn); let attempt = AuthCredential::Password("bad_password".to_string()); match session.validate_creds( @@ -910,6 +907,7 @@ mod tests { &Duration::from_secs(0), &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(_)) => {} _ => panic!(), @@ -917,7 +915,8 @@ mod tests { // === Now begin a new session, and use a good pw. - let mut session = start_password_session!(&mut audit, account, &webauthn); + let (mut session, pw_badlist_cache) = + start_password_session!(&mut audit, account, &webauthn); let attempt = AuthCredential::Password("test_password".to_string()); match session.validate_creds( @@ -926,6 +925,7 @@ mod tests { &Duration::from_secs(0), &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Success(_)) => {} _ => panic!(), @@ -955,7 +955,8 @@ mod tests { let (async_tx, mut async_rx) = unbounded(); // now check, even though the password is correct, Auth should be denied since it is in badlist - let mut session = start_password_session!(&mut audit, account, &webauthn); + let (mut session, pw_badlist_cache) = + start_password_session!(&mut audit, account, &webauthn); let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string()); match session.validate_creds( @@ -964,6 +965,7 @@ mod tests { &Duration::from_secs(0), &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG), _ => panic!(), @@ -980,15 +982,12 @@ mod tests { $account:expr, $webauthn:expr ) => {{ - let mut pw_badlist_set = Set::new(); - pw_badlist_set.insert(Value::new_iutf8("list@no3IBTyqHu$bad")); let (session, state) = AuthSession::new( $audit, $account.clone(), &None, $webauthn, duration_from_epoch_now(), - Some(pw_badlist_set), ); let mut session = session.expect("Session was unable to be created."); @@ -1025,7 +1024,7 @@ mod tests { panic!("Invalid auth state") } - (session, rchal) + (session, rchal, create_pw_badlist_cache()) }}; } @@ -1070,7 +1069,8 @@ mod tests { // check send anon (fail) { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1078,6 +1078,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG), _ => panic!(), @@ -1088,7 +1089,8 @@ mod tests { // Sending a PW first is an immediate fail. { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1096,6 +1098,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG), _ => panic!(), @@ -1103,7 +1106,8 @@ mod tests { } // check send bad totp, should fail immediate { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1111,6 +1115,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG), _ => panic!(), @@ -1120,7 +1125,8 @@ mod tests { // check send good totp, should continue // then bad pw, fail pw { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1128,6 +1134,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1138,6 +1145,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG), _ => panic!(), @@ -1147,7 +1155,8 @@ mod tests { // check send good totp, should continue // then good pw, success { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1155,6 +1164,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1165,6 +1175,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Success(_)) => {} _ => panic!(), @@ -1215,7 +1226,8 @@ mod tests { // check send good totp, should continue // then badlist pw, failed { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1223,6 +1235,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1233,6 +1246,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG), _ => panic!(), @@ -1256,7 +1270,6 @@ mod tests { &None, $webauthn, duration_from_epoch_now(), - Option::None, ); let mut session = session.unwrap(); @@ -1349,6 +1362,7 @@ mod tests { &ts, &async_tx, &webauthn, + None, ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG), _ => panic!(), @@ -1369,6 +1383,7 @@ mod tests { &ts, &async_tx, &webauthn, + None, ) { Ok(AuthState::Success(_)) => {} _ => panic!(), @@ -1397,6 +1412,7 @@ mod tests { &ts, &async_tx, &webauthn, + None, ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG), _ => panic!(), @@ -1441,6 +1457,7 @@ mod tests { &ts, &async_tx, &webauthn, + None, ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG), _ => panic!(), @@ -1479,7 +1496,8 @@ mod tests { // check pw first (fail) { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1487,6 +1505,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG), _ => panic!(), @@ -1495,7 +1514,8 @@ mod tests { // Check totp first attempt fails. { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1503,6 +1523,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG), _ => panic!(), @@ -1513,8 +1534,10 @@ mod tests { // NOTE: We only check bad challenge here as bad softtoken is already // extensively tested. { - let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn); - let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (_session, inv_chal, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _chal, _) = + start_password_mfa_session!(&mut audit, account, &webauthn); let inv_chal = inv_chal.unwrap(); @@ -1529,6 +1552,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG), _ => panic!(), @@ -1537,7 +1561,8 @@ mod tests { // check good webauthn/bad pw (fail) { - let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, chal, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); let chal = chal.unwrap(); let resp = wa @@ -1550,6 +1575,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1560,6 +1586,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG), _ => panic!(), @@ -1574,7 +1601,8 @@ mod tests { // Check good webauthn/good pw (pass) { - let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, chal, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); let chal = chal.unwrap(); let resp = wa @@ -1587,6 +1615,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1597,6 +1626,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Success(_)) => {} _ => panic!(), @@ -1652,7 +1682,8 @@ mod tests { // check pw first (fail) { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1660,6 +1691,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG), _ => panic!(), @@ -1668,7 +1700,8 @@ mod tests { // Check bad totp (fail) { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1676,6 +1709,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG), _ => panic!(), @@ -1684,8 +1718,10 @@ mod tests { // check bad webauthn (fail) { - let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn); - let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (_session, inv_chal, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _chal, _) = + start_password_mfa_session!(&mut audit, account, &webauthn); let inv_chal = inv_chal.unwrap(); @@ -1700,6 +1736,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG), _ => panic!(), @@ -1708,7 +1745,8 @@ mod tests { // check good webauthn/bad pw (fail) { - let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, chal, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); let chal = chal.unwrap(); let resp = wa @@ -1721,6 +1759,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1731,6 +1770,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG), _ => panic!(), @@ -1745,7 +1785,8 @@ mod tests { // check good totp/bad pw (fail) { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1753,6 +1794,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1763,6 +1805,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG), _ => panic!(), @@ -1771,7 +1814,8 @@ mod tests { // check good totp/good pw (pass) { - let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, _, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); match session.validate_creds( &mut audit, @@ -1779,6 +1823,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1789,6 +1834,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Success(_)) => {} _ => panic!(), @@ -1797,7 +1843,8 @@ mod tests { // Check good webauthn/good pw (pass) { - let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn); + let (mut session, chal, pw_badlist_cache) = + start_password_mfa_session!(&mut audit, account, &webauthn); let chal = chal.unwrap(); let resp = wa @@ -1810,6 +1857,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), @@ -1820,6 +1868,7 @@ mod tests { &ts, &async_tx, &webauthn, + Some(&pw_badlist_cache), ) { Ok(AuthState::Success(_)) => {} _ => panic!(), diff --git a/kanidmd/src/lib/idm/server.rs b/kanidmd/src/lib/idm/server.rs index 1de26f0bb..41a9a4c29 100644 --- a/kanidmd/src/lib/idm/server.rs +++ b/kanidmd/src/lib/idm/server.rs @@ -23,6 +23,7 @@ use crate::idm::delayed::{ DelayedAction, PasswordUpgrade, UnixPasswordUpgrade, WebauthnCounterIncrement, }; +use hashbrown::HashSet; use kanidm_proto::v1::CredentialStatus; use kanidm_proto::v1::RadiusAuthToken; use kanidm_proto::v1::SetCredentialResponse; @@ -39,10 +40,16 @@ use async_std::task; use core::task::{Context, Poll}; use futures::task as futures_task; -use concread::bptree::{BptreeMap, BptreeMapWriteTxn}; -use concread::hashmap::HashMap; +use concread::{ + bptree::{BptreeMap, BptreeMapWriteTxn}, + CowCell, +}; +use concread::{ + cowcell::{CowCellReadTxn, CowCellWriteTxn}, + hashmap::HashMap, +}; use rand::prelude::*; -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use url::Url; use webauthn_rs::Webauthn; @@ -67,6 +74,7 @@ pub struct IdmServer { async_tx: Sender, // Our webauthn verifier/config webauthn: Webauthn, + pw_badlist_cache: Arc>>, } pub struct IdmServerAuthTransaction<'a> { @@ -84,6 +92,7 @@ pub struct IdmServerAuthTransaction<'a> { // For flagging eventual actions. async_tx: Sender, webauthn: &'a Webauthn, + pw_badlist_cache: CowCellReadTxn>, } pub struct IdmServerProxyReadTransaction<'a> { @@ -101,6 +110,7 @@ pub struct IdmServerProxyWriteTransaction<'a> { sid: SID, crypto_policy: &'a CryptoPolicy, webauthn: &'a Webauthn, + pw_badlist_cache: CowCellWriteTxn<'a, HashSet>, } pub struct IdmServerDelayed { @@ -125,9 +135,12 @@ impl IdmServer { let (async_tx, async_rx) = unbounded(); // Get the domain name, as the relying party id. - let rp_id = { + let (rp_id, pw_badlist_set) = { let qs_read = task::block_on(qs.read_async()); - qs_read.get_domain_name(au)? + ( + qs_read.get_domain_name(au)?, + qs_read.get_password_badlist(au)?, + ) }; // Check that it gels with our origin. @@ -170,6 +183,7 @@ impl IdmServer { crypto_policy, async_tx, webauthn, + pw_badlist_cache: Arc::new(CowCell::new(pw_badlist_set)), }, IdmServerDelayed { async_rx }, )) @@ -199,6 +213,7 @@ impl IdmServer { sid, async_tx: self.async_tx.clone(), webauthn: &self.webauthn, + pw_badlist_cache: self.pw_badlist_cache.read(), } } @@ -230,6 +245,7 @@ impl IdmServer { sid, crypto_policy: &self.crypto_policy, webauthn: &self.webauthn, + pw_badlist_cache: self.pw_badlist_cache.write(), } } @@ -369,22 +385,7 @@ impl<'a> IdmServerAuthTransaction<'a> { }; let (auth_session, state) = if is_valid { - //TODO #397: we can keep a cached map of the badlist, and pass by reference rather than by value - let badlist_entry = self - .qs_read - .internal_search_uuid(au, &UUID_SYSTEM_CONFIG) - .map_err(|e| { - ladmin_error!(au, "Failed to retrieve system configuration {:?}", e); - e - })?; - AuthSession::new( - au, - account, - &init.appid, - self.webauthn, - ct, - badlist_entry.get_ava_set("badlist_password").cloned(), - ) + AuthSession::new(au, account, &init.appid, self.webauthn, ct) } else { // it's softlocked, don't even bother. lsecurity!(au, "Account is softlocked."); @@ -514,8 +515,16 @@ impl<'a> IdmServerAuthTransaction<'a> { // Process the credentials here as required. // Basically throw them at the auth_session and see what // falls out. + let pw_badlist_cache = Some(&(*self.pw_badlist_cache)); auth_session - .validate_creds(au, &creds.cred, &ct, &self.async_tx, self.webauthn) + .validate_creds( + au, + &creds.cred, + &ct, + &self.async_tx, + self.webauthn, + pw_badlist_cache, + ) .map(|aus| { // Inspect the result: // if it was a failure, we need to inc the softlock. @@ -897,15 +906,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // check a password badlist to eliminate more content // we check the password as "lower case" to help eliminate possibilities - let lc_password = PartialValue::new_iutf8(cleartext); - let badlist_entry = self - .qs_write - .internal_search_uuid(au, &UUID_SYSTEM_CONFIG) - .map_err(|e| { - ladmin_error!(au, "Failed to retrieve system configuration {:?}", e); - e - })?; - if badlist_entry.attribute_value_pres("badlist_password", &lc_password) { + // also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase) + if (&*self.pw_badlist_cache).contains(&cleartext.to_lowercase()) { lsecurity!(au, "Password found in badlist, rejecting"); Err(OperationError::PasswordBadListed) } else { @@ -1517,16 +1519,34 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { } } - pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> { + pub fn commit(mut self, au: &mut AuditScope) -> Result<(), OperationError> { lperf_trace_segment!( au, "idm::server::IdmServerProxyWriteTransaction::commit", || { + if self + .qs_write + .get_changed_uuids() + .contains(&UUID_SYSTEM_CONFIG) + { + self.reload_password_badlist(au)?; + self.pw_badlist_cache.commit(); + }; self.mfareg_sessions.commit(); self.qs_write.commit(au) } ) } + + fn reload_password_badlist(&mut self, au: &mut AuditScope) -> Result<(), OperationError> { + match self.qs_write.get_password_badlist(au) { + Ok(badlist_entry) => { + *self.pw_badlist_cache = badlist_entry; + Ok(()) + } + Err(e) => Err(e), + } + } } // Need tests of the sessions and the auth ... diff --git a/kanidmd/src/lib/server.rs b/kanidmd/src/lib/server.rs index 8e55726e9..14f4b4597 100644 --- a/kanidmd/src/lib/server.rs +++ b/kanidmd/src/lib/server.rs @@ -684,6 +684,32 @@ pub trait QueryServerTransaction<'a> { e }) } + + // This is a helper to get password badlist. + fn get_password_badlist( + &self, + audit: &mut AuditScope, + ) -> Result, OperationError> { + self.internal_search_uuid(audit, &UUID_SYSTEM_CONFIG) + .and_then(|e| match e.get_ava_set("badlist_password") { + Some(badlist_entry) => { + let mut badlist_hashset = HashSet::with_capacity(badlist_entry.len()); + badlist_entry + .iter() + .filter_map(|e| e.as_string()) + .cloned() + .for_each(|s| { + badlist_hashset.insert(s); + }); + Ok(badlist_hashset) + } + None => Err(OperationError::InvalidEntryState), + }) + .map_err(|e| { + ladmin_error!(audit, "Failed to retrieve system configuration {:?}", e); + e + }) + } } // Actually conduct a search request