refactor(maestro): drop allow_inbound policy gate, keep JWT session binding (NAP-339)
The peer (T2) attach path had a per-pair access-control layer (`[daemon.policy].allow_inbound`): a target-side allow-list of daemon ids permitted to broker-attach, enforced inside JWT verification, hot-reloadable via SIGHUP / `POST /api/policy/reload`, with a `maestro policy` CLI. On a flat-trust perimeter (the operator's VPN/firewall is the boundary for which hosts connect) that granularity is unused, and deny-by-default made it config friction — cross-daemon attach silently failed until every peer was listed. Remove the allow-list; keep the registry-minted pair-attach JWT and ALL its binding checks (iss=registry, aud=target, sid=session, exp). The firewall gates host->port reachability; it does not stop a reachable host from attaching to a session it wasn't handed or replaying a token elsewhere — that's the JWT's job and it stays. Only the `sub in allow_inbound` check is dropped. Removed: DaemonPolicySection / [daemon.policy].allow_inbound (config + daemon), policy.rs (SIGHUP listener + GET/POST /api/policy), the `maestro policy` CLI, PairAttachVerifier's allow-list field/reload/snapshot + JwtError::NotAllowed, and the e2e broker_rejects_when_target_doesnt_allow_inbound test. The NAP-265 pre-upgrade-502 path stays covered by broker_attach_returns_502_on_perimeter_mismatch. Also dropped the unit's ExecReload=kill -HUP: with no SIGHUP handler left, default SIGHUP disposition is terminate, so wiring `systemctl reload` to it would kill the daemon. Re-introducing a per-pair gate later is a verify-time change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,13 +27,12 @@ pub struct Daemon {
|
||||
token: String,
|
||||
}
|
||||
|
||||
/// Optional knobs for `Daemon::spawn_with` — perimeter + allow_inbound for
|
||||
/// Optional knobs for `Daemon::spawn_with` — perimeter advertisement for
|
||||
/// NAP-247 broker tests.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DaemonOptions {
|
||||
pub perimeter_id: Option<String>,
|
||||
pub private_addr: Option<String>,
|
||||
pub allow_inbound: Vec<String>,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
@@ -50,7 +49,7 @@ impl Daemon {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Spawn with NAP-247 options (perimeter advertisement, allow_inbound).
|
||||
/// Spawn with NAP-247 options (perimeter advertisement).
|
||||
pub async fn spawn_with(
|
||||
daemon_id: &str,
|
||||
registry_base_url: &str,
|
||||
@@ -116,11 +115,6 @@ impl Daemon {
|
||||
let state = TempDir::new().expect("state tmpdir");
|
||||
let tmux = TempDir::new().expect("tmux tmpdir");
|
||||
|
||||
// Write a minimal config.toml so allow_inbound (and any future
|
||||
// file-only fields) reach the daemon. Required fields keep coming
|
||||
// through env vars set by `launch`.
|
||||
write_config(home.path(), &opts);
|
||||
|
||||
let daemon_url = format!("ws://{bind}");
|
||||
let child = launch(
|
||||
daemon_id,
|
||||
@@ -219,25 +213,3 @@ fn launch(
|
||||
cmd.spawn()
|
||||
.unwrap_or_else(|e| panic!("spawn {} failed: {e}", bin.display()))
|
||||
}
|
||||
|
||||
/// Write a minimal `~/.config/maestro/config.toml` carrying the NAP-247
|
||||
/// `[daemon.policy] allow_inbound` list. Everything else still comes from
|
||||
/// env vars set by `launch`.
|
||||
fn write_config(home: &std::path::Path, opts: &DaemonOptions) {
|
||||
if opts.allow_inbound.is_empty() {
|
||||
return;
|
||||
}
|
||||
let dir = home.join(".config/maestro");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let mut allow = String::new();
|
||||
for (i, id) in opts.allow_inbound.iter().enumerate() {
|
||||
if i > 0 {
|
||||
allow.push_str(", ");
|
||||
}
|
||||
allow.push('"');
|
||||
allow.push_str(id);
|
||||
allow.push('"');
|
||||
}
|
||||
let body = format!("[daemon.policy]\nallow_inbound = [{allow}]\n");
|
||||
std::fs::write(dir.join("config.toml"), body).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user