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