hive-priv: never leave an agent dir or the agents root root-owned on a fresh host
This commit is contained in:
parent
52cdfe84a0
commit
24c5c02942
2 changed files with 78 additions and 5 deletions
|
|
@ -472,7 +472,11 @@ pub enum PrivRequest {
|
||||||
/// hive-priv derives the path from `agent_name` (never passed over the
|
/// hive-priv derives the path from `agent_name` (never passed over the
|
||||||
/// wire), validates the name, then:
|
/// wire), validates the name, then:
|
||||||
/// - path already exists (dir or subvol) → no-op (progressive: existing
|
/// - path already exists (dir or subvol) → no-op (progressive: existing
|
||||||
/// agents are left exactly as they are, never auto-migrated);
|
/// agents are left exactly as they are, never auto-migrated), except
|
||||||
|
/// that a root-owned dir under a non-root `AGENT_STATE_ROOT` is chowned
|
||||||
|
/// to the root's owner, since hive-c0re cannot write below it;
|
||||||
|
/// - `AGENT_STATE_ROOT` itself missing → created and chowned to its
|
||||||
|
/// parent's owner, never left root-owned;
|
||||||
/// - parent FS is not btrfs → no-op (hive-c0re's normal `create_dir_all`
|
/// - parent FS is not btrfs → no-op (hive-c0re's normal `create_dir_all`
|
||||||
/// makes a plain directory, the pre-subvolume behaviour);
|
/// makes a plain directory, the pre-subvolume behaviour);
|
||||||
/// - else → `btrfs subvolume create <path>` and chown it to the owner of
|
/// - else → `btrfs subvolume create <path>` and chown it to the owner of
|
||||||
|
|
|
||||||
|
|
@ -244,17 +244,31 @@ async fn ensure_agent_subvolume(
|
||||||
let root = env.state_root;
|
let root = env.state_root;
|
||||||
let agent_root = root.join(agent_name);
|
let agent_root = root.join(agent_name);
|
||||||
|
|
||||||
// Progressive: existing agents (plain dir OR already a subvol) are left
|
// Progressive: existing agents (plain dir OR already a subvol) are never
|
||||||
// untouched — never auto-migrated.
|
// auto-migrated. The one exception is an agent dir this op itself left
|
||||||
|
// root-owned on a fresh host: hive-c0re cannot create below it.
|
||||||
if agent_root.exists() {
|
if agent_root.exists() {
|
||||||
|
if let (Ok(agent), Ok(root_meta)) = (
|
||||||
|
std::fs::symlink_metadata(&agent_root),
|
||||||
|
std::fs::metadata(root),
|
||||||
|
) && is_stranded_root_dir(agent.uid(), root_meta.uid())
|
||||||
|
{
|
||||||
|
std::os::unix::fs::lchown(&agent_root, Some(root_meta.uid()), Some(root_meta.gid()))
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"chown stranded {} to agents-root owner",
|
||||||
|
agent_root.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
tracing::warn!(agent = %agent_name, path = %agent_root.display(), "handed a root-owned agent dir back to the agents-root owner");
|
||||||
|
}
|
||||||
return Ok((String::new(), String::new()));
|
return Ok((String::new(), String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only btrfs supports subvolumes; on anything else hive-c0re's normal
|
// Only btrfs supports subvolumes; on anything else hive-c0re's normal
|
||||||
// `create_dir_all` makes a plain dir (the pre-subvolume behaviour). The
|
// `create_dir_all` makes a plain dir (the pre-subvolume behaviour). The
|
||||||
// parent must exist for both statfs and `btrfs subvolume create`.
|
// parent must exist for both statfs and `btrfs subvolume create`.
|
||||||
std::fs::create_dir_all(root)
|
ensure_agents_root(root)?;
|
||||||
.with_context(|| format!("create agents root {}", root.display()))?;
|
|
||||||
if !env.probes.on_btrfs(root)? {
|
if !env.probes.on_btrfs(root)? {
|
||||||
return Ok((String::new(), String::new()));
|
return Ok((String::new(), String::new()));
|
||||||
}
|
}
|
||||||
|
|
@ -306,6 +320,30 @@ async fn ensure_agent_subvolume(
|
||||||
Ok((String::new(), String::new()))
|
Ok((String::new(), String::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make the agents root if it is missing. Made by root here, so it is handed
|
||||||
|
/// to the owner of its parent (hive-core's state dir); the subvolume takes the
|
||||||
|
/// root's owner, and a root-owned root would strand every agent dir under it.
|
||||||
|
/// One made meanwhile by the tmpfiles rule is left as that rule made it.
|
||||||
|
fn ensure_agents_root(root: &Path) -> Result<()> {
|
||||||
|
use std::os::unix::fs::MetadataExt as _;
|
||||||
|
|
||||||
|
match std::fs::create_dir(root) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => return Ok(()),
|
||||||
|
Err(e) => return Err(e).with_context(|| format!("create agents root {}", root.display())),
|
||||||
|
}
|
||||||
|
let parent = std::fs::metadata(root.parent().unwrap_or(root))
|
||||||
|
.with_context(|| format!("stat parent of {}", root.display()))?;
|
||||||
|
std::os::unix::fs::chown(root, Some(parent.uid()), Some(parent.gid()))
|
||||||
|
.with_context(|| format!("chown agents root {} to its parent's owner", root.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An agent dir owned by root under an agents root that is not: hive-c0re
|
||||||
|
/// (hive-core) cannot create `claude/` or `state/` in it.
|
||||||
|
fn is_stranded_root_dir(agent_uid: u32, root_uid: u32) -> bool {
|
||||||
|
agent_uid == 0 && root_uid != 0
|
||||||
|
}
|
||||||
|
|
||||||
/// `DeleteAgentSubvolume` — delete the agent's state root iff it's a btrfs
|
/// `DeleteAgentSubvolume` — delete the agent's state root iff it's a btrfs
|
||||||
/// subvolume (purge path only). No-op for plain dirs / missing paths;
|
/// subvolume (purge path only). No-op for plain dirs / missing paths;
|
||||||
/// hive-c0re's own `remove_dir_all` covers those. See the wire doc.
|
/// hive-c0re's own `remove_dir_all` covers those. See the wire doc.
|
||||||
|
|
@ -1506,6 +1544,37 @@ esac"#;
|
||||||
assert!(!fx.agent("iris").exists());
|
assert!(!fx.agent("iris").exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_agents_root_is_made_once_and_a_racing_creator_is_not_an_error() {
|
||||||
|
use super::ensure_agents_root;
|
||||||
|
use std::os::unix::fs::MetadataExt as _;
|
||||||
|
let fx = Fx::new();
|
||||||
|
let root = fx.state.join("made");
|
||||||
|
ensure_agents_root(&root).unwrap();
|
||||||
|
let (dir, parent) = (
|
||||||
|
std::fs::metadata(&root).unwrap(),
|
||||||
|
std::fs::metadata(&fx.state).unwrap(),
|
||||||
|
);
|
||||||
|
assert_eq!((dir.uid(), dir.gid()), (parent.uid(), parent.gid()));
|
||||||
|
ensure_agents_root(&root).unwrap();
|
||||||
|
assert!(ensure_agents_root(&root.join("no/parent")).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_a_root_owned_dir_under_a_non_root_root_is_stranded() {
|
||||||
|
use super::is_stranded_root_dir;
|
||||||
|
assert!(is_stranded_root_dir(0, 998));
|
||||||
|
assert!(
|
||||||
|
!is_stranded_root_dir(0, 0),
|
||||||
|
"a root-run host is not stranded"
|
||||||
|
);
|
||||||
|
assert!(!is_stranded_root_dir(998, 998));
|
||||||
|
assert!(
|
||||||
|
!is_stranded_root_dir(1000, 998),
|
||||||
|
"never touch an agent-owned dir"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ensure_creates_the_subvolume_owned_like_the_root() {
|
async fn ensure_creates_the_subvolume_owned_like_the_root() {
|
||||||
use std::os::unix::fs::MetadataExt as _;
|
use std::os::unix::fs::MetadataExt as _;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue