hivectl: collapse infra allowlist + restart/control ops onto SIBLING_CONTAINERS
Per review: RESTARTABLE_INFRA_CONTAINERS and the new CONTROLLABLE_INFRA_CONTAINERS
were near-identical subsets of SIBLING_CONTAINERS. Drop both and validate infra
lifecycle ops against SIBLING_CONTAINERS directly (all four infra containers;
hive-c0re is never in it, so it can't stop itself). This also makes hive-matrix
restartable, including via an infra_admin agent's restart tool.
Collapse the two priv ops too: RestartInfraContainer is gone; ControlInfraContainer
{ action } is the single op (restart = action: Restart). priv_client's
restart_infra_container is now a thin wrapper over control_infra_container.
This commit is contained in:
parent
c673dce73d
commit
0df9e40940
4 changed files with 46 additions and 109 deletions
|
|
@ -518,10 +518,10 @@ async fn handle_start_child(coord: &Arc<Coordinator>, agent: &str, name: &str) -
|
||||||
async fn handle_restart_child(coord: &Arc<Coordinator>, agent: &str, name: &str) -> AgentResponse {
|
async fn handle_restart_child(coord: &Arc<Coordinator>, agent: &str, name: &str) -> AgentResponse {
|
||||||
// Infra-container restart: an agent holding the `infra_admin`
|
// Infra-container restart: an agent holding the `infra_admin`
|
||||||
// capability can restart a hive infrastructure container (hive-ci /
|
// capability can restart a hive infrastructure container (hive-ci /
|
||||||
// hive-gateway / hive-forge) by passing its name to the same restart
|
// hive-gateway / hive-forge / hive-matrix) by passing its name to the
|
||||||
// tool. These names are never agent children, so this branch is
|
// same restart tool. These names are never agent children, so this
|
||||||
// disjoint from the child-restart path below.
|
// branch is disjoint from the child-restart path below.
|
||||||
if hive_sh4re::priv_proto::RESTARTABLE_INFRA_CONTAINERS.contains(&name) {
|
if hive_sh4re::priv_proto::SIBLING_CONTAINERS.contains(&name) {
|
||||||
return handle_restart_infra(coord, agent, name).await;
|
return handle_restart_infra(coord, agent, name).await;
|
||||||
}
|
}
|
||||||
if let Some(err) = require_child(agent, name, "restart") {
|
if let Some(err) = require_child(agent, name, "restart") {
|
||||||
|
|
@ -541,7 +541,7 @@ async fn handle_restart_child(coord: &Arc<Coordinator>, agent: &str, name: &str)
|
||||||
|
|
||||||
/// Restart a hive infrastructure container on behalf of an agent that
|
/// Restart a hive infrastructure container on behalf of an agent that
|
||||||
/// holds the `infra_admin` capability. The container name is already
|
/// holds the `infra_admin` capability. The container name is already
|
||||||
/// known to be in `RESTARTABLE_INFRA_CONTAINERS`; this gates on the
|
/// known to be in `SIBLING_CONTAINERS`; this gates on the
|
||||||
/// capability and routes the systemctl restart through hive-priv (which
|
/// capability and routes the systemctl restart through hive-priv (which
|
||||||
/// re-validates the name root-side). Direct, not approval-gated.
|
/// re-validates the name root-side). Direct, not approval-gated.
|
||||||
async fn handle_restart_infra(
|
async fn handle_restart_infra(
|
||||||
|
|
|
||||||
|
|
@ -283,24 +283,20 @@ pub async fn restart_matrix_daemon(agent_name: &str) -> Result<()> {
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restart a hive infrastructure container (hive-ci / hive-gateway /
|
/// Restart a hive infrastructure container on the host (thin wrapper over
|
||||||
/// hive-forge) on the host via `systemctl restart
|
/// [`control_infra_container`] with `action = Restart`). hive-priv
|
||||||
/// container@<container>.service`. hive-priv re-validates `container`
|
/// re-validates `container` against its root-side allowlist; callers must
|
||||||
/// against its root-side allowlist; callers must already have checked
|
/// already have checked the requesting agent holds the `infra_admin`
|
||||||
/// the requesting agent holds the `infra_admin` capability.
|
/// capability.
|
||||||
pub async fn restart_infra_container(container: &str) -> Result<()> {
|
pub async fn restart_infra_container(container: &str) -> Result<()> {
|
||||||
ok(call(&PrivRequest::RestartInfraContainer {
|
control_infra_container(container, InfraAction::Restart).await
|
||||||
container: container.to_owned(),
|
|
||||||
})
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start / stop / restart a hive infrastructure container (`hive-ci`,
|
/// Start / stop / restart a hive infrastructure container (`hive-ci`,
|
||||||
/// `hive-gateway`, `hive-forge`, `hive-matrix`) on the host via `systemctl
|
/// `hive-gateway`, `hive-forge`, `hive-matrix`) on the host via `systemctl
|
||||||
/// <action> container@<container>.service`. hive-priv re-validates
|
/// <action> container@<container>.service`. hive-priv re-validates
|
||||||
/// `container` against its root-side allowlist
|
/// `container` against its root-side allowlist (`SIBLING_CONTAINERS`). Used
|
||||||
/// (`CONTROLLABLE_INFRA_CONTAINERS`). Used by the hive-wide `hivectl stop` /
|
/// by the hive-wide `hivectl stop` / `hivectl start` flow.
|
||||||
/// `hivectl start` flow.
|
|
||||||
pub async fn control_infra_container(container: &str, action: InfraAction) -> Result<()> {
|
pub async fn control_infra_container(container: &str, action: InfraAction) -> Result<()> {
|
||||||
ok(call(&PrivRequest::ControlInfraContainer {
|
ok(call(&PrivRequest::ControlInfraContainer {
|
||||||
container: container.to_owned(),
|
container: container.to_owned(),
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use hive_sh4re::priv_proto::{
|
use hive_sh4re::priv_proto::{
|
||||||
AGENT_PREFIX, AGENT_STATE_ROOT, BindMount, CONTROLLABLE_INFRA_CONTAINERS, InfraAction,
|
AGENT_PREFIX, AGENT_STATE_ROOT, BindMount, InfraAction, JournalQuery, MANAGER_NAME, META_DIR,
|
||||||
JournalQuery, MANAGER_NAME, META_DIR, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest,
|
NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream, PrivStreamLine,
|
||||||
PrivResponse, PrivStream, PrivStreamLine, RESTARTABLE_INFRA_CONTAINERS, SIBLING_CONTAINERS,
|
SIBLING_CONTAINERS,
|
||||||
};
|
};
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::net::unix::OwnedWriteHalf;
|
use tokio::net::unix::OwnedWriteHalf;
|
||||||
|
|
@ -265,10 +265,6 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
||||||
restart_matrix_daemon(agent_name).await
|
restart_matrix_daemon(agent_name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivRequest::RestartInfraContainer { ref container } => {
|
|
||||||
restart_infra_container(container).await
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivRequest::ControlInfraContainer {
|
PrivRequest::ControlInfraContainer {
|
||||||
ref container,
|
ref container,
|
||||||
action,
|
action,
|
||||||
|
|
@ -399,43 +395,15 @@ async fn restart_matrix_daemon(agent_name: &str) -> Result<(String, String)> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `RestartInfraContainer` — restart a hive infrastructure container on
|
|
||||||
/// the host via `systemctl restart container@<container>.service`. The
|
|
||||||
/// `container` is validated against `RESTARTABLE_INFRA_CONTAINERS` here,
|
|
||||||
/// root-side, so this is the authoritative allowlist even though
|
|
||||||
/// hive-c0re also gates on the caller's `infra_admin` capability.
|
|
||||||
async fn restart_infra_container(container: &str) -> Result<(String, String)> {
|
|
||||||
if !RESTARTABLE_INFRA_CONTAINERS.contains(&container) {
|
|
||||||
bail!("container {container:?} is not a restartable hive infra container");
|
|
||||||
}
|
|
||||||
let unit = format!("container@{container}.service");
|
|
||||||
let out = Command::new("systemctl")
|
|
||||||
.args(["restart", &unit])
|
|
||||||
.output()
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("systemctl restart {unit}"))?;
|
|
||||||
if !out.status.success() {
|
|
||||||
bail!(
|
|
||||||
"systemctl restart {unit} exited {}: {}",
|
|
||||||
out.status,
|
|
||||||
String::from_utf8_lossy(&out.stderr).trim()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
tracing::info!(target: "infra-restart", "restarted {unit}");
|
|
||||||
Ok((
|
|
||||||
String::from_utf8_lossy(&out.stdout).into_owned(),
|
|
||||||
String::from_utf8_lossy(&out.stderr).into_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ControlInfraContainer` — start/stop/restart a hive infrastructure
|
/// `ControlInfraContainer` — start/stop/restart a hive infrastructure
|
||||||
/// container via `systemctl <verb> container@<container>.service`. The
|
/// container via `systemctl <verb> container@<container>.service`. The
|
||||||
/// `container` is validated against `CONTROLLABLE_INFRA_CONTAINERS` here,
|
/// `container` is validated against `SIBLING_CONTAINERS` here, root-side;
|
||||||
/// root-side; this is the authoritative allowlist (hive-c0re itself can
|
/// this is the authoritative allowlist (hive-c0re is never in it, so a
|
||||||
/// never appear in it, so a hive-wide stop can't sever the daemon socket
|
/// stop can't sever the daemon socket the request arrived on). Serves both
|
||||||
/// the request arrived on).
|
/// the hive-wide `hivectl stop`/`start` flow and an `infra_admin` agent's
|
||||||
|
/// `restart` (action = Restart).
|
||||||
async fn control_infra_container(container: &str, action: InfraAction) -> Result<(String, String)> {
|
async fn control_infra_container(container: &str, action: InfraAction) -> Result<(String, String)> {
|
||||||
if !CONTROLLABLE_INFRA_CONTAINERS.contains(&container) {
|
if !SIBLING_CONTAINERS.contains(&container) {
|
||||||
bail!("container {container:?} is not a controllable hive infra container");
|
bail!("container {container:?} is not a controllable hive infra container");
|
||||||
}
|
}
|
||||||
let verb = action.systemctl_verb();
|
let verb = action.systemctl_verb();
|
||||||
|
|
|
||||||
|
|
@ -15,28 +15,16 @@ pub const MANAGER_NAME: &str = "ruth";
|
||||||
/// Sub-agent container prefix. System container name = `h-<agent_name>`.
|
/// Sub-agent container prefix. System container name = `h-<agent_name>`.
|
||||||
pub const AGENT_PREFIX: &str = "h-";
|
pub const AGENT_PREFIX: &str = "h-";
|
||||||
|
|
||||||
/// Sibling service containers managed by hive-c0re.
|
/// Sibling service containers managed by hive-c0re. This doubles as the
|
||||||
|
/// authoritative allowlist for infra lifecycle ops
|
||||||
|
/// ([`PrivRequest::ControlInfraContainer`]): any of these four may be
|
||||||
|
/// started / stopped / restarted (by the hive-wide `hivectl stop`/`start`
|
||||||
|
/// flow or an `infra_admin` agent's `restart`). `hive-c0re` is deliberately
|
||||||
|
/// absent — stopping it would sever the very socket the request arrived on.
|
||||||
|
/// hive-priv re-validates against this list root-side, so it's authoritative
|
||||||
|
/// regardless of what the caller sends.
|
||||||
pub const SIBLING_CONTAINERS: &[&str] = &["hive-forge", "hive-matrix", "hive-gateway", "hive-ci"];
|
pub const SIBLING_CONTAINERS: &[&str] = &["hive-forge", "hive-matrix", "hive-gateway", "hive-ci"];
|
||||||
|
|
||||||
/// Infra containers an agent holding the `infra_admin` capability may
|
|
||||||
/// restart via the `restart` MCP tool. A deliberate subset of
|
|
||||||
/// [`SIBLING_CONTAINERS`]: hive-matrix is excluded (kicking the matrix
|
|
||||||
/// backend mid-sync is its own concern) and hive-c0re is excluded
|
|
||||||
/// entirely (a self-restart would sever the very socket the request
|
|
||||||
/// arrived on). hive-priv re-validates against this list root-side, so
|
|
||||||
/// it is the authoritative allowlist regardless of what the caller sends.
|
|
||||||
pub const RESTARTABLE_INFRA_CONTAINERS: &[&str] = &["hive-ci", "hive-gateway", "hive-forge"];
|
|
||||||
|
|
||||||
/// Infra containers hive-c0re may stop/start/restart hive-wide for the
|
|
||||||
/// `hivectl stop` / `hivectl start` operator flow. Superset of
|
|
||||||
/// [`RESTARTABLE_INFRA_CONTAINERS`]: it adds `hive-matrix`, because a full
|
|
||||||
/// stop is a deliberate operator action (unlike the disruptive mid-sync
|
|
||||||
/// *restart* the `infra_admin` MCP path forbids). `hive-c0re` is still
|
|
||||||
/// excluded — it runs the daemon servicing the request and must never stop
|
|
||||||
/// itself. hive-priv re-validates against this list root-side.
|
|
||||||
pub const CONTROLLABLE_INFRA_CONTAINERS: &[&str] =
|
|
||||||
&["hive-ci", "hive-gateway", "hive-forge", "hive-matrix"];
|
|
||||||
|
|
||||||
/// Lifecycle verb for [`PrivRequest::ControlInfraContainer`]. Maps directly
|
/// Lifecycle verb for [`PrivRequest::ControlInfraContainer`]. Maps directly
|
||||||
/// to `systemctl <verb> container@<container>.service`.
|
/// to `systemctl <verb> container@<container>.service`.
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
|
@ -336,24 +324,15 @@ pub enum PrivRequest {
|
||||||
agent_name: String,
|
agent_name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Restart a hive infrastructure container on the host via
|
/// Start / stop / restart a hive infrastructure container on the host
|
||||||
/// `systemctl restart container@<container>.service`. hive-priv
|
/// via `systemctl <action> container@<container>.service`. hive-priv
|
||||||
/// validates `container` against [`RESTARTABLE_INFRA_CONTAINERS`]
|
/// validates `container` against [`SIBLING_CONTAINERS`] root-side (the
|
||||||
/// before acting — the root-side allowlist is authoritative. Used
|
/// authoritative allowlist; `hive-c0re` is never in it). Serves both the
|
||||||
/// by hive-c0re to service a `restart` request from an agent that
|
/// hive-wide `hivectl stop` / `hivectl start` flow and an `infra_admin`
|
||||||
/// holds the `infra_admin` capability.
|
/// agent's `restart` (with `action = Restart`).
|
||||||
RestartInfraContainer {
|
|
||||||
/// Infra container name (e.g. `hive-ci`); must be in
|
|
||||||
/// [`RESTARTABLE_INFRA_CONTAINERS`].
|
|
||||||
container: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Start/stop/restart a hive infrastructure container on the host via
|
|
||||||
/// `systemctl <action> container@<container>.service`. hive-priv
|
|
||||||
/// validates `container` against [`CONTROLLABLE_INFRA_CONTAINERS`]
|
|
||||||
/// root-side. Generalises [`PrivRequest::RestartInfraContainer`] for the
|
|
||||||
/// hive-wide `hivectl stop` / `hivectl start` operator flow.
|
|
||||||
ControlInfraContainer {
|
ControlInfraContainer {
|
||||||
|
/// Infra container name (e.g. `hive-ci`); must be in
|
||||||
|
/// [`SIBLING_CONTAINERS`].
|
||||||
container: String,
|
container: String,
|
||||||
action: InfraAction,
|
action: InfraAction,
|
||||||
},
|
},
|
||||||
|
|
@ -414,21 +393,15 @@ pub enum PrivEvent {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{RESTARTABLE_INFRA_CONTAINERS, SIBLING_CONTAINERS};
|
use super::SIBLING_CONTAINERS;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn restartable_infra_is_a_safe_subset_of_siblings() {
|
fn infra_control_allowlist_excludes_c0re_includes_matrix() {
|
||||||
// Every restartable infra container must be a known sibling.
|
// SIBLING_CONTAINERS is the authoritative allowlist for infra
|
||||||
for c in RESTARTABLE_INFRA_CONTAINERS {
|
// lifecycle ops. hive-c0re must NEVER be in it — stopping the daemon
|
||||||
assert!(
|
// would sever the socket the request arrived on.
|
||||||
SIBLING_CONTAINERS.contains(c),
|
assert!(!SIBLING_CONTAINERS.contains(&"hive-c0re"));
|
||||||
"{c} is not a managed sibling container"
|
// hive-matrix IS controllable (operator can stop/start/restart it).
|
||||||
);
|
assert!(SIBLING_CONTAINERS.contains(&"hive-matrix"));
|
||||||
}
|
|
||||||
// hive-matrix and hive-c0re are deliberately excluded: kicking the
|
|
||||||
// matrix backend mid-sync is its own concern, and a self-restart of
|
|
||||||
// c0re would sever the request socket.
|
|
||||||
assert!(!RESTARTABLE_INFRA_CONTAINERS.contains(&"hive-matrix"));
|
|
||||||
assert!(!RESTARTABLE_INFRA_CONTAINERS.contains(&"hive-c0re"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue