Skip to main content

tuwunel_service/migrations/
fix_hashed_sentinel_passwords.rs

1use 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}