hive-c0re: decide matrix token presence from metadata, not by reading it
hive-priv writes an agent's matrix-token 0600 and owned by the agent, so hive-c0re, running as hive-core, cannot read it. The read-based token-present guard in ensure_user_for therefore never fired, and every matrix sweep (boot, every 30 minutes, every rebuild) re-minted each agent's token through the appservice login and restarted its hive-matrix-daemon. Decide presence with a stat instead: a non-empty regular file counts as present. hive-core can stat the file through the 0755 state dir. Closes #4665
This commit is contained in:
parent
978164dc53
commit
46d0b46670
1 changed files with 36 additions and 5 deletions
|
|
@ -132,6 +132,15 @@ fn token_path(name: &hive_types::Ident) -> PathBuf {
|
|||
Coordinator::agent_notes_dir(name).join("matrix-token")
|
||||
}
|
||||
|
||||
/// Whether an agent's token file is present: a non-empty regular file.
|
||||
/// Decided from metadata alone, because hive-priv writes the file 0600
|
||||
/// and owned by the agent, so `hive-core` cannot read it but can stat it
|
||||
/// through the 0755 state dir. A check that reads the file always
|
||||
/// fails here and makes every sweep re-mint the token.
|
||||
fn token_file_present(path: &std::path::Path) -> bool {
|
||||
std::fs::metadata(path).is_ok_and(|m| m.is_file() && m.len() > 0)
|
||||
}
|
||||
|
||||
/// Password file for the agent's matrix account. Stored OUTSIDE the
|
||||
/// purgeable `agent_state_root` tree so it survives `destroy --purge`
|
||||
/// and allows re-login recovery when the same agent name is re-spawned.
|
||||
|
|
@ -719,11 +728,7 @@ pub async fn ensure_user_for(client: &reqwest::Client, name: &str, as_token: &st
|
|||
use std::os::unix::fs::PermissionsExt;
|
||||
let agent = hive_types::Ident::parse(name)
|
||||
.map_err(|e| anyhow::anyhow!("invalid agent name {name:?}: {e}"))?;
|
||||
let path = token_path(&agent);
|
||||
if path.exists()
|
||||
&& let Ok(existing) = std::fs::read_to_string(&path)
|
||||
&& !existing.trim().is_empty()
|
||||
{
|
||||
if token_file_present(&token_path(&agent)) {
|
||||
tracing::debug!(%name, "matrix: token already present");
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -2096,4 +2101,30 @@ mod tests {
|
|||
let err = extract_access_token(&body).unwrap_err();
|
||||
assert!(err.to_string().contains("missing access_token"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_file_present_only_for_a_non_empty_regular_file() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let token = dir.path().join("matrix-token");
|
||||
assert!(!token_file_present(&token), "missing file");
|
||||
std::fs::write(&token, "").unwrap();
|
||||
assert!(!token_file_present(&token), "empty file");
|
||||
std::fs::write(&token, "tok\n").unwrap();
|
||||
assert!(token_file_present(&token), "non-empty file");
|
||||
assert!(!token_file_present(dir.path()), "directory");
|
||||
}
|
||||
|
||||
/// The sweep runs as `hive-core`, which cannot read the agent-owned
|
||||
/// 0600 token file. `chmod 000` does not stop root, so this test uses
|
||||
/// content that `read_to_string` rejects instead: the predicate must
|
||||
/// still report the file as present, which holds only if it never
|
||||
/// reads the content.
|
||||
#[test]
|
||||
fn token_file_present_does_not_read_the_content() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let token = dir.path().join("matrix-token");
|
||||
std::fs::write(&token, [0xff, 0xfe]).unwrap();
|
||||
assert!(std::fs::read_to_string(&token).is_err());
|
||||
assert!(token_file_present(&token));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue