feat(maestro): daemon broker T2 + pair-JWT verifier + allow_inbound (NAP-247)
* New PairAttachVerifier (crates/maestro-daemon/src/jwt.rs): checks signature, aud == self, exp > now, and sub ∈ allow_inbound. Default deny — empty allow_inbound rejects every cross-daemon attach. * /sessions/:id/stream tries the legacy verifier first, falls back to the pair verifier on mismatch. Compat with existing direct-attach. * New BrokerHandle (crates/maestro-daemon/src/broker.rs): shared between the registry-client task and HTTP handlers; calls ResolveAttach over the control link and correlates the reply via a pending map. * RegistryClient now owns a BrokerHandle, drains its outbound mpsc alongside session events / heartbeats, and routes ResolveAttachResult back into the pending map. First heartbeat fires immediately so the registry has the daemon's perimeter info before the first ResolveAttach. * /api/attach/🆔 self-owned → local PTY; peer-owned (per catalog mirror) → ResolveAttach + T2 dial + bidirectional frame proxy. Returns structured errors for missing local perimeter, peer on a different perimeter, etc. T1/T3 stay stubbed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ use std::time::Duration;
|
||||
|
||||
use common::DaemonFixture;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use maestro_daemon::{local_api, CatalogMirror};
|
||||
use maestro_daemon::{local_api, BrokerHandle, CatalogMirror};
|
||||
use maestro_protocol::{CreateSessionRequest, DaemonInfo, RegistryEvent, Session};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -34,7 +34,18 @@ async fn spawn(daemon_id: &str, hostname: &str, daemon_url: &str) -> Harness {
|
||||
let manager = fx.manager.clone();
|
||||
let catalog = CatalogMirror::new();
|
||||
let state = local_api::state_for(manager.clone(), hostname, daemon_url, catalog.clone());
|
||||
let router = local_api::router(state).merge(local_api::broker_router(manager.clone()));
|
||||
// NAP-247: tests don't exercise the cross-host broker path; hand the
|
||||
// router an isolated BrokerHandle whose receiver is dropped, so any
|
||||
// resolve_attach attempt fails fast with NoControlLink.
|
||||
let (broker, _broker_rx) = BrokerHandle::new();
|
||||
let broker_state = local_api::BrokerState {
|
||||
manager: manager.clone(),
|
||||
catalog: catalog.clone(),
|
||||
broker,
|
||||
self_daemon_id: std::sync::Arc::new(daemon_id.into()),
|
||||
self_perimeter_id: None,
|
||||
};
|
||||
let router = local_api::router(state).merge(local_api::broker_router(broker_state));
|
||||
let listener = TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user