tuwunel_service/migrations/
fix_hashed_sentinel_passwords.rs1use tuwunel_core::{
2 Result, debug, info,
3 utils::{ReadyExt, hash::verify_password, stream::TryExpect},
4 warn,
5};
6
7use crate::Services;
8
9pub(super) async fn fix_hashed_sentinel_passwords(services: &Services) -> Result {
10 const PASSWORD_SENTINEL: &str = "*";
11
12 if services.config.identity_provider.is_empty() {
13 debug!("Skipping sentinel password migration since no SSO IdP configured.");
14 return Ok(());
15 }
16
17 let db = &services.db;
18 let cork = db.cork_and_sync();
19 let userid_password = db["userid_password"].clone();
20
21 warn!(sentinel = %PASSWORD_SENTINEL, "Fixing occurrences of hashed sentinel passwords");
22
23 let (checked, good, bad) = userid_password
24 .stream()
25 .expect_ok()
26 .ready_fold(
27 (0, 0, 0),
28 |(mut checked, mut good, mut bad): (usize, usize, usize),
29 (key, val): (&str, &str)| {
30 let good_sentinel = val == PASSWORD_SENTINEL;
31 let bad_sentinel = !val.is_empty()
32 && !good_sentinel
33 && verify_password(PASSWORD_SENTINEL, val).is_ok();
34
35 checked = checked.saturating_add(usize::from(true));
36 good = good.saturating_add(usize::from(good_sentinel));
37 bad = bad.saturating_add(usize::from(bad_sentinel));
38
39 if bad_sentinel {
40 userid_password.insert(key, PASSWORD_SENTINEL);
41 }
42
43 (checked, good, bad)
44 },
45 )
46 .await;
47
48 drop(cork);
49 info!(?checked, ?good, ?bad, "Fixed any occurrences of hashed sentinel passwords");
50
51 db["global"].insert(b"fix_hashed_sentinel_passwords", []);
52 userid_password.sort()
53}