Skip to main content

tuwunel_service/migrations/
fix_hashed_sentinel_passwords.rs

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