Harden overload auth scans and masking safeguards

This commit is contained in:
sabraman
2026-04-09 01:14:15 +03:00
parent 5f5a3e3fa0
commit d3b0dbd541
9 changed files with 358 additions and 44 deletions
+49 -6
View File
@@ -47,12 +47,18 @@ pub(crate) struct UserAuthEntry {
impl UserAuthSnapshot {
fn from_users(users: &HashMap<String, String>) -> Result<Self> {
// Keep runtime user ids stable across reloads so overload scans and
// sticky hints do not depend on HashMap iteration order.
let mut sorted_users: Vec<_> = users.iter().collect();
sorted_users
.sort_unstable_by(|(left, _), (right, _)| left.as_bytes().cmp(right.as_bytes()));
let mut entries = Vec::with_capacity(users.len());
let mut by_name = HashMap::with_capacity(users.len());
let mut sni_index = HashMap::with_capacity(users.len());
let mut sni_initial_index = HashMap::with_capacity(users.len());
for (user, secret_hex) in users {
for (user, secret_hex) in sorted_users {
let decoded = hex::decode(secret_hex).map_err(|_| ProxyError::InvalidSecret {
user: user.clone(),
reason: "Must be 32 hex characters".to_string(),
@@ -1724,7 +1730,7 @@ mod tests {
}
#[test]
fn proxy_protocol_trusted_cidrs_missing_uses_trust_all_but_explicit_empty_stays_empty() {
fn proxy_protocol_trusted_cidrs_missing_defaults_to_empty_and_explicit_empty_stays_empty() {
let cfg_missing: ProxyConfig = toml::from_str(
r#"
[server]
@@ -1734,10 +1740,7 @@ mod tests {
"#,
)
.unwrap();
assert_eq!(
cfg_missing.server.proxy_protocol_trusted_cidrs,
default_proxy_protocol_trusted_cidrs()
);
assert!(cfg_missing.server.proxy_protocol_trusted_cidrs.is_empty());
let cfg_explicit_empty: ProxyConfig = toml::from_str(
r#"
@@ -1758,6 +1761,46 @@ mod tests {
);
}
#[test]
fn runtime_user_auth_snapshot_order_is_stable_across_hashmap_insertion_orders() {
let mut left_users = HashMap::new();
left_users.insert(
"beta".to_string(),
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
);
left_users.insert(
"alpha".to_string(),
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
);
let mut right_users = HashMap::new();
right_users.insert(
"alpha".to_string(),
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
);
right_users.insert(
"beta".to_string(),
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
);
let left_snapshot = UserAuthSnapshot::from_users(&left_users).unwrap();
let right_snapshot = UserAuthSnapshot::from_users(&right_users).unwrap();
let left_names: Vec<_> = left_snapshot
.entries()
.iter()
.map(|entry| entry.user.as_str())
.collect();
let right_names: Vec<_> = right_snapshot
.entries()
.iter()
.map(|entry| entry.user.as_str())
.collect();
assert_eq!(left_names, ["alpha", "beta"]);
assert_eq!(left_names, right_names);
}
#[test]
fn unknown_sni_action_parses_and_defaults_to_drop() {
let cfg_default: ProxyConfig = toml::from_str(