feat(maestro): add [daemon.policy] allow_inbound to config (NAP-247)

New [daemon.policy] section with allow_inbound: Vec<String>. Default empty
(deny all peer attaches). The daemon plumbs it through DaemonConfig.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 20:32:06 +00:00
co-authored by Claude Opus 4.7
parent a701d537a3
commit 1f6a12e439
3 changed files with 31 additions and 0 deletions
+23
View File
@@ -141,6 +141,9 @@ pub struct DaemonSection {
/// back to peer daemons as a T2 dial target.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub private_addr: Option<String>,
/// `[daemon.policy]` — per-daemon attach policy (NAP-247).
#[serde(default, skip_serializing_if = "DaemonPolicySection::is_empty")]
pub policy: DaemonPolicySection,
}
impl DaemonSection {
@@ -153,6 +156,23 @@ impl DaemonSection {
&& self.worktree_root.is_none()
&& self.perimeter_id.is_none()
&& self.private_addr.is_none()
&& self.policy.is_empty()
}
}
/// `[daemon.policy]` — application-layer access control complementing network
/// ACLs. Default deny: an empty `allow_inbound` rejects every cross-daemon
/// attach. Local attach (no JWT, same daemon) always bypasses.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DaemonPolicySection {
/// daemon-ids permitted to broker-attach into this daemon's sessions.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allow_inbound: Vec<String>,
}
impl DaemonPolicySection {
fn is_empty(&self) -> bool {
self.allow_inbound.is_empty()
}
}
@@ -311,6 +331,9 @@ mod tests {
worktree_root: Some(PathBuf::from("/var/lib/maestrod/wt")),
perimeter_id: Some("wg-naps62".into()),
private_addr: Some("10.0.0.7:7070".into()),
policy: DaemonPolicySection {
allow_inbound: vec!["konishi".into()],
},
},
registry: RegistrySection {
bind_addr: Some("0.0.0.0:8080".parse().unwrap()),
+5
View File
@@ -83,6 +83,9 @@ pub struct DaemonConfig {
/// Daemon's reachable address on the declared perimeter. Only meaningful
/// when `perimeter_id` is set.
pub private_addr: Option<String>,
/// daemon-ids permitted to broker-attach to this daemon's sessions.
/// Default empty (deny all). Local attach bypasses. (NAP-247.)
pub allow_inbound: Vec<String>,
}
impl DaemonConfig {
@@ -194,6 +197,7 @@ pub fn load_config(path: &Path, env: EnvOverrides) -> Result<DaemonConfig, Confi
let perimeter_id = env.perimeter_id.or(daemon.perimeter_id);
let private_addr = env.private_addr.or(daemon.private_addr);
let allow_inbound = daemon.policy.allow_inbound;
Ok(DaemonConfig {
daemon_id,
@@ -206,6 +210,7 @@ pub fn load_config(path: &Path, env: EnvOverrides) -> Result<DaemonConfig, Confi
worktree_root,
perimeter_id,
private_addr,
allow_inbound,
})
}
+3
View File
@@ -125,6 +125,9 @@ pub fn run(
// are operator-edited in config.toml directly. Leave None on bootstrap.
perimeter_id: None,
private_addr: None,
// Default deny: empty allow_inbound means no peer can attach until the
// operator opts in. Set in config.toml under [daemon.policy].
policy: Default::default(),
};
file.save(path).map_err(ConfigError::from)?;
let _ = handle; // stdout handle only used during prompts; binary prints "wrote ..."