swarm: let every hive read every agent's credential, and say so
A hive reads its agents' credentials with its own certificate, and nothing said which paths that certificate may read, so the read half of a delivery answered 403. The grant is wide on purpose. An agent's path does not name the hive hosting it -- agents move -- so a per-hive grant has to be an enumeration the controller re-emits whenever the roster changes, and an enumeration that can drift or land out of order advertises a boundary it does not hold. A wide grant that says what it is beats a narrow one that only looks narrow. mara's call, on the PR: rather a too-lax scope than one that pretends to be strict. What that buys, beyond honesty: the document is identical for every hive and depends on nothing, so it is written once at startup beside the rest of a hive's provisioning instead of on every declaration. No derived state, no re-emission, and the ordering hazard that came with one stops existing. What still holds is read-only. A hive cannot write an agent's credential, so it cannot hand itself an agent's identity, and the grant reaches nothing in the store outside the agent-credential prefix. The fact is documented where someone meets the boundary rather than only in this message, and the two ways to narrow it later -- scope per hive, or give agents their own store identity -- are tracked.
This commit is contained in:
parent
e638db262e
commit
3752482524
5 changed files with 119 additions and 306 deletions
|
|
@ -731,32 +731,7 @@ fn render(declaration: &swarm_queue_client::wanted::HiveWanted) -> Vec<AgentDecl
|
|||
/// auth-callout traffic and give the two paths independent reconnect state,
|
||||
/// so one could be serving while the other was still down.
|
||||
fn wanted_writer(status: Option<&Arc<status::StatusReader>>) -> Option<Arc<wanted::WantedWriter>> {
|
||||
status.map(|s| {
|
||||
Arc::new(wanted::WantedWriter::new(
|
||||
s.queue_client(),
|
||||
read_policy_sink(),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// The sink that keeps a hive's read grant in step with its declaration, or
|
||||
/// `None` on a deployment that has no store identity to write one with.
|
||||
///
|
||||
/// Decided once here rather than per write: the `BAO_*` environment is a
|
||||
/// systemd unit's, so a variable that is absent at start is absent for the
|
||||
/// life of the process. The absent case is logged because it is otherwise
|
||||
/// indistinguishable from a grant that is being published and ignored.
|
||||
fn read_policy_sink() -> Option<Arc<dyn read_policy::ReadPolicySink>> {
|
||||
match swarm_secret_client::client::Settings::from_env() {
|
||||
Ok(_) => Some(Arc::new(read_policy::StoreSink)),
|
||||
Err(e) => {
|
||||
tracing::info!(
|
||||
reason = %e,
|
||||
"no secret-store identity: hives get no read grant, and no credential delivery either"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
status.map(|s| Arc::new(wanted::WantedWriter::new(s.queue_client())))
|
||||
}
|
||||
|
||||
/// The per-agent status reader, sharing the status reader's connection and
|
||||
|
|
@ -1647,8 +1622,15 @@ async fn main() -> Result<()> {
|
|||
let config_prs = forge_client.clone().map(config_pr::spawn);
|
||||
let state_forge = keep_forge_for_state(forge_client, webhook_secret.clone());
|
||||
|
||||
let hives = load_hives();
|
||||
// Before serving, because a hive whose policy does not exist cannot read
|
||||
// anything this daemon writes for it. Same "log and carry on" shape as
|
||||
// every connect above.
|
||||
read_policy::ensure_hive_policies(&hives.iter().map(|h| h.name.clone()).collect::<Vec<_>>())
|
||||
.await;
|
||||
|
||||
let state = AppState {
|
||||
hives: Arc::new(load_hives()),
|
||||
hives: Arc::new(hives),
|
||||
links: Arc::new(load_links()),
|
||||
wanted: wanted_writer(status.as_ref()),
|
||||
agent_status: agent_status_reader(status.as_ref()),
|
||||
|
|
|
|||
Loading…
Reference in a new issue