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:
parent
5da7f6cd3a
commit
9e12012a95
7 changed files with 279 additions and 65 deletions
|
|
@ -21,8 +21,8 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use hive_sh4re::priv_proto::{
|
||||
AGENT_PREFIX, BindMount, MANAGER_NAME, META_DIR, PRIV_SOCK, PrivRequest, PrivResponse,
|
||||
SIBLING_CONTAINERS,
|
||||
AGENT_PREFIX, BindMount, JournalOutput, MANAGER_NAME, META_DIR, PRIV_SOCK, PrivRequest,
|
||||
PrivResponse, SIBLING_CONTAINERS,
|
||||
};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
|
|
@ -186,6 +186,24 @@ async fn exec(req: PrivRequest) -> Result<(String, String)> {
|
|||
|
||||
PrivRequest::ListContainers => container_run(&["list"]).await,
|
||||
|
||||
PrivRequest::ReadContainerJournal {
|
||||
ref container,
|
||||
lines,
|
||||
boot,
|
||||
output,
|
||||
ref unit,
|
||||
ref priority,
|
||||
ref grep,
|
||||
ref since,
|
||||
ref until,
|
||||
} => {
|
||||
validate_container_system_name(container)?;
|
||||
read_container_journal(
|
||||
container, lines, boot, output, unit, priority, grep, since, until,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
PrivRequest::WriteNspawnFlags {
|
||||
ref container,
|
||||
ref binds,
|
||||
|
|
@ -313,6 +331,71 @@ async fn container_run(args: &[&str]) -> Result<(String, String)> {
|
|||
Ok((stdout, stderr))
|
||||
}
|
||||
|
||||
/// Read a container's journal as root via `journalctl -M`. Returns
|
||||
/// `(stdout, stderr)`. Unlike `container_run` a non-zero exit is *not* a
|
||||
/// hard error — journalctl's own diagnostic (folded into `stderr` with
|
||||
/// the exit status) is what the caller surfaces to the operator, so the
|
||||
/// helper never bails.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn read_container_journal(
|
||||
container: &str,
|
||||
lines: u32,
|
||||
boot: bool,
|
||||
output: JournalOutput,
|
||||
unit: &Option<String>,
|
||||
priority: &Option<String>,
|
||||
grep: &Option<String>,
|
||||
since: &Option<String>,
|
||||
until: &Option<String>,
|
||||
) -> Result<(String, String)> {
|
||||
let mut args: Vec<String> = vec![
|
||||
"-M".to_owned(),
|
||||
container.to_owned(),
|
||||
"--no-pager".to_owned(),
|
||||
format!("--output={}", output.as_journalctl()),
|
||||
"-n".to_owned(),
|
||||
lines.to_string(),
|
||||
];
|
||||
if boot {
|
||||
args.push("-b".to_owned());
|
||||
}
|
||||
if let Some(u) = unit {
|
||||
args.push("-u".to_owned());
|
||||
args.push(u.clone());
|
||||
}
|
||||
if let Some(p) = priority {
|
||||
args.push("-p".to_owned());
|
||||
args.push(p.clone());
|
||||
}
|
||||
// `--grep=`/`--since=`/`--until=` use the `=`-joined form so a value
|
||||
// can never be parsed as a separate journalctl flag.
|
||||
if let Some(g) = grep {
|
||||
args.push(format!("--grep={g}"));
|
||||
}
|
||||
if let Some(s) = since {
|
||||
args.push(format!("--since={s}"));
|
||||
}
|
||||
if let Some(u) = until {
|
||||
args.push(format!("--until={u}"));
|
||||
}
|
||||
let out = Command::new("journalctl")
|
||||
.args(&args)
|
||||
.output()
|
||||
.await
|
||||
.context("invoke journalctl -M")?;
|
||||
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
|
||||
let stderr = if out.status.success() {
|
||||
String::from_utf8_lossy(&out.stderr).into_owned()
|
||||
} else {
|
||||
format!(
|
||||
"journalctl -M {container} exited {}: {}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr).trim()
|
||||
)
|
||||
};
|
||||
Ok((stdout, stderr))
|
||||
}
|
||||
|
||||
/// Return the system container name for a logical agent name.
|
||||
/// All agents (including the manager) use the `h-` prefix.
|
||||
fn container_system_name(name: &str) -> String {
|
||||
|
|
|
|||
Loading…
Reference in a new issue