fix(#702): route container journal reads through hive-priv

The privsep drop to the hive-core user left four journalctl -M <container>
call sites shelling out directly. -M enters the container namespace via the
machine bus, which needs root, so all container-journal reads failed with
Permission denied. Add a ReadContainerJournal verb to hive-priv and route
dashboard get_journal, manager get_logs, the rebuild-failure journal tail,
and the agent host-journal -M path through it. Host-journal reads (no -M)
stay direct via systemd-journal group membership.
This commit is contained in:
müde 2026-06-02 23:43:02 +02:00
commit 9e12012a95
7 changed files with 279 additions and 65 deletions

View file

@ -22,6 +22,29 @@ pub const SIBLING_CONTAINERS: &[&str] = &["hive-forge", "hive-matrix", "hive-gat
/// `{META_DIR}#{name}`, derived by `hive-priv` — never passed over the wire.
pub const META_DIR: &str = "/var/lib/hyperhive/meta";
/// Output format for `ReadContainerJournal`. Maps to journalctl
/// `--output=<...>`. Restricted to the two formats hive callers use so
/// the wire type can't smuggle an arbitrary `--output` value.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum JournalOutput {
/// `short` — the journalctl default (syslog-style timestamps).
#[default]
Short,
/// `short-iso` — ISO 8601 timestamps.
ShortIso,
}
impl JournalOutput {
/// The string journalctl expects after `--output=`.
pub fn as_journalctl(self) -> &'static str {
match self {
JournalOutput::Short => "short",
JournalOutput::ShortIso => "short-iso",
}
}
}
/// One bind-mount entry for `WriteNspawnFlags`.
/// hive-priv constructs `--bind=<host_path>:<container_path>` (or `--bind-ro=`)
/// and validates both paths before writing the conf file.
@ -65,6 +88,45 @@ pub enum PrivRequest {
/// `nixos-container list`
ListContainers,
// --- Container journal reads ---
/// Read a container's journal via `journalctl -M <container>`.
/// Requires root: the machine-bus transport enters the container's
/// namespace, so this can't run from the unprivileged hive-c0re
/// process. hive-priv validates `container` against the managed-
/// container allowlist, then runs journalctl and returns its output.
///
/// The filters (`unit` / `priority` / `grep` / `since` / `until`)
/// are applied within the already-authorized machine and passed to
/// journalctl as plain argument values; they can't widen access
/// beyond the validated `container`.
ReadContainerJournal {
/// System container name (`h-<agent>` or a sibling service).
container: String,
/// `-n <lines>`.
lines: u32,
/// `-b` — restrict to the current boot.
#[serde(default)]
boot: bool,
/// `--output=<...>`.
#[serde(default)]
output: JournalOutput,
/// `-u <unit>`.
#[serde(default)]
unit: Option<String>,
/// `-p <priority>`.
#[serde(default)]
priority: Option<String>,
/// `--grep=<regex>`.
#[serde(default)]
grep: Option<String>,
/// `--since=<ts>`.
#[serde(default)]
since: Option<String>,
/// `--until=<ts>`.
#[serde(default)]
until: Option<String>,
},
// --- Config file writes ---
/// Update `/etc/nixos-containers/<container>.conf`: strip network-isolation
/// vars, force `PRIVATE_NETWORK=0`, and set `EXTRA_NSPAWN_FLAGS` from the