swarm-controller: keep a hive's read grant in step with its declaration
The controller writes an agent's credential; the hive fetches it back with its own certificate. Nothing said which paths that certificate may read, so the read half of a delivery answers 403 with no way to tell why. The grant is derived from the declaration, so it is re-rendered at the one place the declaration changes -- WantedWriter::set -- rather than at its caller, which would work today and break on the second caller. Emitted before the KV write: a grant that lands late is a 403 on an agent's first fetch, while one that shrinks early only affects an agent already being torn down. A failed write then leaves a superset the next declaration re-renders. Destroyed agents are filtered out. The declared set is a hive's whole history -- a destroyed entry stays so that redeclaring it Up is refused as the terminal transition it is -- so granting every declared agent would leave a torn-down agent's credentials readable forever. The sink is a trait because a missed emission is that same untraceable 403: the double pins which agents were published, and the no-sink and refusing arms pin the two deployments that are not a happy path. Not covered: the call site inside set(), which needs a live queue. The cert role moves to its own module on the way past. It is the controller's identity at the store, not something the matrix route owns, and the policy writer needs the same login.
This commit is contained in:
parent
c590447e8f
commit
e638db262e
5 changed files with 268 additions and 17 deletions
|
|
@ -26,20 +26,11 @@ use axum::extract::State;
|
|||
use axum::http::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use swarm_queue_client::{CredentialNotice, credential_subject};
|
||||
use swarm_secret_client::{SecretStore, matrix};
|
||||
use swarm_secret_client::matrix;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::{AppState, error_problem, swarm_hive};
|
||||
|
||||
/// The cert-auth role this daemon logs into the secret store as.
|
||||
///
|
||||
/// `nix/host-modules/swarm-bao.nix`'s `controllerPolicyName` creates the role,
|
||||
/// names the policy after it, and `nix/module-eval.nix` pins the literal.
|
||||
///
|
||||
/// ⚠️ Not the certificate's CN. The role *matches on* the CN
|
||||
/// (`allowed_common_names`), so the two are deliberately different strings.
|
||||
const CERT_ROLE: &str = "swarm-controller";
|
||||
|
||||
fn default_mode() -> String {
|
||||
"token".to_owned()
|
||||
}
|
||||
|
|
@ -156,7 +147,7 @@ pub async fn put_matrix_account(
|
|||
// touched, so a failed login leaves no partial state behind.
|
||||
let (token, homeserver, user_id) = resolve_credential(&req).await.map_err(|b| *b)?;
|
||||
|
||||
let store = SecretStore::from_env(CERT_ROLE).await.map_err(|e| {
|
||||
let store = crate::store::connect().await.map_err(|e| {
|
||||
tracing::warn!(error = %e, "connecting to the swarm secret store failed");
|
||||
error_problem(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
|
||||
})?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue